Flutter tabsView 和 NestedScrollView 滚动问题

Posted

技术标签:

【中文标题】Flutter tabsView 和 NestedScrollView 滚动问题【英文标题】:Flutter tabsView and NestedScrollView scroll issue 【发布时间】:2019-07-08 10:22:17 【问题描述】:

我有一个“NestedScrollView”,其中包含多个“TabView”小部件,每个选项卡都有一个列表构建器。问题是当我滚动浏览特定选项卡中的一个列表时,滚动位置会影响其他选项卡中的所有其他列表。

即使我确实将“ScrollController”添加到每个列表视图(在选项卡中),选项卡滚动内的 listBuilder 也会与“NestedScrollView”分离 这是一个示例代码:

import 'package:flutter/material.dart';

void main() => runApp(
  MaterialApp(
    home:     MyApp()
    ,
  )
);


class MyApp extends StatefulWidget
  MyAppState createState() => MyAppState();


class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin 

  TabController tabController;
  Widget _tabBarView;

  @override
  void initState() 
    super.initState();
    tabController = TabController(length: 2, vsync: this,);
    _tabBarView = TabBarView(
        children: [
          DemoTab(),
          DemoTab(),
        ]);
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: NestedScrollView(
          controller: ScrollController(keepScrollOffset: true),
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) 
            return <Widget>[
              SliverList(
                delegate: SliverChildListDelegate(
                    [
                      Container(height: 300, color: Colors.blue)
                    ]
                ),
              ),
            ];
          ,
          body: DefaultTabController(
            length: 2,
            child: Column(
              children: <Widget>[
                Expanded(
                  child: Container(
                      child: _tabBarView
                  ),
                ),
              ],
            ),
          )
      ),
    );
  


class DemoTab extends StatefulWidget
  DemoTabState createState() => DemoTabState();


class DemoTabState extends State<DemoTab> with AutomaticKeepAliveClientMixin<DemoTab>
  @override
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;


  @override
  Widget build(BuildContext context) 

    return ListView.builder(
      key: UniqueKey(),
      itemBuilder: (b, i) 
      return Container(
        height: 50,
        color: Colors.green,
        margin: EdgeInsets.only(bottom: 3),
        child: Text(i.toString(),),
      );
    , itemCount: 30,) ;

  

【问题讨论】:

【参考方案1】:

3天后我发现这是解决这个问题的最佳方法,但还需要改进,因为sliver header扩展和收缩太快,你可以改进代码,分享给我们

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
  home: MyApp(),
));

class MyApp extends StatefulWidget 
  MyAppState createState() => MyAppState();


class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin 
  TabController tabController;
  Widget _tabBarView;
  var scrollController = ScrollController();

  @override
  void initState() 
    super.initState();
    tabController = TabController(
      length: 2,
      vsync: this,
    );
    _tabBarView = TabBarView(children: [
      DemoTab(parentController : scrollController),
      DemoTab(parentController : scrollController),
    ]);
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: NestedScrollView(
          controller: scrollController,
          physics: ScrollPhysics(parent: PageScrollPhysics()),
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) 
            return <Widget>[
              SliverList(
                delegate: SliverChildListDelegate(
                    [Container(height: 300, color: Colors.blue)]),
              ),
            ];
          ,
          body: DefaultTabController(
            length: 2,
            child: Column(
              children: <Widget>[
                Container(
                  child: TabBar(labelColor: Colors.grey, tabs: [
                    Tab(
                      text: 'One',
                    ),
                    Tab(
                      text: 'two',
                    )
                  ]),
                ),
                Expanded(
                  child: Container(child: _tabBarView),
                ),
              ],
            ),
          )),
    );
  


class DemoTab extends StatefulWidget 

  DemoTab( 
    this.parentController
  );

  final ScrollController parentController;


  DemoTabState createState() => DemoTabState();


