Leave a rating/review
This video Add Navigation & SliverAppBar was last updated on Nov 9 2021
In previous episodes, we used a FloatingActionButton
to add an onPressed
interaction. Flutter provides us with some widgets that let’s us build interactivity into any widget. We would be using a GestureDetector
widget to do this. This widget detects different types of gestures from a user like a tap, drag, long press and many others.
Open up home_page.dart
file. Scroll down to the ListView
. And wrap the ArticleCard
with this widget and update it like so:
...
final article = snapshot.data![index]; // Dont ignore this!!!
return GestureDetector(
onTap: () {
Navigator.push<void>(
context,
MaterialPageRoute(
builder: (context) => ArticlePage(article: article)),
);
},
child: ArticleCard(article: article), // add this Update too!!!
);
...
First, we create an article variable to hold the snapshot data for each index of the listview item. We do this since we’ll be using the snapshot data in multiple places. Then, we wrapped the ArticleCard
with a GestureDetector
and implemented its onTap
callback function. This function would be called whenever this widget is tapped.
Inside this function, we add the code for navigating to a different page. In Flutter, app screens or pages are referred to as routes. Routes are arranged and managed like Stacks. The active route is placed on top of the stack and that would be the current page.
These routes are managed by the Navigator
widget. In here, we use its push
method to push the ArticlePage
to the top of the stack. In other words, it navigates to the ArticlePage
.
Note: The ArticlePage
is constructed with the use of the MaterialPageRoute
widget which serves this widget as a route.
The Navigator
widget used here is the provided by the MaterialApp
. The ArticlePage
widget has already been added to the stater project of this episode. Go ahead and open it. This widget returns a Scaffold
with an AppBar
and a centered text.
We would update this widget soon. Save your work and let’s try it out.
It navigates to the ArticlePage
as expected. Notice that the AppBar
automatically adds a back button for us. So we can click this to pop this route off the stack. Let navigate to the page once more.
Next, let’s create the contents of the ArticlePage
. Open up the article_page.dart file. Update the body of the Scaffold with the following code:
ListView(
children: <Widget>[
ArticleMeta(article: article),
ArticleContent(article: article),
ArticleTags(),
Container(
margin: const EdgeInsets.symmetric(vertical: 16),
height: 16,
color: Colors.grey[300],
)
]
),
We use a ListView
to wrap the contents of the page to make it scrollable. Its children are a list of custom widgets we’re going to create soon. The last child is a Container
which would act as a separator. Let’s create the ArticleMeta
widget.
First, comment out other widgets in the ListView then paste the following code:
class ArticleMeta extends StatelessWidget {
final Article? article;
const ArticleMeta({
Key? key,
required this.article,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[
Text(
'${article!.title}',
style: Theme.of(context).textTheme.headline4!.copyWith(
fontSize: 30,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
ListTile(
contentPadding: const EdgeInsets.all(0),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
title: Text(article!.author),
subtitle: Text(article!.publishedAt),
trailing: ElevatedButton(
onPressed: () {},
child: const Text(
'Subscribe',
style: TextStyle(color: Colors.white),
),
style: ElevatedButton.styleFrom(
primary: Theme.of(context).primaryColor,
),
),
),
const SizedBox(height: 16),
],
),
);
}
}
Save your work. This is a StatelessWidget
which is used to display information about the article like its title, author information and publish date. It returns a Padding
which is used to add some innner spacing. Its child is a Column
that has a Text
, SizedBox
and a ListTile
as its children.
The title text uses the textTheme
of headline4 and customizes it to a different font size and weight. The fontWeight
property is used to adjust the boldness of the text. Note: The textTheme
has different styles that corresponds to material design guidelines and headline4 is just one of them.
Next, we use a SizedBox
to add some spacing between the Text
and the ListTile
widget. And you can se the spacing between them. A ListTile
is a predefined styled widget provided by Flutter that is used for List related interfaces. For example, you could use this for a chat item in a chat list. Here, we use it to display the author name and other related information.
We set its contentPadding
to zero since one of its ancestor already has a padding. We fill up its leading, title, subtitle and trailing properties with widgets and the ListTile widget arranges them accordingly.
And the grey bar below is the Container
we added as a separator at the end of all widgets in the ArticlePage
. Next, let’s create the ArticleContent
widget which houses the main content. Uncomment the ArticleContent
line and paste the following code:
class ArticleContent extends StatelessWidget {
const ArticleContent({
Key key,
@required this.article,
}) : super(key: key);
final Article article;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(
children: <Widget>[
Text(
'\"${article.description}\"',
style: Theme.of(context).textTheme.subtitle.copyWith(
fontStyle: FontStyle.italic,
color: Colors.grey[600],
fontSize: 20,
),
),
SizedBox(height: 16),
Text(
article.content,
style: TextStyle(fontSize: 18, height: 1.5),
),
SizedBox(height: 16),
],
),
);
}
}
Save your work. You should be familiar with ths code by now but i’ll mention some new additions. For the padding, we previously used the EdgeInsets.all()
constructor which adds the padding to all sides of the box.
Now, we use EdgeInsets.fromLTRB()
constructor which adds padding from the Left, Top, Right and Bottom. So LTRB corresponds to the argument positions. So we give a padding of 16 on the left, 0 at the top, 16 on both the right and bottom sides of the box. For the content text, we give it a line height of 1.5 which increases the space between each line of text.
Next, let’s add the code for the article tags. Scroll to the top and uncomment the ArticleTags
in the ListView
. Then paste in the following code at the end of the file:
class ArticleTags extends StatelessWidget {
const ArticleTags({ Key? key }) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Wrap(
spacing: 8,
children: <Widget>[
const Chip(label: Text('Science')),
const Chip(label: Text('Technology')),
const Chip(label: Text('Devices')),
],
),
);
}
}
Save your work. This widget displays a list of Chip
s side by side. The Chip
s are wrapped with a Wrap
widget…no pun intended. The Wrap
widget is used when you want to display a list of widget on one axis but need them to move to the next line if they exceed their bounding box. This prevents the overflow warning sign.
Let’s duplicate some of the Chips to show you how it would look like. You can see they wrap to the next line. If you used something like a row, you’ll get the overflow warning sign at the end of the box. So, I’ll undo that…and save our work. Inside the Wrap
widget, we added a spacing of 8 between each Chip
widget.
For the padding
, EdgeInsets.symmetric()
is used here to specify the padding on an axis. Here we specified a horizontal
padding of 16 which add the padding to the left and right sides.
Finally, we need to show the article’s image. We want to display this at the top of the page and shrink the image as we scroll down the page. The ListView
is not built for this type of effect. Even if we were to create this with a ListView
, we would need to incorporate other widgets and write many custom codes.
Flutter provides us with the CustomScrollView
widget which is used to create custom scroll effects using slivers.
A Sliver is just a portion of a scrollable area. In this case, the slivers are areas inside a CustomScrollView
.
Update your code to use a CustomScrollView
like so:
...
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 300,
pinned: true,
floating: true,
elevation: 50,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
'${article.urlToImage}',
fit: BoxFit.cover,
),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
ArticleMeta(article: article),
ArticleContent(article: article),
...
]
),
)
]
),
Save your work. Let’s scroll through the page to see what happens. You can see the appbar expands and shrinks as we scroll the view. Let’s go to another article. Cool, it grows and shrinks as expected.
Here, the CustomScrollView
takes in a list of slivers. The first child is a SliverAppBar
while the second child is a SliverList
. The SliverList
is just the sliver version of a normal ListView
. We use it here because a CustomScrollView
can only accept slivers as its children. And if you notice, we removed the appbar from the scaffold because we’ll be using the SliverAppBar
. The SliverAppBar
is where is magic happpens.
We set the expandedHeight
to 300 which gives the app bar a height of 300. The pinned
property is set to true which makes prevents the appbar from scolling out of view. floating
is also set to true to make the appbar visible as soon as the user scrolls towards the appbar. You’ll need to play around with these properties to see the different effects they create.
Next, we give it an elevation of 50 to add some shadows below the appbar. Finally, we add a FlexibleSpaceBar
and set its background to the article’s image. The flexibleSpace is the portion behind the appbar’s content and it takes up the same height of the appbar. CustomScrollView
s are used to add custom scroll effect just like what we just created. (Scroll the view once again)