Sunday, August 19, 2018

Flutter - Collapsing Toolbar OR Sliver App Bar

If you are an Android application developer. Then you definitely know about Collapsing Toolbar. It is a wrapper for Toolbar which implements a collapsing app bar. Flutter team calling it Sliver App barIt displays an image or background in the upper part of the screen, occupying a fixed space, so that later, by scrolling upwards, the content changes and becomes a navigation bar in iOS or toolbar in the case of Android. 

In Flutter, Sliver App bar is designed to be used as a direct child of an App Bar. This type of widget is commonly seen in the User Profile Screen. You can check it with Whatsapp user profile screen.

Let's have look on Sliver App Bar properties in the following diagram that flutter providing us.



  • leading: A widget to display before the title. This is the widget where usually a hamburger icon or back button is displayed.
  • title: Toolbar title goes here wrapped in a Text widget.
  • actions: It is the right side of App Bar. Here, you can declare other option of apps like search, setting and profile etc.
  • bottom: The bottom is usually used for a TabBar below the Appbar.
  • flexibleSpace: This widget is used to create a Collapsing Toolbar effect with Appbar.


You can create simple Appbar having a leading, title and menus like this:
main.dart
import 'package:flutter/material.dart'; void main() => runApp(new MaterialApp( home: new Scaffold( backgroundColor: Colors.yellowAccent, appBar: new AppBar( leading: new Icon(Icons.menu), title: new Text("Developer Libs"), actions: <Widget>[ new IconButton( icon: new Icon(Icons.shopping_cart), onPressed: () {}, ), new IconButton( icon: new Icon(Icons.monetization_on), onPressed: () {}, ) ], ), ), ));
This is the output from the above code. 


Simple App bar flutter

As you can see above, if we need a simple app bar. You can create it just simply add the widgets in the app bar by providing param. But if you want some scrolling effect and other animation on App bar widgets. Then you have to use the Sliver App Bar.

In this post, we going to create a Flutter application that'll show all the use case of Sliver App Bar. In this app, we'll use the sliver app bar to display a background image and a user profile pic widget in the circular shape. The final output of the app looks like below.


Creating a new Project

1. Create a new project from File ⇒ New Flutter Project with your development IDE.
2. Open main.dart file and edit it. As we have set our theme and change debug banner property of Application.
main.dart
import 'package:flutter/material.dart'; import 'package:flutter_collapsing_toolbar/collapsing_profile.dart'; import 'package:flutter_collapsing_toolbar/collapsing_tab.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, theme: new ThemeData( primaryColor: const Color(0xFF02BB9F), primaryColorDark: const Color(0xFF167F67), accentColor: const Color(0xFF167F67), ), initialRoute: '/', routes: { // When we navigate to the "/" route, build the FirstScreen Widget '/': (context) => CollapsingTab(), // When we navigate to the "/profile" route, build the SecondScreen Widget '/profile': (context) => CollapsingProfile(), }, ); } }             
as you can see above,  we have used routes parameter for declaring all screen of the app. We'll use the first screen for just scaling user profile icon on the same place and add some tabs. In the second screen, we'll scale and translate user profile icon and use list view. 

3. Now create a state full widget that is the first screen of the app and give name collapsing_tab.dart. Here, starting edit build method. Use body param and add DefaultTabController as a root of screen widget. It will control the tab bar movement after sliver app bar change. As you can see below, we have created a flexible space bar widget.