class DemoTabState extends State<DemoTab>
    with AutomaticKeepAliveClientMixin<DemoTab> 
  @override
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;

  ScrollController _scrollController;

  ScrollPhysics ph;
  @override
  void initState() 
    super.initState();
    _scrollController = ScrollController();


    _scrollController.addListener(()


      var innerPos      = _scrollController.position.pixels;
      var maxOuterPos   = widget.parentController.position.maxScrollExtent;
      var currentOutPos = widget.parentController.position.pixels;

      if(innerPos >= 0 && currentOutPos < maxOuterPos) 

        //print("parent pos " + currentOutPos.toString() + "max parent pos " + maxOuterPos.toString());
        widget.parentController.position.jumpTo(innerPos+currentOutPos);

      else
        var currenParentPos = innerPos + currentOutPos;
        widget.parentController.position.jumpTo(currenParentPos);
      


    );





    widget.parentController.addListener(()
      var currentOutPos = widget.parentController.position.pixels;
      if(currentOutPos <= 0) 
        _scrollController.position.jumpTo(0);
      
    );


  

  @override
  Widget build(BuildContext context) 
    return ListView.builder(
      key: UniqueKey(),
      controller: _scrollController,
      itemBuilder: (b, i) 
        return Container(
          height: 50,
          color: Colors.green,
          margin: EdgeInsets.only(bottom: 3),
          child: Text(
            i.toString(),
          ),
        );
      ,
      itemCount: 30,
    );
  

【讨论】:

任何其他解决方案 @gowthamanC 目前正在研究一种解决此问题的新方法,我将在本周末尝试覆盖此答案 如果bottomnavbar也添加到这里怎么办? @LOG_TAG 之间没有关系,但是如果有任何意外行为,请分享您的代码,我很感兴趣 @abdalmonem,你能回答这个问题吗?会有很大的帮助! ***.com/questions/69795503/…【参考方案2】:

您可以使用 SliverSafeArea 和 SliverOverlapAbsorber。

class NewsScreen extends StatefulWidget 
  @override
  State<StatefulWidget> createState() => _NewsScreenState();


class _NewsScreenState extends State<NewsScreen> 
  final List<String> listItems = [];

  final List<String> _tabs = <String>[
    "Featured",
    "Popular",
    "Latest",
  ];

  @override
  Widget build(BuildContext context) 
    return Material(
      child: Scaffold(
        body: 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),
                  child: SliverSafeArea(
                    top: false,
                    sliver: SliverAppBar(
                      title: const Text('Books'),
                      floating: true,
                      pinned: true,
                      snap: false,
                      primary: true,
                      forceElevated: innerBoxIsScrolled,
                      bottom: TabBar(
                        // 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 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(8.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: SliverFixedExtentList(
                              // The items in this example are fixed to 48 pixels
                              // high. This matches the Material Design spec for
                              // ListTile widgets.
                              itemExtent: 60.0,
                              delegate: SliverChildBuilderDelegate(
                                (BuildContext context, int index) 
                                  // This builder is called for each child.
                                  // In this example, we just number each list item.
                                  return Container(
                                      color: Color((math.Random().nextDouble() *
                                                      0xFFFFFF)
                                                  .toInt() <<
                                              0)
                                          .withOpacity(1.0));
                                ,
                                // 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,
                              ),
                            ),
                          ),
                        ],
                      );
                    ,
                  ),
                );
              ).toList(),
            ),
          ),
        ),
      ),
    );
  

【讨论】:

谢谢爸爸的帮助

以上是关于Flutter tabsView 和 NestedScrollView 滚动问题的主要内容,如果未能解决你的问题,请参考以下文章

Nested组件,解决Flutter布局嵌套过深的利器

Flutter跨组件共享状态的利器Provider简析

Flutter跨组件共享状态的利器Provider框架简析

如何从 Prism 中的 RequestNavigate 方法获取回调?

mybatis的嵌套查询(嵌套查询nested select和嵌套结果nested results查询)区别

如何将复杂(嵌套)对象解析为 JSON 并在 Flutter 中使用 HTTP 将其发送到服务器?