I am trying to create an app with a top application bar and a tab bar below. When you scroll down, the bar should hide by moving off the screen (but tabs should stay), and when you scroll back up, the application bar should show again. This behaviour can be seen in WhatsApp. Please see this video for a demonstration. (Taken from Material.io). This is a similar behaviour, although the app bar and tab bar are hidden on scroll, so it is not exactly the behaviour I am looking for.
I have been able to achieve the autohiding, however, there are a few issues:
I have to set the snap
of the SliverAppBar
to true
. Without this, the application bar will not show when I scroll back up.
Although this is works, it is not the behaviour I am looking for. I want the application bar to show smoothly (similar to WhatsApp) rather than coming into view even if you scroll very little.
To clarify, when I scroll all the way down, even if I scroll up very little, the app bar should come into view. I do not want to have to scroll all the way up to see the app bar.
When I scroll down and change tabs, a little bit of the content is cut out of view.
Below is a GIF showing the behaviour:
(See the part when I scroll down on the listView (tab1), then move back to tab2)
Here is the code for the DefaultTabController
:
DefaultTabController(
length: 2,
child: new Scaffold(
body: new NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
title: Text("Application"),
floating: true,
pinned: true,
snap: true, // <--- this is required if I want the application bar to show when I scroll up
bottom: new TabBar(
tabs: [ ... ], // <-- total of 2 tabs
),
),
];
},
body: new TabBarView(
children: [ ... ] // <--- the array item is a ListView
),
),
),
),
In case it is needed, the full code is in this GitHub repository. main.dart
is here.
I also found this related question: Hide Appbar on Scroll Flutter?. However, it did not provide the solution. The same problems persist, and when you scroll up, the SliverAppBar
will not show. (So snap: true
is required)
I also found this issue on Flutter's GitHub. (Edit: someone commented that they are waiting for the Flutter team to fix this. Is there a possibility that there is no solution?)
This is the output of flutter doctor -v
: Pastebin. Certain issues are found, but from what I have learned, they should not have an impact.
Edit: There are two issues for this:
If you want to see Sliver App Bar expanded as soon as someone scrolls up i.e. not scrolling all the way to top but just little bit, Then just change snap: false
to snap: true
in code :)
After surfing google, stackoverflow, github issues, reddit for hours. I could finally come up with a solution that addresses following:
Sliver App bar with title getting hidden and only tab bar visible after scrolling down. You would see title again when you reach top.
MAJOR : When you scroll in Tab 1 & then Navigate to Tab 2, you would not see any overlap. The content of Tab 2 will not get obstructed by Sliver App bar.
Sliver Padding for top most element in List is 0.
Preserves the Scroll Position in individual Tabs
below is the code, I would attempt to explain in a bit (dartpad preview) :
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final List<String> _tabs = <String>['Tab 1', 'Tab 2'];
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title: const Text('Books'),
floating: true,
pinned: true,
snap: false,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (BuildContext context) {
return CustomScrollView(
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
Test it out all you want in dartpad, once you are fine then lets try to understand what is happening here.
Most of the code is from flutter documentation of NestedScrollView
They have mentioned very nicely in comments. I am no expert so I would just highlight what I think solved most of the issues.
I believe Two things are critical here:
SliverOverlapAbsorber
& SliverOverlapInjector
SliverList
instead of ListView
Whatever extra space we were seeing or the space which sliver app bar consumed and first list item was overlapped was mainly resolved with the use of above two points.
To remember the scroll position of tabs, they added PageStorageKey
inside CustomScrollView
:
key: PageStorageKey<String>(name),
name
is just a string -> 'Tab 1'
They also mentioned in docs that we can use SliverFixedExtentList, SliverGrid, basically Sliver
widgets. Now use of Sliver widgets should be done when needed. In one of the Flutter Youtube videos (official channel) they mentioned that ListView, GridView, are all high level implementation of Slivers. So Slivers is low level stuff if you looking to super customize scrolling or appearance behaviour.
Please let me know in comments if I missed something or said wrong.