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.