collapsing_tab.dart
var flexibleSpaceWidget = new SliverAppBar( expandedHeight: 200.0, pinned: true, flexibleSpace: FlexibleSpaceBar( centerTitle: true, title: Text("Developer Libs", style: TextStyle( color: Colors.white, fontSize: 16.0, )), background: Image.asset( "assets/logo.png", )), actions: <Widget>[ new Padding( padding: EdgeInsets.all(5.0), child: _buildActions(), ), ], );
in the above code snippet, we have created another method _buildAction(). It'll show user profile pic in the circular shape in the right side fo App Bar.
collapsing_tab.dart
Widget _buildActions() { Widget profile = new GestureDetector( onTap: () => showProfile(), child: new Container( height: 30.0, width: 45.0, decoration: new BoxDecoration( shape: BoxShape.circle, color: Colors.grey, image: new DecorationImage( image: new ExactAssetImage("assets/logo.png"), fit: BoxFit.cover, ), border: Border.all(color: Colors.black, width: 2.0), ), ), ); double scale; if (scrollController.hasClients) { scale = scrollController.offset / 300; scale = scale * 2; if (scale > 1) { scale = 1.0; } } else { scale = 0.0; } return new Transform( transform: new Matrix4.identity()..scale(scale, scale), alignment: Alignment.center, child: profile, ); }
in this method, we calculate a scaling state of user profile pic icon and create a new widget. The output of the above code snippet looks like below.


After scroll upward, user profile pic will be visible




4. After that let's add tab bar in the bottom of the sliver app bar. For add tab bar,  we have used delegate param of sliver header. To manage tab content, we have created another class _SliverAppBarDelegate. It will create tab bar scroll animation proper way as we want. In the following code snippet, you can see complete collapsing_tab.dart that we use in the app. If you facing any issue to implement tab bar. You can read it here. We have discussed it in detail.

collapsing_tab.dart
import 'package:flutter/material.dart'; import 'package:flutter_collapsing_toolbar/tab_screen.dart'; class CollapsingTab extends StatefulWidget { @override _CollapsingTabState createState() => new _CollapsingTabState(); } class _CollapsingTabState extends State<CollapsingTab> { ScrollController scrollController; Widget _buildActions() { Widget profile = new GestureDetector( onTap: () => showProfile(), child: new Container( height: 30.0, width: 45.0, decoration: new BoxDecoration( shape: BoxShape.circle, color: Colors.grey, image: new DecorationImage( image: new ExactAssetImage("assets/logo.png"), fit: BoxFit.cover, ), border: Border.all(color: Colors.black, width: 2.0), ), ), ); double scale; if (scrollController.hasClients) { scale = scrollController.offset / 300; scale = scale * 2; if (scale > 1) { scale = 1.0; } } else { scale = 0.0; } return new Transform( transform: new Matrix4.identity()..scale(scale, scale), alignment: Alignment.center, child: profile, ); } @override void dispose() { scrollController.dispose(); super.dispose(); } @override void initState() { super.initState(); scrollController = new ScrollController(); scrollController.addListener(() => setState(() {})); } @override Widget build(BuildContext context) { var flexibleSpaceWidget = new SliverAppBar( expandedHeight: 200.0, pinned: true, flexibleSpace: FlexibleSpaceBar( centerTitle: true, title: Text("Developer Libs", style: TextStyle( color: Colors.white, fontSize: 16.0, )), background: Image.asset( "assets/logo.png", )), actions: <Widget>[ new Padding( padding: EdgeInsets.all(5.0), child: _buildActions(), ), ], ); return Scaffold( body: new DefaultTabController( length: 3, child: NestedScrollView( controller: scrollController, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return <Widget>[ flexibleSpaceWidget, SliverPersistentHeader( delegate: _SliverAppBarDelegate( TabBar( labelColor: Colors.black87, unselectedLabelColor: Colors.black26, tabs: [ Tab( icon: Icon(Icons.account_box), text: "Detail", ), Tab(icon: Icon(Icons.add_location), text: "Address"), Tab(icon: Icon(Icons.monetization_on), text: "Earning"), ], ), ), pinned: true, ), ]; }, body: new TabBarView( children: <Widget>[ new TabScreen("Detail"), new TabScreen("Address"), new TabScreen("Earning"), ], ), ), ), ); } showProfile() { Navigator.pushNamed(context, '/profile'); } } class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { final TabBar _tabBar; _SliverAppBarDelegate(this._tabBar); @override double get minExtent => _tabBar.preferredSize.height; @override double get maxExtent => _tabBar.preferredSize.height; @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { return new Container( child: _tabBar, ); } @override bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { return false; } }
the output of the first screen.


            After scroll upward         



