[MUSIC PLAYING] KATE LOVETT: Hi, there. Welcome. My name is Kate Lovett. And I'm a software engineer
on the Flutter team at Google. Today, we're going to learn
about scrolling UIs in Flutter. We'll be taking a
simple scrolling UI and enhancing it to be a
rich, interactive experience for our users. We'll even be diving
into slivers, which may seem daunting at first. But stay with me
and you will see that not only are slivers quite
nice once you get to know them, they are powerful
tools when applied to create custom scrolling
UIs. If you have never written a Flutter app before, you
may like to check out Writing Your First Flutter App before
taking this workshop I'll be right here waiting for
you when you come back. For those of you joining
us on the live stream, remember to post your
questions in the Q&A section. We have Flutter experts standing
by to answer your questions. Or they may send them
my way, so that we can explore them together. So when you are ready
for our journey today, we'll be using DartPad. DartPad is an open
source tool you can use to write Flutter
applications without ever needing to download and
install the Flutter SDK. It's a fantastic way to get
started writing Dart code and experimenting with Flutter
without any of the setup. We'll be using a special version
of DartPad today as well-- one that will take us through
this workshop one at a time. So when you're ready, join me at
Flutter.dev/go/sliver-workshop. That's
Flutter.dev/go/sliver-workshop. Are you here with me? Excellent. You should see a UI just like
I have here on my screen. On the left, we have
our instructions, which will guide us through
each step of the workshop. I won't be reading directly
from this pane the whole time. But I'll be staying on
track with it as we go. On the right, we
have two panes-- one with our code and the
other with our output. It is here, when we
press the Run button, that we will see
our UI rendered. This is because Flutter doesn't
just run on a mobile device. And it doesn't run on just
a desktop machine, either. Flutter also runs on the web. So we can check out
the result of our code here, just as we
would on a device. A few more points of
interest in our workspace that I like to call out-- in your top left corner,
you'll see a Format button. That's handy for quickly tidying
up our workspace as we go. You could also use the keyboard
shortcut, Control+Shift+F. For more keyboard shortcuts,
click the Keyboard icon in the bottom left
corner of your screen. So let me just run
our starter code here. And let's first
take a look and see what we're starting out with. All right. Here, we have a simple
weather application with the forecast for
the next seven days. And it is scrollable, for
some of our smaller screens. Our primary scrollable here
is a single child scroll view that contains a column
with all seven of our forecasts. Anything that
scrolls in Flutter, like a single child scroll
view, is really a sliver. If you have used a list
view or a grid view in your applications,
you have already worked with some slivers. These classes give us convenient
ways to compose slivers, rather than working
with them directly. And we'll dig into
why they're different later on in this workshop. We also have a custom
scroll behavior in our app. Scroll behaviors dictate how
scrolling widgets look and feel in a Flutter app. By default, they adapt
to the current platform, behaving a little differently
depending on the machine or device you are using. This is so that every
Flutter scrolling experience matches the native behavior your
users expect on their devices. For the purposes
of this workshop, we have a constant
scroll behavior. So every one of us is having
the same experience, regardless of the machine we
are using while we're learning about the
intricacies of slivers. Scroll behaviors are inherited. So if you'd like to learn more,
check out our Inherited Widgets Workshop. OK. So where do we even start? Well, first, we will
be looking at making our scrollable more efficient,
before we add to our UI. Later, we'll dive into
working directly with slivers. So let's get going
and move on to step 2. OK. As we progress
through this Codelab, keep an eye out for the to-dos. They will help us focus in on
our area of work for the step. If you click on them here
in the Analyzer pane, it will take you straight there. So the first thing that
we would like to do here is we'd like to make
our single child scroll view more efficient. But let's first talk about
why this sometimes may not be the most efficient choice. Currently, our UI
is pretty simple. The scrolling
seven-day forecast is likely to fit on most screens. And the single child scroll
view gives us an assurance that, on some of the
smallest screens, we won't run out of room. What I mean by that is, if I
remove the single child scroll view, we might end up
overflowing our column like this. So I'm going to hide
the Analyzer pane. And I'm going to make use of
one of our handy shortcuts, Alt+Enter, or on my
keyboard Option+Enter. This gives us a quick picker
to make some modifications to our widgets. And I'm going to remove the
single child scroll view. So now, we just have a column
with our seven forecasts. And if I run this, we will
see that we get a nasty render overflow error. Why is that, though? This is where we can see how
the layout protocol of slivers differs from their relative,
the box layout protocol. A column is laid out
using box constraints. It has a height, and a
width, and a position within the window. A column cannot lay out beyond
the bounds of the window. When we wrap our column in
a single child scroll view, we are essentially wrapping
our column in a sliver. Slivers lay out using
sliver constraints and use a sliver geometry. When working with
slivers, the window pane we were constricted
to previously becomes an infinite amount of
space in the given axis. So we use language different
from height, width, and position. As a sliver, we need to know
things like how much of it is visible, and how
far to the next sliver, and how far have
we scrolled anyway. The answers to these
questions allow us to lazily load slivers,
meaning we only build the slivers we can
see, and a little bit of the ones on either edge. This makes our scrolling
more efficient, as we won't build any
slivers we don't need to, since we can't
actually see them. Since the single child scroll
view is only one sliver, we aren't lazily loading our UI. Instead, as our column gets
bigger and more complicated, all of its contents
are being built. So before we add
more to our UI, let's make this more efficient by
using a list view instead. So I'm going to go ahead
and close our Output pane. And in our instructions,
this is our aim. We're going to replace our
column instead with a list view using the Builder Constructor. This will allow us to
build our cards on demand. So if I change
this to a builder, I no longer want to
provide all of my children. I want to build them
using an item builder. I'm going to go ahead and
get rid of this Map function and just change these parameters
to what an item builder would provide us. That would involve a
build context, as well as the current index that we
are building for, all right? Down here, I no longer to need
to cast all of my forecasts into a list or a column. So we can just clean
that up and remove this. And lastly, before, I was
getting all of my forecasts all at once. But now, I'm going to go
ahead and get them on demand. So I'm going to go
ahead and remove the forecasts and
our little to-do, since we are resolving it here. And inside our
Builder Function, I'm going to go ahead and retrieve
our daily forecast on demand, just like we're building
our cards on demand. I'm going to do that by
accessing our mock server and getting it by ID
[INAUDIBLE] index. OK, let's see how this
looks when we run it. Well, instead of
our render overflow, we're now back to
the way that we were. Oh, it looks like I may
have forgotten something. I actually have an
assertion in my server code to make sure that I am not
building beyond my seven forecasts. And it looks like I forgot
to include my item count. There are only seven days in
my forecast in the mock data that we're using. So I want to make sure
that I include that. So let's try that again
OK, we are back to looking the way we were before. But we've made a
performance change. We are now only building
our cards on demand. So Monday, Tuesday,
Wednesday, those are only being built as we
need them, as we're scrolling. So now that we're
being efficient, let's go ahead
and add to our UI. Our daily forecast object
comes with an image. I think it would be great to add
some context to our forecasts. So looking at our
cards, we've been composing all of our information
about our daily forecast using a list tile. This is a really handy widget
that handles a lot of layout, and padding, and
alignment for you. But looking at what we have, I
think that we can handle this. So let's break this up
into some rows and columns, and then find a way to
integrate our image. What I'm going to
do is I'm going to break this up into a row
with our title and subtitle. Since they're stacked
on top of one another, put them in a column. So let me close this
so we have a little bit more space to work with. I'm going to change our
list style to a row, which takes the list of children. And I'm going to copy-paste
right out of our list tile parameters. The Analyzer's going to be
unhappy with me for a moment. So do bear with me. I'm going to move the leading up
here, and then take that away. I'm going to go ahead and move
the trailing just after it. And let me see if
I can resize this. And then in between, I'm going
to place a column for our title and subtitle. Now, a column, just like a
row, takes a list of children. And we're just going to go ahead
and plug our title and subtitle from our former list
tile right back in there. Take away parameters. It looks like we need a little
bit of formatting help here. Easily solved. All right, so what
we've done is we've taken away our list tiles. We've added a row with
a column and pretty much aligned everything just the way
it was in our list tile, right? Let's take a look and
see how it turned out. Well, that is pretty
close to the way the list tile put things together. We are missing some white space. And as many of you
know, I'm sure padding is the secret cherry
on top of most UIs. So let's just spruce
this up a little bit with some white space. I'm going to go
ahead and close this. And around our column, I'm
going to put an expanded widget. This will force our
column to take up the majority of the space in
the row that it possibly can. So we'll get a similar effect
to the tile that we had. Using our Option+Enter
or Alt+Enter shortcut, I'm going to go ahead and wrap
with a widget and make this one expanded. I'm also going to
add some padding. So we'll add some padding
around our tile here. And we might as well add
it around our temperature as well, just for good
measure, so everything has a nice bit of leeway around it. And there's one more
thing that I forgot. In our column, we
want to make sure that everything has that left
justify that we originally had with the list tile. So I'm going to add a
cross-axi alignment. And that'll be Start. OK, let's see if
we're any closer, so we can start plugging
in our pictures. Looking much better. Wow. And it really didn't take
too much for us to get there. So I'd like to put an
image behind our date. I think that would create a
really nice effect to our UI. So the first thing
that I'm going to do is I am going to go up
here to where our date is. And I'm going to wrap
it in a sized box. Just like so. I'm going to give it a
height and a width of 200. I think that should
be big enough. And then in order to place
the image underneath my text, I'm going to use a stack. Now, in our shortcuts
here, I'm going to choose a column,
just because that'll make it a little easier
to turn this into a stack because a stack takes
a list of children. And from the
beginning to the end, it stacks them one
on top of each other. So before my text,
I'll put an image. Our image is stored in our
daily forecast at imageID. And then I'm also
going to make sure that I set a fit so that
the image can take advantage of the entire space that I've
given it with that sized box and do the same for the stack. OK. Well, we have a sized box-- wait, comma there. We have a sized
box with a stack, with a text on top of an image. Survey says, let's run this. All right, this is actually
looking pretty interesting now. There are a couple adjustments
that we can certainly make for the contrast. Monday looks a little scary. We'll have to pack an umbrella. So we definitely want to make
sure that we can see this. So let's add a little
bit more contrast by adding a decoration. Ultimately, I know
that I can add as much as I like now,
because we're being really efficient with our scrolling. So in order to make
this a little bit better before we move on, let's make
our text a little bit bigger. And let's add a decorated
box around our image. OK. So we are going to
add a decorated box. And for our
decoration, I'm going to choose a box decoration. And I think it
would be pretty neat if we could put in a gradient. That way, we could see the
text and still see the image. So let's add a radial gradient. Just take the list of colors
that it will transition over. And so I'm going
to choose just two. We're going to do
a dark gray, so that we can get that contrast
that we're looking for, and a transparent color so that
we can still see the image. All right. I think I'm missing
one more thing, though. Since I want this decoration
to be on top of my image, I have to make sure that
I provide a position, as long as I can spell it. That will be a declaration
position of foreground, so that it'll be
on top of my image. Let's see if we can read our
text a little bit better Oh, I forgot one more
thing, didn't I? We have our radial
gradient, as you can see. But our text is still
up in the corner. The last thing I wanted to do
was I wanted to add a center and place it right on top. Awesome. We have better contrast now. We have images. It's starting to
look pretty nice, although our app bar does seem a
bit lacking now, in my opinion. It's not nearly as
exciting as the rest of the work we've been doing. Let's move on to the
next step and see what we can do about that. OK, I'd like to use a
more dynamic app bar. And we do have the Sliver
App Bar available to us. But first, we're
going to need to dive into working with
slivers directly and set a few things up first. Let's see what our
to-dos have for us. First, we want to add
a custom scroll view. Slivers are contained within a
scroll view, like a container. And the list view is already
setting some of this up for us. So the first thing we need to
do is create our own container. So up here in the
body of our scaffold, I'm going to add a
custom scroll view. A custom worldview doesn't take
a child or a list of children. Instead, it takes
a list of slivers. Now, our weekly forecast
list is not a sliver yet. But we're going to
handle that next. OK. So there we are. We now have a custom scroll
view with a soon to be sliver weekly forecast list. So let's go on down here and
talk about converting this to a sliver list. A sliver list, just
like our list view, will lazily load our UI,
just like we intended to when we transferred from
a single child scroll view to a list view with the
Builder Constructor. Now, a sliver list, however,
instead of an item builder, needs to be provided a
sliver child delegate. This delegate is
responsible for providing us the children on demand so that
the sliver list can lay them all out. And one type of
sliver child delegate is a sliver child
builder delegate, which expects a build
context and an index. And look, we are
already ready for this. We already have
much of this set up. So the first thing
that I'm going to do to make this transition
as easy as possible is I'm just going to wrap
the list view in a widget. And I am going to make
this our sliver list. Now, instead of a child, we are
going to provide our delegate. And instead of-- let me
hide that Analyzer pane. Instead of a list
view, we are going to provide our sliver child
builder delegate, which does not take an item count. We'll get to that in a sec. And instead of an item builder,
it takes a positional builder function. Look at that. The Analyzer doesn't seem
to be disappointed in us. We just have to still
account for that item count, since I have those assertions
in place in the server. And in order to find where
to put this item count, I'm going to show
you a fun trick. Since our builder function is
a required positional argument, we have to go ahead
and place our child count, which is a named
optional parameter, afterwards. So if I put my
cursor right here, I can see that the curly
brace is illuminated. And I can easily find where
to pick up and continue adding my parameters. I'm going to provide
a child count of 7. All right, let us run
this and take a look. It's the same again. Again, we did not expect
a visual difference here. Instead, we had changed
to using different tools and different widgets
to get the same result. And now that we are working
directly with slivers, we can explore doing something
like the Sliver App Bar to have a much more
dynamic experience here at the top of our scroll view. So now that we are working
directly with slivers-- hooray and
congratulations to us-- let us move on to step 5,
where we can start playing with the Sliver App Bar. OK. So I'm going to go
ahead and close this. We want our Sliver App
Bar to be at the top, just like we expect for
any other normal app bar. So right here in our
custom scroll view, above our weekly forecast list,
is where I'm going to add it. Now, the Sliver App Bar shares
a lot in common with the app bar that you may already
know and love, including having a title
and a background color. But let's just go ahead
and move those over. That was a teal. Awesome. OK. So we now have a Sliver App Bar. What does it actually look like? Oh, it looks a lot
like our app bar. This, at the top,
being the bar that we have added right
here in our scaffold, and our second one being
our Sliver App Bar. But the big difference
here is when I scroll, the Sliver App Bar scrolls
away with the rest of my list. This is just one behavior
of the Sliver App Bar. And there are many that
we are about to explore. And so let's remove the app
bar that we started with. Thank you very much. And let's start to explore
the different behaviors of the Sliver App Bar. So I'm going to list
them all out first. And then we'll explore
them one by one. First, Sliver App
Bars can be pinned. They can float in
and out of view. It can also complete
a snap animation. And they can have
an expanded height. Let's just say 200. Let's look at these
one at a time now. All right, we've
pinned Sliver App Bar. This looks an awful lot like
the bar that we started with. It stays at the top and lets
the other rest of the content scroll underneath it, just like
we would see in our other bar. OK, let's try floating. In this case, we'll
see it scroll away. But at any point in the
list, if I scroll back, it presents itself to me. This is really great
for your users, if you want to present actions
or shortcuts in the app bar without them going all the way
back up to the top of the list. Floating app bars
can also snap, which completes an animation
that will open and close the bar as you scroll. So here we are again. It scrolls away. And this time,
when I scroll back, it will present
itself into full view without me scrolling
it all the way back. Cool. Last on our list
is expanded height. And this makes
for a big app bar. OK. So we're still floating. We're still snapping. And now, we have a lot of extra
real estate in our Sliver App Bar. As I scroll away
though now, you'll see that this extra
space collapses before it scrolls away. And since we're snapping,
when I bring it back, it will all snap back into view. Just another really cool effect. There are so many
different combinations that you can make with
the Sliver App Bar. These are really just a few. If I go ahead and look at it
just as a pinned app bar pinned with expanded
height, I'm not quite sure what will happen then. Do you? Before, when we were pinned,
the app bar didn't go anywhere. But since we have
this expanded height, it becomes a collapsible
space that then stays pinned. I like this going forward. So I'm going to
keep it this way. But feel free to
explore on your own and see which way you like best. Let's move on to step 6. Now that we have all
this expanded height, I'd really like to
take advantage of it. All right, the best
way to take advantage of all this space in
our Sliver App Bar now is using the
flexible space bar. This is a widget
that is designed to stretch and collapse its
content as its container changes, which sounds just about
perfect for our use case here. So I'm going to go ahead and
add it to our Sliver App Bar. We can do this using
flexible space. All right. We can move our title down here. And I'm going to keep
our original title there because I'll show you why. [INAUDIBLE] And I also have
a background image available. So let's add that there as well. I have this saved
under header image. There we go. And again, I'm going
to set a box for it so that we take advantage
of all this space that we have available
with boxfit.cover. All right, I think that
checks all the boxes. Let's see what happens now
when we use a flexible space. OK, we have a lovely image. We have some
contrast issues we're going to have to address again. But as you can see,
we have the title at the top is the original
title of the Sliver App Bar. And I left it there
so I can show you the cool effect that happens
as we collapse this now. Our flexible space
title will shrink to meet the other one as we go. And you'll notice that we
have faded back into teal. And the image is doing this
really neat parallax effect as we scroll. I think that's really great. And that's because flexible
space bar has collapsed modes. Right now, by default,
our flexible space bar is completing this
parallax effect. But there are other
ones available. So let's clean up
a little bit here. We'll remove the title
from our Sliver App Bar, since we don't need it anymore. And let's take a look at these
collapse modes real quick. All right, so I'm in
a flexible space bar, setting a collapse mode. Which ones do we have available? Well, there's parallax, which
we know is the default, none, and pinned. So if it's pinned,
we'll see that it will scroll just like the rest
of our content in the list. I do like that parallax
effect, though. So I think I'm going to
stick with the default. But again, this is a choose
your own adventure workshop. So pick which
works best for you. OK. Let's take care of that contrast
issue before we move on. And I'd like to do it in a way
that we did similar to before. But instead of a
radial gradient, let's see what else we
have available to us. So around our image, we're going
to put another decorated box with a lower case e. And we're again going to
position in the foreground. And again, our decoration
will be a box decoration. And this time, instead
of a radial gradient, why don't we try a linear one? I think that would
look really nice going right
underneath that title so that we can see it clearly. Unlike our radial gradient,
our linear gradient requires that we specify
a beginning and an end that we're going to transition
these colors towards. So to end up right
underneath our title, I'm going to choose
alignment.bottomcenter. And I'm going to transition
all the way to the center. And then of course, we
need to specify our colors. I really do like that teal
that we've been sticking with up here as the header. So let's keep that
and carry it here. Let me go transparent so that
we can still see the image. All right. Let's see if that solves
our contrast issue. Oh, there it is. Oh, wow, that
looks really great. We can clearly see our image. And now, as it
collapses and fades out, it transitions
from teal to teal. I think that looks really nice. This is coming along very
nicely from the simple UI that we started with. However, I think that there is
a perfect little cherry on top that we can add. Let's explore that in step 7. All right, I have
been holding back. There is one more behavior that
the Sliver App Bar supports that I would like to show you. And it is called stretch. This is a common
UI pattern that we see when folks want
to refresh their data at the top of a list. And as you can see in our
constant scroll behavior that we set at the very
beginning of our workshop, we are using bouncing
scroll physics. These scroll physics are
common when you are using iOS platforms, for example. When you reach the end
of your scroll view, you can overscroll and
reveal this empty space. This extra space, though,
can be taken advantage of by our Sliver App Bar. So let's see how to do that. I'm going to close this again. And it's quite simple. Just like panning,
floating, and snapping, you can stretch to true. Now, of course, if you
wanted to refresh your data, we exposed a callback for you. This is called on
stretch trigger, which allows you to set
an asynchronous callback. You can use this
to tap our server and say, hey, get more data
because the forecasts are constantly changing, no
matter how hard we try. Now, we have a fake server here. So I can't actually do that. But I can click on the
console just to confirm. This will be
triggered by default when your user overscrolls
to a distance of 100 pixels. But you can actually configure
it to whatever distance you like. OK. So let's see what it looks
like when we stretch. Open this up. Run it. Wow, instead of all that extra
space when we over scroll, now our Sliver App Bar is
taking advantage of it. And as you can see,
there's something happening in the console. We've been printing every single
time we over scroll far enough to trigger our callback. That is going to be super handy
for updating our forecast. Now, as you can see,
our image is scaling. And that is because, of
course, the flexible space bar complements this
feature by default. And it's actually using one
of several stretch modes that are available. Let's take a look. Under flexible space
bar, by default, we are seeing that it
is using a stretch mode. It actually provides
more than one. And by default, it is doing
a zoom background effect. And this is what's
creating that scaling. There we go. OK. If I take this away, we can
see what the flexible space bar is doing for us out of the box. So without any
stretch modes enabled, the image doesn't go anywhere. I don't really mind it because
we have that linear gradient filling in the space. It's kind of neat. I'm going to leave
it on, though. And let's just add all of them,
all the bells and whistles for our last go-through. So the ones that
are missing here are blurring the background,
as well as fading the title as we scroll further away. So one last time with all
of them turned on now, let's see what we have made. Again, we're
collapsing our app bar. We're stretching our app bar. And it is fading, and
blurring, and scaling. There are a lot of
different things that you can do with
the Sliver App Bar. And these are just a few
of them we've touched on. We have really taken this
far from where we started. Let's sum up in step 8. So, my friends, we have
reached the end of our journey together. In this workshop,
we have learned how to build scroll views
efficiently using lazy loading for our scrollable. We converted our scroll view
to work directly with slivers and explored the Sliver App
Bar and how it's complemented by the flexible space bar. There are a lot more
slivers to explore. And oftentimes, they are the
same as their box counterparts. Where you may use
a fade transition, you could use a sliver
fade transition or padding versus sliver padding. All sliver widgets are
prefixed with the word sliver. To learn more
about these widgets and all of the
Flutter widgets, you can visit Flutter.dev or dive
into our API documentation link right down here. And you can also check out
our Flutter YouTube channel for lots of tips,
tricks, and tutorials on creating your next
great user experience. I am so glad that you
joined me and created what I think is a really fun app. So thank you very, very much. I hope that you are as excited
now about scrolling as I am. And as always, I can't
wait to see what you build. [MUSIC PLAYING]