You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Flutter中嵌套的NestedScrollView正常工作?

嵌套NestedScrollView触发栈溢出的解决方案

问题现象

在Flutter中嵌套使用NestedScrollView(内层包含ListView)时,会抛出栈溢出错误:

═════════ Exception caught by widgets library ═══════════════════════════════════
The following StackOverflowError was thrown building PrimaryScrollController(no controller):
Stack Overflow

若给内层ListView绑定PrimaryScrollController,错误会指向ListView:

═════════ Exception caught by widgets library ═══════════════════════════════════
The following StackOverflowError was thrown building ListView(scrollDirection: vertical, _NestedScrollController#81c19(inner, one client, offset 0.0), ClampingScrollPhysics, dependencies: [MediaQuery]):
Stack Overflow

The relevant error-causing widget was
ListView

复现代码

import 'package:flutter/material.dart';

void main() async {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const NestedScrollView1();
  }
}

class NestedScrollView1 extends StatelessWidget {
  const NestedScrollView1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: NestedScrollView(
        physics: const ClampingScrollPhysics(),
        headerSliverBuilder: (_, __) => [
          SliverToBoxAdapter(
            child: Container(
              color: Colors.blue,
              height: 100,
            ),
          )
        ],
        body: NestedScrollView2(),
      ),
    );
  }
}

class NestedScrollView2 extends StatelessWidget {
  final ScrollController scrollController = ScrollController();
  NestedScrollView2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return NestedScrollView(
      controller: PrimaryScrollController.of(context),
      physics: const ClampingScrollPhysics(),
      headerSliverBuilder: (ctx, __) => [
        SliverToBoxAdapter(
          child: Container(
            color: Colors.red,
            height: 100,
          ),
        ),
      ],
      body: const ListOfItems(),
    );
  }
}

class ListOfItems extends StatelessWidget {
  const ListOfItems({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView(
      physics: const ClampingScrollPhysics(),
      // controller: PrimaryScrollController.of(context),
      children: [
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
      ],
    );
  }
}

场景说明

实际项目中存在嵌套TabBarView结构:外层页面包含TabBarView,其中一个子项又有独立的TabBarView,希望通过NestedScrollView的headerSliverBuilder来承载标签栏,需要无需调整UI结构或编写复杂自定义滚动逻辑的解决办法。

解决办法

核心思路:避免NestedScrollView嵌套

NestedScrollView的设计初衷是处理单一层级的嵌套滚动(比如header+可滚动body),嵌套使用会导致ScrollController的循环引用,进而触发栈溢出。以下是两种低成本的替代方案:

方案1:内层用CustomScrollView替代NestedScrollView

把内层的NestedScrollView换成CustomScrollView,既保留header+列表的结构,又不会和外层NestedScrollView产生控制器冲突:

class NestedScrollView2 extends StatelessWidget {
  NestedScrollView2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      physics: const ClampingScrollPhysics(),
      slivers: [
        // 原内层NestedScrollView的header
        SliverToBoxAdapter(
          child: Container(
            color: Colors.red,
            height: 100,
          ),
        ),
        // 将ListView转为SliverList
        SliverList(
          delegate: SliverChildListDelegate([
            Container(color: Colors.green, height: 200),
            Container(color: Colors.yellow, height: 200),
            Container(color: Colors.green, height: 200),
            Container(color: Colors.yellow, height: 200),
            Container(color: Colors.green, height: 200),
            Container(color: Colors.yellow, height: 200),
            Container(color: Colors.green, height: 200),
            Container(color: Colors.yellow, height: 200),
          ]),
        ),
      ],
    );
  }
}

方案2:给内层NestedScrollView使用独立控制器

如果必须保留内层NestedScrollView,不要使用父级的PrimaryScrollController,而是创建独立的ScrollController,同时禁止内层ListView的滚动物理效果,让它跟随外层滚动:

class NestedScrollView2 extends StatelessWidget {
  // 创建独立的控制器,不依赖父级PrimaryScrollController
  final ScrollController _innerController = ScrollController();
  NestedScrollView2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return NestedScrollView(
      controller: _innerController,
      physics: const ClampingScrollPhysics(),
      headerSliverBuilder: (ctx, __) => [
        SliverToBoxAdapter(
          child: Container(
            color: Colors.red,
            height: 100,
          ),
        ),
      ],
      body: const ListOfItems(),
    );
  }
}

class ListOfItems extends StatelessWidget {
  const ListOfItems({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView(
      // 禁止ListView自身滚动,跟随外层NestedScrollView
      physics: const NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      children: [
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
        Container(color: Colors.green, height: 200),
        Container(color: Colors.yellow, height: 200),
      ],
    );
  }
}

针对嵌套TabBarView的优化建议

如果是外层TabBarView嵌套内层TabBarView的场景,建议将外层的NestedScrollView的body直接设置为外层TabBarView,每个外层Tab的内容如果需要header+列表,直接用CustomScrollView实现,这样整个页面的滚动逻辑统一,不会出现控制器冲突。


内容的提问来源于stack exchange,提问作者Linet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 00:06:24