Search code examples
flutterflutter-layoutflutter-sliver

Flutter tabsView and NestedScrollView scroll issue


I have a "NestedScrollView" that contains multiple "TabView" widgets and each one of those tabs has a list builder. The problem is that when i scroll through one list in a specific tab, the scroll position affects all the other lists in the other tabs.

Even if i do add "ScrollController" to each of the listview (in tabs), The listBuilder inside the tab scroll gets separated from the "NestedScrollView" here is an example code :

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,) ;

  }
}

Solution

  • after 3 days i found this is the best solution to this problem ,but still need more improvement , cuz the sliver header expand and shrink too fast , you can improve code , and share us

    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,
        );
      }
    }