Noor Dawod
Noor Dawod

Reputation: 779

Nested scrollbars issue: NestedScrollView, SliverAppBar, SliverPersistentHeader, TabBarView, and PageView

This issue has been haunting me for some time and even though I read countless web pages, I still cannot solve it. Maybe you're able to help out!

I got the following scenario:

A Flutter app has a PageView with just 3 pages.

First page has a simple GridView (vertical scrolling) and no problem there with nested scrolling.

Third page is a simple ListView with lists of items, no problem here also with scrolling.

The second page has a NestedScrollView, with two widgets for its headerSliverBuilder: SliverAppBar and SliverPersistentHeader. Nothing fancy here.

The body of the NestedScrollView contains a TabBarView which contains 3 tabs, and it's possible to swipe between them using a horizontal swipe -- the same swipe direction as the PageView which contains this page.

The body is where the scrolling problem occurs.

Swiping between the 3 tabs works like a charm. However, when the current tab is the first and you try to swipe further to the left (finger motion is from left-to-right), the first page (belonging to PageView) doesn't show. Conversely, when the current tab is the 3rd and you try to swipe further to the right (finger motion is from right-to-left), nothing happens.

If you do the same finger motion on the header contained in the second page, the pages turn fine (either to the first or third pages).

Here's the code inside the second page, would love to know why swiping motion inside the TabBarView isn't propagating to the container when tabs reach the edges:

Scaffold(
  appBar: _generateAppBar(),
  body: DefaultTabController(
    length: 3,
    child: NestedScrollView(
      headerSliverBuilder: (_, __) => [
        SliverAppBar(
          backgroundColor: backgroundColor,
          elevation: 0.0,
          expandedHeight: 200.0,
          floating: true,
          pinned: false,
          flexibleSpace: backgroundImageView,
        ),
        SliverPersistentHeader(
          floating: false,
          delegate: _SliverAppBarDelegate(
            TabBar(
              labelColor: Theme.of(context).primaryColor,
              unselectedLabelColor: Colors.black26,
              indicatorWeight: 2.5,
              tabs: const [
                Text('Tab 1'),
                Text('Tab 2'),
                Text('Tab 3'),
              ],
            ),
          ),
          pinned: true,
        ),
      ],
      body: TabBarView(
        children: [
          Center(child: Text('Body 1')),
          Center(child: Text('Body 2')),
          Center(child: Text('Body 3')),
        ],
      ),
    ),
  ),
);

Auxiliary class:

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

  final TabBar _tabBar;

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

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

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) =>
    Container(child: _tabBar);

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) =>
    false;
}

Any idea how to fix this? Thanks!

Upvotes: 0

Views: 2844

Answers (1)

Matso Abgaryan
Matso Abgaryan

Reputation: 716

You should wrap the NestedScrollView like

DefaultTabController(
      length: tabs.length,
      child: NestedScrollView())

Upvotes: 1

Related Questions