Flutter NestedScrollView + TabBarView + RefreshIndicator错误

时间:2020-08-04 01:58:32

标签: flutter flutter-layout

为NestedScrollView使用doc示例代码,但有很多问题。

  • 如果未设置SliderAppBar的expandedHeight,则在列表视图内部 tabbarview无法垂直滚动。
  • 所以我评论了SliverOverlapAbsorber 和SliverOverlapInjector代码,并且可以垂直滚动
  • 但是 保留tabbarview中的每个页面,我用 AutomaticKeepAliveClientMixin和wantKeepAlive设置为true,
  • 每个 页面上有一个RefreshIndicator,滑动时刷新一个页面,另一页面刷新 所有页面触发onRefresh并同时显示刷新进度 并且所有页面滚动位置都同步到同一位置。
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> _tabs = [
    "AAA",
    "BBB",
    "CCC",
    "DDD",
    "EEE",
    "FFF",
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: _tabs.length, // This is the number of tabs.
      child: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          // These are the slivers that show up in the "outer" scroll view.
          return <Widget>[
//            SliverOverlapAbsorber(
              // This widget takes the overlapping behavior of the SliverAppBar,
              // and redirects it to the SliverOverlapInjector below. If it is
              // missing, then it is possible for the nested "inner" scroll view
              // below to end up under the SliverAppBar even when the inner
              // scroll view thinks it has not been scrolled.
              // This is not necessary if the "headerSliverBuilder" only builds
              // widgets that do not overlap the next sliver.
//              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
//              sliver:
          SliverAppBar(
                title: const Text('Books'), // This is the title in the app bar.
                pinned: true,
                floating: false,
                snap: false,
                // The "forceElevated" property causes the SliverAppBar to show
                // a shadow. The "innerBoxIsScrolled" parameter is true when the
                // inner scroll view is scrolled beyond its "zero" point, i.e.
                // when it appears to be scrolled below the SliverAppBar.
                // Without this, there are cases where the shadow would appear
                // or not appear inappropriately, because the SliverAppBar is
                // not actually aware of the precise position of the inner
                // scroll views.
                forceElevated: innerBoxIsScrolled,
                bottom: TabBar(
                  isScrollable: true,
                  // These are the widgets to put in each tab in the tab bar.
                  tabs: _tabs.map((String name) => Tab(text: name)).toList(),
                ),
              ),
//            ),
          ];
        },
        body: TabBarView(
          // These are the contents of the tab views, below the tabs.
          children: _tabs.map((String name) {
            return SafeArea(
              top: false,
              bottom: false,
              child: Builder(
                // This Builder is needed to provide a BuildContext that is
                // "inside" the NestedScrollView, so that
                // sliverOverlapAbsorberHandleFor() can find the
                // NestedScrollView.
                builder: (BuildContext context) {
                  return RefreshIndicator(child: CustomScrollView(
                    // The "controller" and "primary" members should be left
                    // unset, so that the NestedScrollView can control this
                    // inner scroll view.
                    // If the "controller" property is set, then this scroll
                    // view will not be associated with the NestedScrollView.
                    // The PageStorageKey should be unique to this ScrollView;
                    // it allows the list to remember its scroll position when
                    // the tab view is not on the screen.
                    key: PageStorageKey<String>(name),
                    slivers: <Widget>[
//                      SliverOverlapInjector(
//                        // This is the flip side of the SliverOverlapAbsorber
//                        // above.
//                        handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
//                      ),
                      SliverPadding(
                        padding: const EdgeInsets.all(0),
                        // In this example, the inner scroll view has
                        // fixed-height list items, hence the use of
                        // SliverFixedExtentList. However, one could use any
                        // sliver widget here, e.g. SliverList or SliverGrid.
                        sliver: TabPage(),
                      ),
                    ],
                  ), onRefresh: () => Future.delayed(Duration(seconds: 5)));
                },
              ),
            );
          }).toList(),
        ),
      ),
    );

  }
}

class TabPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _TabPageState();
  }

}

class _TabPageState extends State<TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return SliverFixedExtentList(
      // The items in this example are fixed to 48 pixels
      // high. This matches the Material Design spec for
      // ListTile widgets.
      itemExtent: 48.0,
      delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index) {
          // This builder is called for each child.
          // In this example, we just number each list item.
          return Material(child: ListTile(
            title: Text('Item $index'),
          ));
        },
        // The childCount of the SliverChildBuilderDelegate
        // specifies how many children this inner list
        // has. In this example, each tab has a list of
        // exactly 30 items, but this is arbitrary.
        childCount: 30,
      ),
    );
  }
}

1 个答案:

答案 0 :(得分:0)

要解决使用SliverOverlapAbsorber时的垂直滚动问题,请将此行添加到SliverOverlapAbsorber上方。

SliverToBoxAdapter(child: Container(height: 0.1,),),

不知道为什么会这样,但这是我可以想到的解决方案。