Double Sliver AppBar in Flutter

After releasing my brand new and fully revamped RentMi v3, I got many enquiries from fellow developers on how did I do this fancy double AppBar, which contracts/expands while scrolling.

It’s not hard! But also not really out of the box thing as you’ll need to give it some thought and sweat. But since Christmas 🎄is around the corner, here, take it!

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: DoubleAppBarDemo(),
);
}
}

class DoubleAppBarDemo extends StatelessWidget {
final ScrollController _scrollController = ScrollController();

@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
snap: true,
forceElevated: true,
floating: true,
title: Text("Double AppBar Demo"),
actions: <Widget>[
IconButton(icon: Icon(Icons.apps), onPressed: () {}),
],
expandedHeight: 2 * kToolbarHeight,
flexibleSpace: Padding(
padding: const EdgeInsets.only(top: kToolbarHeight),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 16),
child: RaisedButton(child: Text("Button One"), onPressed: () {}),
),
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 16),
child: RaisedButton(child: Text("Button Two"), onPressed: () {}),
),
],
),
),
),
),
SliverList(
delegate: SliverChildListDelegate([
ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text("$index"),
);
})
]))
],
),
),
);
}
}

Copy paste — it will work out of the box.

Now for fellows who would like to dig deeper — let’s start!

First of all, for whole contraction/expansion mechanics you will need to start dealing with Sliver widgets. They provide you with more custom scroll feeling. However what I noticed myself, more fancy you want to get with your animations, more troubles you might have handling custom logical behaviours.

Current structure is very easy. We have Scaffold, SafeArea (will come to in in a second), CustomScrollView (which helps dealing with Slivers), SliverAppBar (exchange for regular AppBar) and SliverList (replacement of ListView).

You will have to wrap CustomScrollView into SafeArea widget not only due to its ability to provide right paddings (it ensures that on new(?) phone models your app would have enough space in case there is a notch or more free bottom area (iPhone X). Without this wrapping, it might not look good.

CustomScrollView without SafeArea widget

Once we have all set up, we need provide maximum height of SliverAppBar after it’s fully expanded. We can simply put

expandedHeight: 2 * kToolbarHeight

Which will give us double the amount of regular toolbar height.

Then we need to provide Widget to flexibleSpace. It comes to you creativity and UIUX design as it can be any Widget you want. For this Demo, I simply chose a Row with couple of buttons and placed it there.

Padding(
padding: const EdgeInsets.only(top: kToolbarHeight),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 16),
child: RaisedButton(child: Text("Button One"), onPressed: () {}),
),
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 16),
child: RaisedButton(child: Text("Button Two"), onPressed: () {}),
),
],
),
),
),

What you might notice is that this Widget has some padding. Only from the top. It’s simply due to the fact that by default, flexibleSpace Widget is part of overall SliverAppBar widget and gets all of the space. Since we are making illusion as it is only fixed bottom part — we need to add padding to it.

Once we are done with SliverAppBar, last thing left is to display list of items which we can scroll. We simply construct our SliverList with giving it ListView and that’s it!

SliverList(
delegate: SliverChildListDelegate([
ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text("$index"),
);
})
]),
)

..em no, not really. One last thing is that you will need to initialise ScrollController and assign it to the ListView.

final ScrollController _scrollController = ScrollController();

If it’s not there — your list won’t scroll. Why you ask?

I have the same question myself 😅 I guess it’s due to nesting of Lists (ListView inside of CustomListView + Sliver magic).

And now it’s really a finish line! We have simple yet quite powerful scrolling experience.

Hope you enjoyed it and this little tutorial will help you to build great UIUX having Flutter Apps!

I build kick-ass mobile apps @ https://isawthatguy.com || Product Virtuoso and Startup Freak

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store