Reappearing App Bar Titles
Flutter’s SliverAppBar
makes collapsing headers easy to create. Here is a basic example with a header expanded to 500 points. Instead of displaying a purple color, the FlexibleSpaceBar
could of course feature some beautiful imagery. When scrolling, the header shrinks to become the standard AppBar
.
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(),
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
primary: true,
pinned: true,
expandedHeight: 500,
flexibleSpace: FlexibleSpaceBar(
background: Container(
color: Colors.purple,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text("List tile $index"),
);
},
childCount: 30,
),
),
],
),
);
}
}
The Problem We Want to Solve
Let’s assume we don’t want to display the title if the header is expanded because that would disturb our beautiful imagery. But if collapsed, the app bar should display it as it would normally. To my knowledge, Flutter has no such behavior by default.
We can either add a title
to the FlexibleSpaceBar
which is then displayed at the bottom of the expanded header and animated to the usual position or to the SliverAppBar
which then is displayed without animation.
Analyzing The Status Quo
I browsed the Flutter source code for inspiration how to create a solution that doesn’t require replacing or modifying large parts of the SliverAppBar
source code.
Fortunately, each widget inside a Scrollable
widget can get access to that widget’s scroll position and if adding a listener to that position, it gets notified each time when the user scrolls the Scrollable
widget which is a CustomScrollView
in our case.
Also, a SliverAppBar
creates a FlexibleSpaceBarSettings
contextual widget for its FlexibleSpaceBar
to internally get access to the minimum, maximum, and current height. We can use those values to determine whether the app bar is collapsed or not.
Developing The Solution
I wrap the normal title
widget with another widget that only displays the title if it detects a collapsed app bar and hides the title otherwise. I call this wrapper SABT
which is short for sliver app bar title (I know, an awful name).
Because we want to track scrolling using a listener on the scrollable’s position, the SABT
needs to be a stateful widget.
There’s one problem, though. We cannot add the listener in the usual initState
method because we cannot access the context
at this point of time. It looks like didChangeDependencies
is the right method to override. As this method is also called if the widget tree is reconfigured, we need to first remove the listener as shown in the code below. Otherwise we might end up with multiple listeners.
Here is the complete source code:
import 'package:flutter/material.dart';class SABT extends StatefulWidget {
final Widget child; const SABT({
Key key,
@required this.child,
}) : super(key: key); @override
_SABTState createState() {
return new _SABTState();
}
}class _SABTState extends State<SABT> {
ScrollPosition _position;
bool _visible; @override
void dispose() {
_removeListener();
super.dispose();
} @override
void didChangeDependencies() {
super.didChangeDependencies();
_removeListener();
_addListener();
} void _addListener() {
_position = Scrollable.of(context)?.position;
_position?.addListener(_positionListener);
_positionListener();
} void _removeListener() {
_position?.removeListener(_positionListener);
} void _positionListener() {
final FlexibleSpaceBarSettings settings =
context.inheritFromWidgetOfExactType(FlexibleSpaceBarSettings);
bool visible = settings == null || settings.currentExtent <= settings.minExtent;
if (_visible != visible) {
setState(() {
_visible = visible;
});
}
} @override
Widget build(BuildContext context) {
return Visibility(
visible: _visible,
child: widget.child,
);
}
}
To integrate the SABT
with our example, we simple add any title widget wrapped in the SABT
widget like so:
SliverAppBar(
primary: true,
pinned: true,
expandedHeight: 500,
title: SABT(child: Text("The title")),
flexibleSpace: FlexibleSpaceBar(
background: Container(
color: Colors.purple,
),
),
),
If you like, feel free to add some kind of animation to the appearance and/or disappearance of the title by replacing the Visibility
widget with for example an AnimatedOpacity
widget.