Nested Scroll View with TabBarView and Keep Alive true, makes all scrolls sync in TabBarView pages in Flutter

280 Views Asked by At

In the flutter, I use NestedScrollView, in the Body I use TabBarView. On the pages in TabBarView, I use keep alive true, but in this case the scroll is synchronized. How can I not synchronize them.

Dartpad.dev Link: https://dartpad.dev/?id=d2b17ba70aa2be26002352bc6601b6a6

Github Gist Link: https://gist.github.com/demirdev/d2b17ba70aa2be26002352bc6601b6a6

Video

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NestedScroll View with TabBarView',
      home: PostsPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class PostsPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _PostsPageState();
}

class _PostsPageState extends State<PostsPage> {
  final List<String> _tabs = <String>[
    "Featured",
    "Popular",
    "Latest",
  ];

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: DefaultTabController(
          length: _tabs.length,
          child: NestedScrollView(
            body: TabBarView(
              children: _tabs.map((String name) {
                return TestTabItem(name);
              }).toList(),
            ),
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return [
                SliverToBoxAdapter(
                  child: Container(
                    color: Colors.blue.withOpacity(0.2),
                    height: 100,
                  ),
                ),
                SliverAppBar(
                  pinned: true,
                  flexibleSpace: TabBar(
                    tabs: _tabs.map((String name) => Tab(text: name)).toList(),
                  ),
                ),
              ];
            },
          ),
        ),
      ),
    );
  }
}

class TestTabItem extends StatefulWidget {
  const TestTabItem(
    this.name, {
    super.key,
  });

  final String name;

  @override
  State<TestTabItem> createState() => _TestTabItemState();
}

class _TestTabItemState extends State<TestTabItem>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: <Widget>[
        SliverPadding(
          padding: const EdgeInsets.all(8),
          sliver: SliverList.list(
            children: [
              for (int i = 0; i < 30; i++)
                Container(
                  height: 100,
                  margin: const EdgeInsets.only(bottom: 8),
                  padding: const EdgeInsets.all(8),
                  color: Theme.of(context).secondaryHeaderColor,
                  child: Text(
                    '$i',
                    style: Theme.of(context).textTheme.headlineLarge,
                  ),
                )
            ],
          ),
        ),
      ],
    );
  }

  @override
  bool get wantKeepAlive => true;
}
2

There are 2 best solutions below

1
Thabet Alsaleh On

in TestTabItem class you just add "ScrollController" for every "CustomScrollView" Widget

like here:

class _TestTabItemState extends State<TestTabItem>
    with AutomaticKeepAliveClientMixin {

  // Add this line
  ScrollController _scrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
    controller: _scrollController, // add controller here 
    ...
    ...
    ...
    }
 }

2
lilvagg On

It seems like the problem you're experiencing is related to the NestedScrollView in Flutter. The NestedScrollView is designed to make all scrollable children share the same ScrollController, which causes the scroll to be synchronized across all tabs.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NestedScroll View with TabBarView',
      home: PostsPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class PostsPage extends StatefulWidget {
  @override
  _PostsPageState createState() => _PostsPageState();
}

class _PostsPageState extends State<PostsPage> {
  final List<String> _tabs = <String>[
    "Featured",
    "Popular",
    "Latest",
  ];

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: DefaultTabController(
          length: _tabs.length,
          child: Builder(builder: (BuildContext context) {
            return NestedScrollView(
              body: TabBarView(
                children: _tabs.map((String name) {
                  return TestTabItem(name, key: PageStorageKey<String>(name));
                }).toList(),
              ),
              headerSliverBuilder:
                  (BuildContext context, bool innerBoxIsScrolled) {
                return [
                  SliverToBoxAdapter(
                    child: Container(
                      color: Colors.blue.withOpacity(0.2),
                      height: 100,
                    ),
                  ),
                  SliverAppBar(
                    pinned: true,
                    flexibleSpace: TabBar(
                      tabs:
                          _tabs.map((String name) => Tab(text: name)).toList(),
                    ),
                  ),
                ];
              },
            );
          }),
        ),
      ),
    );
  }
}

class TestTabItem extends StatefulWidget {
  const TestTabItem(
    this.name, {
    required Key key,
  }) : super(key: key);

  final String name;

  @override
  State<TestTabItem> createState() => _TestTabItemState();
}

class _TestTabItemState extends State<TestTabItem>
    with AutomaticKeepAliveClientMixin {
  final ScrollController _scrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: 30,
      itemBuilder: (context, index) {
        return Container(
          height: 100,
          margin: const EdgeInsets.only(bottom: 8),
          padding: const EdgeInsets.all(8),
          color: Theme.of(context).secondaryHeaderColor,
          child: Text(
            '$index',
            style: Theme.of(context).textTheme.headline4,
          ),
        );
      },
    );
  }

  @override
  bool get wantKeepAlive => true;
}