5. Now, it time to create the second screen of app that'll show another use case of sliver app bar. So create collapsing_profile.dart state full widget. It will open when you will tab user profile icon on that we have added in the first screen. For manage translate animation of the user profile pic, we have created a utility class sliver_fab.dart.

sliver_fab.dart
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class SliverContainer extends StatefulWidget { final List<Widget> slivers; final Widget floatingActionButton; final double expandedHeight; final double marginRight; final double topScalingEdge; SliverContainer( {@required this.slivers, @required this.floatingActionButton, this.expandedHeight = 256.0, this.marginRight = 16.0, this.topScalingEdge = 96.0}); @override State<StatefulWidget> createState() { return new SliverFabState(); } } class SliverFabState extends State<SliverContainer> { ScrollController scrollController; @override void initState() { super.initState(); scrollController = new ScrollController(); scrollController.addListener(() => setState(() {})); } @override void dispose() { scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return new Stack( children: <Widget>[ new CustomScrollView( controller: scrollController, slivers: widget.slivers, ), _buildFab(), ], ); } Widget _buildFab() { final topMarginAdjustVal = Theme.of(context).platform == TargetPlatform.iOS ? 12.0 : -4.0; final double defaultTopMargin = widget.expandedHeight + topMarginAdjustVal; double top = defaultTopMargin; double scale = 1.0; if (scrollController.hasClients) { double offset = scrollController.offset; top -= offset > 0 ? offset : 0; if (offset < defaultTopMargin - widget.topScalingEdge) { scale = 1.0; } else if (offset < defaultTopMargin - widget.topScalingEdge / 2) { scale = (defaultTopMargin - widget.topScalingEdge / 2 - offset) / (widget.topScalingEdge / 2); } else { scale = 0.0; } } return new Positioned( top: top, right: widget.marginRight, child: new Transform( transform: new Matrix4.identity()..scale(scale, scale), alignment: Alignment.center, child: widget.floatingActionButton, ), ); } }
as you can see above, in this class we calculating a widget position after scrolling the sliver app bar.

6. After that, open collapsing_profile.dart file and start editing build method as below we did. In this method, we using floatingActionButton for display user profile pic in the circular shape. We have set height 256.0 of the flexible space and in the slivers param.  We have added a background image and SliverList widgets. Here, you can see a complete file of second screen. 
collapsing_profile.dart
import 'package:flutter/material.dart'; import 'package:flutter_collapsing_toolbar/sliver_fab.dart'; class CollapsingProfile extends StatefulWidget { CollapsingProfile({Key key, this.title}) : super(key: key); final String title; @override _CollapsingProfileState createState() => new _CollapsingProfileState(); } class _CollapsingProfileState extends State<CollapsingProfile> { @override Widget build(BuildContext context) { return new Scaffold( body: new Builder( builder: (context) => new SliverContainer( floatingActionButton: new Container( height: 60.0, width: 60.0, decoration: new BoxDecoration( shape: BoxShape.circle, color: Colors.grey, image: new DecorationImage( image: new ExactAssetImage("assets/logo.png"), fit: BoxFit.cover, ), border: Border.all(color: Colors.black, width: 2.0), ), ), expandedHeight: 256.0, slivers: <Widget>[ new SliverAppBar( iconTheme: IconThemeData(color: Colors.white), expandedHeight: 256.0, pinned: true, flexibleSpace: new FlexibleSpaceBar( title: new Text("Developer Libs", style: TextStyle(color: Colors.white), ), background: new Image.asset( "assets/logo.png", ), ), ), new SliverList( delegate: new SliverChildListDelegate( new List.generate( 30, (int index) => new ListTile(title: new Text("Item $index")), ), ), ), ], ), ), ); } }

So, that's all about Sliver App Bar. Run app and see the magic,


If you have followed the article carefully, you can see the app running very smoothly as shown in the above video demo. But if you are facing any problem. 



Source Code               Flutter firebse storage apk


You can get the working project from Github and please feel free to ask in the comment section below.
Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...