cloudwalker
cloudwalker

Reputation: 2456

How to achieve this using NestedScrollView?

I've boiled my code down to a small sample app, so considering the following code:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  TabController _tabController;
  ScrollController _scrollController;
  ScrollController _sliverScrollController;

  var tabs = ['Tab1', 'Tab2', 'Tab3'];

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: 3);
    _scrollController = ScrollController();
    _sliverScrollController = ScrollController();
  }

  @override
  void dispose() {
    // "Unmount" the controllers:
    _tabController.dispose();
    _scrollController.dispose();
    _sliverScrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
        length: 3,
        child: Scaffold(
          body: NestedScrollView(
            controller: _scrollController,
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return [
                SliverOverlapAbsorber(
                    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                        context),
                    sliver: SliverAppBar(
                        backgroundColor: Colors.black,
                        pinned: true,
                        expandedHeight: 400,
                        elevation: 0,
                        flexibleSpace: FlexibleSpaceBar(
                            collapseMode: CollapseMode.pin,
                            background: Flex(
                              direction: Axis.vertical,
                              children: <Widget>[
                                Padding(
                                    padding: EdgeInsets.only(
                                        top: 75,
                                        left: 10,
                                        right: 10,
                                        bottom: 5),
                                    child: Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.start,
                                      children: <Widget>[
                                        Text('Testing',
                                            overflow: TextOverflow.ellipsis,
                                            maxLines: 2,
                                            style: TextStyle(
                                                color: Colors.white,
                                                fontSize: 18,
                                                fontWeight: FontWeight.bold)),
                                        Padding(
                                            padding:
                                                EdgeInsets.only(bottom: 5)),
                                        Text('More Text',
                                            style: TextStyle(
                                                color: Colors.white,
                                                fontSize: 18,
                                                fontWeight: FontWeight.bold))
                                      ],
                                    ))
                              ],
                            )))),
                SliverPersistentHeader(
                    delegate: _SliverAppBarDelegate(PreferredSize(
                        preferredSize:
                            Size(MediaQuery.of(context).size.width, 30),
                        child: TabBar(
                          labelColor: Colors.white,
                          tabs: tabs
                              .map((String name) => Container(
                                  color: Colors.blue, child: Tab(text: name)))
                              .toList(),
                        ))),
                    pinned: true)
              ];
            },
            body: TabBarView(
              children: [_buildPanel1(), _buildPanel2(), _buildPanel3()],
            ),
          ),
        ));
  }

  _wrapTabWidget(tabWidget) {
    return Builder(
      builder: (BuildContext context) {
        return CustomScrollView(
          controller: _sliverScrollController,
          slivers: <Widget>[
            SliverOverlapInjector(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
            ),
            SliverToBoxAdapter(
                child: Padding(
                    padding: EdgeInsets.only(top: 10, left: 10, right: 10),
                    child: tabWidget))
          ],
        );
      },
    );
  }

  _buildPanel1() {
    return _wrapTabWidget(ListView(
        shrinkWrap: true,
        children: new List<Widget>.generate(
            100,
            (i) => ListTile(
                leading: Icon(Icons.shopping_cart), title: Text('item $i')))));
  }

  _buildPanel2() {
    return _wrapTabWidget(Text('Panel 2'));
  }

  _buildPanel3() {
    return _wrapTabWidget(Text('Panel 3'));
  }
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate(this._tabBar);

  final PreferredSizeWidget _tabBar;

  @override
  double get minExtent => _tabBar.preferredSize.height;

  @override
  double get maxExtent => _tabBar.preferredSize.height;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new Container(
      color: Colors.blue,
      child: _tabBar,
    );
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return false;
  }
}

What I would really like to do is effectively apply the same "scroll controller" to the whole page and be able to scroll the list widget in tab1 and have that scroll the TabBar up and anchor it to the top and then continue to scroll within the ListView.

So, if I were to tap and hold here:

enter image description here

and then start to scroll, it would scroll the whole screen until the TabBar reaches the top and then pin the TabBar there and continue scrolling the ListView at the bottom.

I hope I'm explaining this well enough! :)

Upvotes: 0

Views: 455

Answers (1)

Lucky Dog
Lucky Dog

Reputation: 628

Is this what you want?

class TestWidget extends StatefulWidget {
  @override
  _TestWidgetState createState() => _TestWidgetState();
}

class _TestWidgetState extends State<TestWidget>
    with SingleTickerProviderStateMixin {
  bool isAbsorbing = false;

  TabController _primaryTC;

  final List<Tab> tabs = [
    Tab(
      text: "tab1",
    ),
    Tab(
      text: "tab2",
    ),
    Tab(
      text: "tab3",
    )
  ];

  @override
  void initState() {
    super.initState();
    _primaryTC = TabController(length: tabs.length, vsync: this);
    _primaryTC.index = tabs.length - 1;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: NestedScrollView(
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return []..add(SliverAppBar(
                  flexibleSpace: FlexibleSpaceBar(
                    title: Text(""),
                  ),
                  expandedHeight: 500,
                  pinned: true,
                  floating: true,
                  snap: false,
                ));
            },
            body: Container(
              child: Column(
                children: <Widget>[
                  TabBar(
                    tabs: tabs,
                    controller: _primaryTC,
                  ),
                  Expanded(flex:1,child: TabBarView(
                      controller: _primaryTC,
                      children: tabs
                          .map((tab) => Container(
                        child: ListView.builder(
                            itemCount: 30,
                            itemBuilder: (context, index) {
                              return Container(height: 50,alignment: Alignment.center,child: Text("test"),);
                            }),
                        width: double.infinity,
                        height: double.infinity,
                        alignment: Alignment.center,
                      ))
                          .toList()))
                ],
              ),
            )),
      ),
    );
  }
}

Upvotes: 1

Related Questions