【问题标题】:Flutter tabsView and NestedScrollView scroll issueFlutter tabsView 和 NestedScrollView 滚动问题
【发布时间】: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,) ;

  }
}

【问题讨论】:

    标签: flutter flutter-layout flutter-sliver


    【解决方案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,你能回答这个问题吗?会有很大的帮助! stackoverflow.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(),
                ),
              ),
            ),
          ),
        );
      }
    }
    

    【讨论】:

    • 谢谢爸爸的帮助
    猜你喜欢
    • 2015-08-28
    • 1970-01-01
    • 1970-01-01
    • 2017-03-11
    • 2019-02-20
    • 1970-01-01
    • 2020-09-10
    • 1970-01-01
    • 2021-04-26
    相关资源
    最近更新 更多