Dynamic Header in Flutter App

How to make AppBar with dynamic height using Sliver in Flutter

Matatias Situmorang
5 min readMay 17, 2023

*Some sentences are generated by AI 🚀 🚀 🚀

Flutter AppBar is a Material Design app bar that consists of a toolbar and potentially other widgets, such as a TabBar and a FlexibleSpaceBar. It is usually placed at the top of the screen and has the ability to contain other widgets within its layout. The AppBar commonly displays branding information such as logos and titles and often contains buttons or other points of user interaction.

The AppBar can be customized by changing its background color, elevation, leading widget, title widget, actions widget, etc.

Here’s an example of how to use the background-color property to change the background color of an AppBar:

appBar: AppBar (
title: const Text ('Title'),
backgroundColor: Colors.green,
),

In this article, we will explore about SliverAppBar to make a dynamic header on Flutter. By the end, you will know how to make a screen like the one below:

dynamic AppBar

If we slice the screen above, we can divide it into 3 parts.

  • Collapsed AppBar
Collapsed AppBar
  • Expanded AppBar
Expand AppBar
  • Body
body widget

Did you notice that the body and the AppBar are scrollable? 😲

So here there are 2 combined scrollable widgets. In Flutter usually, we can't use 2 scrollable widgets on a screen. It will throw an error.

error nested Scrollable widget

For this issue, we need to use NestedScrollView or CustomScrollView widget to handle 2 scrollable widgets. But to really understand this workaround, we need to learn about slivers in Flutter.

Do you familiar with sliver (not silver) in Flutter?

A sliver is a portion of a scrollable area, which means anything that scrolls in Flutter is a sliver. If you’ve used ListView or GridView, congratulations! You've already used a sliver in your Flutter app. Most often, slivers are wrapped in convenience classes such as these. This is because slivers use a different layout protocol from most other widgets in the Flutter framework. [ref]

As a novice in Flutter, I usually use ListView or SingleChildScrollview in my apps. And for complex UI like nested scroll widgets, ListView is not enough. That's why we need to learn more about sliver.

To learn about Sliver in Flutter, you can follow this awesome workshop:

DartPad Workshops Getting started with slivers

ok back to the screen…

We will use NestedScrollView and SliverAppBar( you can find them in the workshop step 5).

Now we have to listen to the ScrollController so that we know when to hide the expanded AppBar and then show the collapsed AppBar and vice versa. We will have the StatefullWidget because we need to initialize our scrollController.

class DynamicAppbar extends StatefulWidget {
const DynamicAppbar({super.key});
@override
State<DynamicAppbar> createState() => _DynamicAppbarState();
}

class _DynamicAppbarState extends State<DynamicAppbar> {
....

Then define all variables that we need.

class _DynamicAppbarState extends State<DynamicAppbar> {
late ScrollController _scrollController;
bool lastStatus = true;
double height = 390;
...

_scrollController is the controller that we will use later on NestedScrollView.

lastStatus is the condition that we listen for the position of the scroll widget.

and last the height we use for the expanded AppBar. You can define it as you need. In this example, I need about 390 pixels to show all my content in the AppBar when it's expanded.

Now we need to create a condition for whether the AppBar will be collapsed or expanded. lets called it as _isShrink.

 bool get _isShrink {
return _scrollController.hasClients &&
_scrollController.offset > (height - kToolbarHeight);
}

we return value based on scroll offset by the difference between the toolbar and AppBar.

This is the toolbar, or some people name it the status bar.

toolbar

why toolbar?

Because when we display widgets on the screen, the starting point is from the top left of the screen (including the toolbar). That’s why Flutter provides a widget named SafeArea widget. This widget is useful when we want to place a widget that is displayed between the statusbar and bottombar.

Next, create the listener function and set it to the lastStatus value.

void _scrollListener() {
if (_isShrink != lastStatus) {
setState(() {
lastStatus = _isShrink;
});
}
}

and we use the function for the Scrollcontroller listener

@override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_scrollListener);
}

we had initialized the scroll controller. Before we jump to the build method, don't forget to dispose the controller.

Because…. why not 😆 😆. It’s good for memory when removing unused listeners.

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

ok, we had done for the variable and listener. Now we start building widgets.

This is a long code for the whole view. I will point only to the important part. For the rest of the code, you can find it in the repository later that I provided.

  • first, in the body of Scaffold, we use the NestedScrollView widget and implement the _scrollController that we define before. I also use the SafeArea widget 😃
body: SafeArea(
child: NestedScrollView(
controller: _scrollController,
....
  • In SliverAppbar we use _isShrink variable to determine which widget we have to show.
flexibleSpace: FlexibleSpaceBar(
title: _isShrink
? const Text('pmatatias Statistic',
style: TextStyle(fontSize: 16))
: const SizedBox(),

So…
basically, in this state, we show the AppBar which is collapsed when state is shrunk. Otherwise we show SizedBox which means it displays nothing on the screen.

Instead of displaying the title, we display the FlexibleSpaceBar background. This Container contains all widget that shows when the AppBar is expanded.

background: Container(
margin: const EdgeInsets.only(top: kToolbarHeight-16),
padding: const EdgeInsets.only(left: 8),
child: Column(
...
  • And last the body widget. Here I will use ListView.
    By default, it sets the minimum height equal to the screen height. To add some decorations like rounded borders and background color, I wrap the ListView in a Container widget.
 Container(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 10),
decoration: const BoxDecoration(
color: Palette.myrtleGreen,
borderRadius: BorderRadius.only(
topRight: Radius.circular(30), topLeft: Radius.circular(30))),
child: ListView(
.... rest of widget body

Finally, we need to combine all the pieces. It looks like below:

Ps: for some extracted widgets you may found in the repository GitHub here:

ck-linecode/lib/dynamic_appbar at main · pmatatias/ck-linecode · GitHub

🙏Thank you for having the end. Don't forget to clap 👏 if you like this article. 😃

--

--

Matatias Situmorang

Flutter developer | Blogger | Reach me on twitter @pmatatias_