MALE SPEAKER 1: ListView has
been around since Android 1.0. And it's meant to handle
repeated structure in consistent content. And more specifically,
it was made to handle this sort of
repeated structure in some very simple settings. So as you can see here, we've
got our old settings screen from Gingerbread. And this was already kind
of after a lot of extensions to what ListView
was meant to do. And sometimes, the content
isn't so consistent, so you've got different
view types floating around, so on and so forth. And we just kind of
kept adding features. But the core problem that
ListView meant to solve is, how do you make
it fast when there's a ton of items-- hundreds,
thousands, possibly more. Creating views themselves can
be really expensive, especially on old devices back in
Android's early days, and memory is limited. So you can't just necessarily
create tens of thousands of views and deal with
that all at one time. And the answer is we cheat. So with ListView, we do a trick
with some smoke and mirrors. And really, anyone who
works on UI development has a lot in common
with stage magicians. Stage magicians use
misdirection and other sorts of tricks on stage
to make you think that you're seeing something
that you're really not. And if it looks
right, it is right. So I mean, what they're
seeing is the UI in terms of what the user believes. So the trick that we use is
that we only create and lay out the views that the
user can see right now. And as the user scrolls, we
continue to lay out more items. The image you should
have in your heads right now is that bit
from "Wallace and Gromit" where you're quickly
laying the track in front of the moving train. So that's basically
how ListView works. And in order to
enable this, ListView uses adapter components. Now, this is a component
that's provided by the application itself. And it's used to
create and populate the item views on demand. You've got this getView
method in the adapter that is used to query the adapter
for a particular view to show a data item
that corresponds to a particular
position in the list. And the extra trick
that we do there is we also pass in a special
convert view that's guaranteed to be of
the same item type that the adapter believes
that new position to be so that you can
reuse the same view, and you don't have to pay
the cost of inflation again. And over time, we continued
adding more and more features to ListView because
they were things that people needed
in their apps. And this question
kind of came up a lot. So, ListView grew feature
after feature after feature. And you can go back and
watch this talk from I/O 2010 if you want to
see a few of them. MALE SPEAKER 2: Watch that talk. It's really nice. You should watch it. I enjoyed it. MALE SPEAKER 1:
Yeah, if you want to see some of our classic UIs
in some of that talk, as well, yeah. MALE SPEAKER 2: It's nice. MALE SPEAKER 1: But really,
as we kept adding features to ListView, we kind of hit
this complexity overdraft point. We had so many features
that kinda sorta interacted with each
other but not really, all of them a little bit one-off. And it was just all
these long tail features that led to a lot of very
strange interactions. And what that meant
in practice is that everybody wrote
their apps such that things worked
how they wanted it to, but a lot of times
those things were really just kind of the result
of undefined behavior in ListView's
implementation itself. And once enough apps
start doing that, then that's de facto API
that we have to support. Any small change that we make
to ListView's behavior that might improve it for some
of these core use cases ends up breaking some app's
really hero use case that they want to use someplace else. We also had a lot of
duplicated functionality in ListView, like ListView
selections or View focus, which ones do we use? I mean, the view
hierarchy already knows how to handle
things like keyboard focus as you navigate
around, but ListView had its own completely
separate idea of this in terms of the
current selection, a selector drawable that's drawn either
behind or over the views, depending on what
configurations that you set up. And this led to
a lot of overlap. And not only that,
but it meant that, when you start using more
complex views in your ListView items, you kind of
have to switch over to this setItemsCanFocus
option that disables ListView's internal selection
handling and lets you use the View hierarchy's
focus handling instead. Similarly, we had the same issue
around item click listeners versus View click listeners and
other sorts of general touch handling. So ListView provided this
very nice convenience where you could set an item
click listener on the ListView itself and get clicks
for anything that happens in that list. Now, this makes a
whole lot of sense for lists that are
really uniform, where you have a list
of items and you just want to pick something from it,
kind of like the pop-up menus that we have from
the overflow menu up in the upper corner
of your apps today. But alternatively,
you can also just go ahead and attach
click listeners to these views themselves. And which one was going
to handle the events was sometimes not always
clear or intuitive. So people would ask
us a lot of times, you know, which one do I use. And the answer always
ended up being, it depends. It was like, OK, well, how
complex are your list items, so on and so forth. And anytime that
you have to answer a lot of those types of
clarifying questions about, like, OK, well, what
are you trying to do? Then that really kind of
complicates the developer story. But really the big
thing that kind of kicked off the whole
RecyclerView effort was that ListView animations
are really, really difficult. And the core problem is
that adapters don't tell us enough to do them smart. So when you change an adapter,
you say notifyDataSetChange, and the assumption is
anything could have changed. We're not entirely sure
what, and we're just going to go ahead and rebind
all the items that are currently visible and go from there. And over time, we had a few
prototypes-- some of them were done by members of
the team, some of them were done by members
of the community-- all to try to show
items animating in and out as the data
set changes, and do so in a way that's
kind of intuitive. But all of these had some
really major limitations. Most of these had
to do with, again, the internal implementation
details of ListView that apps had really
come to rely on. So we couldn't fix
these things to make these animations possible. So eventually, you just end up
making this face at ListView. MALE SPEAKER 2: Yeah,
look at his-- look at the regret in his face, like
I'm doing this [INAUDIBLE], but I'm not really sure
if I should do this. Like, I'm gonna regret this. I have to do. MALE SPEAKER 1: Yeah, Chet
wears this face a lot. MALE SPEAKER 2: It comes
with the framework. MALE SPEAKER 1: Right. And again, another
one of these problems that we had with
ListView was just this duplicated functionality-- MALE SPEAKER 2: [INAUDIBLE]-- MALE SPEAKER 1: Like what is-- MALE SPEAKER 2:
[? --slides. ?] [INAUDIBLE]. MALE SPEAKER 1: Yeah, I
guess we did, didn't we. OK. So as we moved
on, people started using much, much more complex
layouts in their ListView items. We went from something like
that very simple settings screen in Gingerbread to
suddenly, people are showing items from a
full social stream, items with many different
components to them, with many different
points of interaction. So these simple grids and lists
that we provided with ListView and GridView and
the framework really weren't enough to handle
these use cases appropriately. People started wanting to create
these sorts of staggered grids, responsive layouts,
so on and so forth. And really, if you wanted to
change these types of layouts, you were rewriting a
whole lot of core code that ListView used to do
this adapter-based recycling. So if we had a do-over,
what would we change? Well, one of the things
that we wanted to do was to elevate
the best practices of working with ListView
to first-class API. We had this ViewHolder pattern
that Roman and I went over in the "World of
ListView" talk linked earlier in the talk here. And the nice thing about
the ViewHolder pattern as it existed for
ListView is it gave one point where you could sort of
collect a bunch of attributes about a recycled
view that you were going to work with and have
that single point of access. You could kind of cache a lot
of these findViewbyID calls that you would make to an
inflated View sub-hierarchy. And especially on
older devices, that made a big difference,
because you were doing fewer traversals. And as you were creating more
and more complex item layouts, it mattered even more. So we thought, OK,
well, let's go ahead and just make that
part of the core API. Like, let's go ahead and,
instead of having this just be a best practice that
everybody has to read some blog posts to figure out about,
let's go ahead and make sure that this is
something that you see as soon as you read the
documentation for working with RecyclerView. We want to separate view
creation and binding. Now, this was something that
happened in the cursor adapters that many of you have probably
used if you worked with SQL Lite databases or
content providers as your backing data source
for your ListView adapters. We separated out an onCreate
versus onBind step in those. But if you were just writing a
raw ListView adapter yourself, then this was
essentially a question of the very first couple lines
of code in your getView method where if convertView equals
null, create a new thing. And then sort of follow through
and do the binding there. And this was one of
those things that was fairly easy to forget to do. And if you forget
to do it, then that means that you entirely lose
out on this recycling mechanic that ListView gave you to really
kind of boost your performance. Sometimes, it
wasn't really clear why this was happening
from the outside. And next, we really
kind of wanted to throw out a bunch of
this selection handling that really only made sense
for these very simplified lists and just rely on the framework's
normal focus and input handling, because this
was-- this, again, just kind of comes
down to simplicity of working with the
rest of the framework, and kind of reinforcing
the consistency of working with the rest of
the View hierarchy. And then selfishly, we also
wanted a little bit more easier maintenance on
our end, because-- I don't know if any of you have
taken a look at the internals of ListView in AOSP. I'm pretty sure that it could
be used to scare small children. So we wanted to keep
the core universal. So all of those things
that were a total pain to rewrite yourself if you tried
to write your own recycling container, we
wanted to have a way to give that to
developers more or less for free in a canned form
so that they didn't have to sort of rediscover
all the things that we had to do the hard way. And we wanted to make
sure that you could still implement all of these
other long tail features that ListView just kind of
provided as API on ListView itself as other plug-ins that
we could sort of share and mix and match to get the
experience that you wanted. We needed smarter adapters, too. Again, the problem
with ListView adapters is that you could say,
notifyDataSetChanged. OK, what changed? Uh. And that was about all you knew. And this makes animations
really difficult. We'll get to that a little
bit further on down the line. So RecyclerView adapters
tell us what changed, not just that something changed. Now, if you're working
with a data source that doesn't provide that sort
of fine-grained messaging, then you can still use
the old ListView style, hey, something changed,
go figure it out. We'll make some best-effort
attempts at dealing with it. But really, if you
have any knowledge of what the specific changes
in your data set are, you can get a lot
better results. So this also means that we can
do more efficient recycling, not just animations. If we know that only a
particular item in the adapter changed, then we don't
have to rebind everything that's on the screen, we only
have to rebind that one view. And this can save a lot of time
in the measurement and layout phases of applying
those updates. MALE SPEAKER 2: All right. So that was born RecyclerView. So make mistakes we
learned from ListView, Now like, OK, how do we
write RecyclerView not to repeat the same mistakes. And the major thing in
RecyclerView is we try to make the [? complement-based ?]
architecture. This is the architecture
of RecyclerView. And we will go through
the details of it. I think it's very important. I believe most of
you already use it. It's good to know who does what,
and which one you should use, or which one you
should customize, depending on use case. So with the RecyclerView, that's
the main view, [INAUDIBLE] there's major three--
three major components. There's the layout manager, an
item animator, and an adapter. So adapter is provided
by your layout managers. Most of you provide
you can write yourself, and item animator is the same. There's a good default one
where [? you can ?] [? write ?] yourself. So layout manager
positions the wheels, item animator animates
them, and the adapter provides the wheels. And the RecyclerView
is the boss. So it's the central
location that communicates with
all these components to make them work together. So we need to look at
the layout manager. It can be a linear
list, it can be a grid, or it can be a [INAUDIBLE] grid. It can be anything. RecyclerView doesn't
know or doesn't care about how the
items are laid out. It's the layout
manager's responsibility. Or scrolling, RecyclerView
handles the interactions with the [? finger ?] but
tells the layout manager to scroll because only
the layout manager knows where the views are. So it stores the content. Or if you do focus
traversal, RecyclerView handles the basic
focus traversal because the-- actually, the
[? V ?] framework handles it. But if you are focusing
and you need one more item, then it's the
layout manager that brings the new item
to the focus area, because that did
not exist before. Or the accessibility,
[INAUDIBLE] provides basic information
about those items, but layout manager
[INAUDIBLE] is the one that knows, OK,
this is a section title. Or, you could attach your
custom delegate that says, hey, like do not focus on this,
or additional information that only you can provide. [INAUDIBLE] RecyclerView
accessible to delegate API for this. MALE SPEAKER 1: All right. So similar to ListView,
RecyclerView still relies on adapters. Now, a RecyclerView
adapter's responsibilities are, again, to create
the View, but also to create the ViewHolder. And the ViewHolder
is what we really use as the tracking
element for recycling. So when you're
binding an item, you don't just bind it to
the View, you bind it to the whole ViewHolder. And this is something
that you can use as a place to stash
other information, as well. And of course, it's the
adapter's responsibility to notify the
RecyclerView about changes that happen to the data set,
or if the data is out of sync. So in contrast to
ListView, where you could use this item
click listener type of model for things, the adapter is now
responsible for configuring the item interaction handling. So these are all your click
listeners, touch listeners, so on and so forth. Adapters also deal with
your multiple View types, sort of being able to keep track
of the different ViewHolders that you may be tracking. MALE SPEAKER 2: And I-- one tip
about the [? weave ?] types. In the ListView world,
you need to give us how many [? weave ?] types you
have, versus in Recycle View, they're unlimited. So I suggest just use
[? r dot ?] layouts [? whatever ?] as
the [INAUDIBLE] type, and you're done. MALE SPEAKER 1: Right. So your View type
IDs is no longer needed to be contiguous the way
that they did with ListView. So as long as you have any
sort of unique identifier you can use to identify a View
type, then go ahead and use it, and RecyclerView
isn't going to care. So that means that you can
use this handy trick of using the layout resource ID itself. And since AEPT
already guarantees that that ID is
going to be unique, it makes a pretty good
stand-in for that identifier. Oh, whoops, went
a little too fast. So the adapter also deals with
things like recycler recovery. So we have this
onFailedToRecycleView concept. So why could something
fail to recycle a view? Well, again, we're
trying to keep in mind that people are doing
more and more complex things with their UIs. And in those cases,
you might start making a lot of
changes to your views that are very
difficult to reverse. So in those cases where we
know that a View has been left in kind of an
inconsistent state, then we can communicate that back
and forth to the adapter so that the adapter is able
to sort of recover from a case where a View has
been messed with kind of beyond the normal
bounds of expectation. And again, we have the
granular data change events, as added, to sort of
handle efficiency and-- MALE SPEAKER 2: Performance. MALE SPEAKER 1:
I'm totally blank. Performance. That's efficiency. MALE SPEAKER 2: Animations. MALE SPEAKER 1: Animations. Yes, that one. All right. MALE SPEAKER 2: All right. So let's deep dive
into the ViewHolder, because most of-- everybody
needs to write their ViewHolder to use RecyclerView, and you
spend most of your time there. It's important to know the
life cycle of a ViewHolder. So we'll go through it. What happens when we create
it, what are the stages. So while the Layout Manager
is calculating the layout, it tells the
RecyclerView, OK, give me the View for position five. So it wants to get the
View for that position. RecyclerView checks the cache,
because we cache the views. Now if you already
have it in the cache, we will return it to
the Layout Manager. If not, so we have it,
[? need to return it. ?] If we didn't have
it, the case said no, RecyclerView will tell
the-- ask the adapter, OK, what's the type of this view? Adapter returns it. Then we go check the pool. This might be a
shared pool, this might be the-- like the only
pool used by the RecyclerView. Do we have a ViewHolder
for this [? View ?] type. If it says no, we tell
the adapter to create one. So this is when we actually
create a ViewHolder. Or maybe the pool returned
an item that already exists. Then we tell the adapter,
OK, bind this position to this ViewHolder. It returns it, and we give it
back to the Layout Manager. Eventually, Layout Manager will
add it back to the RecyclerView as [? a ?] [? real ?] child, and
then we will tell the adapter, hey, this [? View ?] has
been added to the layout. This is a very good
hint for your adapter to know that, OK, user is
about to see this view. This other case where
Layout Manager might say, OK, I'm done with this
view, remove and recycle it. RecyclerView will tell
the adapter, OK, I removed this view,
let you know so that if you have an
[? expensive ?] things inside that view,
you can uncache them. You can remove them from memory. And then we will
check, OK, is this view valid for this position. If it is valid for this
position, we will cache it. The idea is that if the
Layout Manager asks back for that position
again, we can give it without talking to the adapter. This is, again, for
performance reasons. And then the cache will
evict the oldest one. And I will tell the adapter,
hey, we got rid of this view. Now you know that it will not
be reused for that position, so you can do even
further memory clean-up. MALE SPEAKER 1: Right. This is kind of an important
step that was a lesson learned from ListView, where ListView
eventually added this Recycled View listener that you could use
to track when a view was really no longer in use. And again, because people create
much, much more complex UIs in these sorts of
recycling containers these days, this allows
you to take a step and say, OK, well,
instead of leaving these giant, expensive bitmaps
attached to these views that are just being held in a cache
offscreen, let's go ahead and clean those
things up, but still retain the core view structure
that we had before so that we can rebind it later. MALE SPEAKER 2: So if
the view is not valid-- let's says it might be-- maybe
the item has been removed because the adapter told
us, or the item was changed and the contents of the
view does not represent that position anymore. If [INAUDIBLE] valid anymore,
we just send it to the pool and we tell the
adapter about it. There is another use case where
Layout Manager recalculates the layout after
some adapter changes, and that doesn't use
some of the Views. So [? it ?] [? had ?] all the
Views from 1 to 5, let's say, and then makes one, it
only use eight of them, and the other two are missing. So what RecyclerView
does is, OK, we understand that the
Layout Manager doesn't want these Views anymore,
so but ItemAnimator may want them, right. You may want to fade them out
instead of disappearing them instantly. So what RecyclerView will do is,
for each disappearing children, it's going to make them children
again and then hide them from the Layout Manager. We will go into
details of this later, but basically the list of
children from the Layout Manager's perspective are not
the same that as the children of the View group. When the ItemAnimator says,
OK, we'll tell ItemAnimator, animate these children. And when the ItemAnimator says,
OK, I'm done with these Views, you can do whatever
we want, then RecyclerView will remove them. It will tell the adapter
they are going away, and then we will recycle it. So how do we lose a ViewHolder? This is very important because,
if you're losing a ViewHolder, you will hit
performance problems. [INAUDIBLE] same thing happened,
Recycler Layout Manager said, OK, I want to
get rid of this view. RecyclerView will check
is this view valid. [INAUDIBLE] no, OK, we want
to put this into the pool. And the pool [INAUDIBLE]
[? that ?] [? this view ?] has [? transisted ?] this is
what Adam mentioned earlier. A view having [? transisted ?]
means [? is like ?] [? is ?] animating the
contents or, you know, we have a button that's
doing some animation. That means if
[? a view ?] is animating, we cannot reuse it
for other ViewHolder. Because imagine that it
was fading out and then we bind it to something
else, and it comes to the UI as faded out. It's like, that's
not what you want. You want fresh views. MALE SPEAKER 1: There
are a few other cases that can cause views to have
a transient state, as well. This is a concept that's
part of the framework, not just the
RecyclerView itself. So for example, if you
have an editText widget with a piece of text that
the user has entered, and they've gone
ahead and created a selection across several
words but maybe not others, then that's kind of
this complex interaction that the user has
created that we really didn't want to make it the
developer's responsibility to sort of track and restore
that if that particular item is rebound across other contexts. MALE SPEAKER 2: So what will
happen is that now we cannot reuse this view, and we are
removing from the RecyclerView [? like ?] [? the last ?]
reference we have to that view is going away, so we give
adapter one last chance. Hey, I could not
recycle this view. Can I recycle? Because most of the time,
it's because of animations [? on selection. ?]
You can actually recover from that state. The only problem is that
we cannot do it ultimately, because we don't
know what's going on. So adapter can say either
[INAUDIBLE] failed to recycle, you can end those animations or
clear the [? selected ?] state, or you can even say do nothing. Like, I know I fixed this
when [INAUDIBLE] [? this ?] [? code. ?] Like, recycle
it, I will use it. But if the adapter does
not implement this method, by default, it returns false. So we will have to
destroy the ViewHolder. So it goes to the dead pool. You don't want this. The moral of the story is,
if you're animating items, don't go ahead and create
animations [INAUDIBLE]. We have the ItemAnimator API
to do these things properly. So use the ItemAnimator
if you want to animate. And the ItemAnimator receives
the correct life cycle events so that we can
recycle-- we can take care of recycling those views
when they are done animating. MALE SPEAKER 1: Right. Essentially, you can
forget about everything that just happened on
that slide before you, as long as you implement
the ItemAnimator interface. MALE SPEAKER 2:
Yeah, [INAUDIBLE]. MALE SPEAKER 1: If
you're doing it yourself, you're on your own,
and you're going to have to handle all of
these problems yourself. MALE SPEAKER 2: Yeah. And [INAUDIBLE]
like the major thing when you start using
[? Recycle, ?] you really need to forget everything
you learned in ListView. Most of them are
valid, but [INAUDIBLE] some bits that are not valid
will actually hurt you here. So there's another
use case where we will lose views is
RecyclerView is like, OK, I am done with this view. We tried to put
it into the pool, but pool is limited
per size per item type. So if you have way too many of
those view types in the pool already, We're like, you know
what, we have no space for it. Throw it out. So why would this happen? Why would we create
views that we already had [INAUDIBLE] them stuff
that are unused in the pool? Well, this usually
happens if-- go ahead. So if you have too many
ViewHolder types, and why would it happen is mostly
because of animations. Like you told us, [INAUDIBLE]
to make it animate, instead of calling
[INAUDIBLE], call [INAUDIBLE] change from zero to all of them. So if you say
something like this, RecyclerView is
[? like, ?] [INAUDIBLE] for every single visible
child, by the default behavior, I have to create another
ViewHolder to represent the child so I can
cross-fade them, because the default
behavior is cross-fade. That means you duplicate
the number of ViewHolders. Of course, when those
animations are finished, we try to put all of
them back to the pool. And pool is like, eh, I
don't need this many items. You know, just throw them out. So don't do that. When an item has changed,
tell us what actually changed is really, really important. [INAUDIBLE] the
correct animation, or if you have some other cases
where you notice the views are being lost, you
can change the type-- the size per type in the pool. MALE SPEAKER 1: So this
comes up when you have cases where, again, social streams are
a really big use case for this, as well. You might have some posts
that are very long that fill an entire screen
all by themselves, or you might have several posts
that are very, very short. And if you have items that are
of drastically different sizes like that, then
the number of items that can be attached
at one time is going to vary pretty greatly. So those are cases
where you really want to sort of measure what
your app's own behavior is under some of these
use cases, and tune the size of the RecycledView
pool accordingly. All right. MALE SPEAKER 2: ItemAnimator. MALE SPEAKER 1:
So, ItemAnimators. Items come and
items go over time. And again, we want to use a
smarter way of figuring out exactly what it
was that changed. So let's say that we add
a new item in between H and I in this diagram here. So now we have P in the
middle, so no longer sorted. Ignore that. What happens if
something disappears? Well, now we have
another image of a list. Now, the interesting
thing here-- oh, and then of course we have the case
where an item simply changes, and its contents
change in place. So the interesting
thing in these cases is that there's always
kind of some side effects when you add and remove views. So we added view P here. I and J got moved down,
and K is completely gone. When we remove, something
really similar happens. So we remove item H. I, J, K
move, but L is new on screen. We had to get a new view in
order to show it, in that case. So without extra information--
oh, sorry, change first. And of course, this is
one of the easy cases. We can just go ahead and
cross-fade between these. So if we're handling things
this way, if an item's removed, we can fade it out. If an item's added,
we fade it in. And if an item is just sort
of moving around on screen, we can translate it. And our default items
animation handlers can handle this, no problem. Changing, we just do a
simple cross-fade in place. And as long as we
don't have cases where that item
has changed size, we're in a super easy case. But otherwise, it just
means other items around it are moving, and we get into
exactly the same cases as before. But can we do better than this? There's kind of a key
problem in these cases, which is that, if all we know
is that something changed in the adapter and here's the
new state, like with ListView, then how do we really tell the
difference between an item that was removed from the adapter
after this versus an item that just was knocked off screen
and is no longer there. When we're putting together
these sorts of animations, this is suddenly
really important. Similarly, how do we know
something came on screen because other things
moved around it, or if it's actually a new
item in the adapter itself. Again, without there
being a representation of those views attached to
the parents from the outset, we don't necessarily know. So we can do better,
because now we know this. We know that
conceptually-- again, coming back to this
smoke and mirrors analogy from before, even though we only
have a certain number of items attached to the view
group at a time, conceptually we have a lot
of items that come before and after what's
being shown on screen. So we can get a little
bit predictive about this if we're willing
to ask the adapter some additional questions, and
specifically in conjunction with the Layout Manager. So as we add this
item here and we have to delete K,
in this case, we know that K is still present
if the adapter hasn't told us that it's now gone. So we know that it's just
sliding offscreen instead. Similarly, with removal,
we can use the same trick. We can ask the
Layout Manager, hey, show some stuff
around what I'm doing so that I have the
added context to be able to take the diff of
the before and after states and animate it correctly. MALE SPEAKER 2: OK. [INAUDIBLE]
speed up a little bit. MALE SPEAKER 1: Yeah. MALE SPEAKER 2: So the way this
works about the ItemAnimator is the predictive animations
is that RecyclerView can show a view of the
adapter in the past. So how we can do
is like, you know, if you have added new
items to the adapter, we don't tell about them
to the Layout Manager, because it's like it
always sits in between. What we do is we show
a different view. For example, before
we implemented that, there was a Layout
Manager in ItemAnimator. And there's a view. And they will always fight. No, this view is mine. No, no, no, this view is mine. Like, I want to animate it. But wait, wait, I want
to remove and recycle it. So it always gets into
this conflicting cases. The way we solved this
was the ChildHelper. So when Layout Manager
says hey, like, I want to get the
child at the position or remove it or do
whatever I want, RecyclerView doesn't
call the View group. Like, we don't change
the children instantly. We instead [INAUDIBLE]
ChildHelper, which is the component that
is responsible to create this [INAUDIBLE] about the contents. And then the
ChildHelper decides, OK, Layout Manager
told me to remove it, but ItemAnimator told
me to animate it, so I'll keep it until
the ItemAnimator is done. But we don't want Layout
Managers to get more complex trying to understand
about animations, so for the Layout Manager they
look like they disappeared, but they're actually real
children in the View group. So ChildHelper is responsible
to provide a virtual list to the Layout Manager. So for example, let's
assume this view has been swiped-- has been removed. So while you're on the
Remove Animations at that 200 milliseconds, if that
Layout Manager tries to get the child at
the third position, it's going to return Barcelona. And if the RecyclerView is-- if
you try to get the RecyclerView [INAUDIBLE] child, [INAUDIBLE]. So the [INAUDIBLE]
between these components helps us do these kind
of tricks without making these components complex. And when the
animation is complete, they will start
returning the same value. AdapterHelper is the same thing,
but doing the same abstraction towards the adapter. So when you're like, have
the adapter [INAUDIBLE] send all these [INAUDIBLE]
a bunch of things happening in the list. And Recycler is like,
what's going on. I have no idea what's
happening there. It's very hard for Layout
Manager to track these things, because it's already
busy tracking the UI. So AdapterHelper
is to the rescue. So when you say
notifyItemInserted in the adapter, we record
it and we request a layout. We know something happened. We need to redraw. That's it. The only thing we do is we
keep the information about it. And now, as you add
more [INAUDIBLE], we just keep them in a list. Let's say, during this
moment, before we recalculate the layout but we know the
adapter contents have changed, user clicked on a button. And you wanted to get an item. You called getAdapterPosition
for the item at 59, which was clicked. But we know there's a new
item, added at index 55, so we know that in
the adapter, there's actually at position 60. So we return you 60. So [INAUDIBLE] not
recalculated, if you need the adapter positioned to
go access your adapter items, we can provide you
the right position. So this simply
happens by checking what you told us before. And let's say you
send more updates. And then we will
go through them. The RequestLayout will
be honored by the system. We will start preparing
the new layout. And at this time,
the adapter is going to provide the Layout Manager
the [INAUDIBLE] positions. So how does it work? What we do is, when
I talk about, like, faking the adapter contents, is
we reorder your updates first. So the idea is
that everything we have to tell to the
Layout Manager first, we tell about them. We let it do the
predictive layout. And then everything else
we tell about them later. So if you look at you
removed two items from 61, we know that was
61 we reordered. [? When ?] we look at
this, OK, I actually have the item for 60. So even though it is removed,
when the Layout Manager is recalculating the
previous state, which is part of predictive
animations, if it asks for item
60, I can provide it. So I don't need
to tell the Layout Manager that that item has
been deleted from the adapter. But item 61, I don't have it. So what I do is I divide that
[? remove ?] [? operation ?] into two. I tell the Layout
Manager, hey, like, before you calculate the
layout, the item 61 is gone, just to let you know. It does the pre-layout, goes
to the post-layout phase. Then we tell about these things,
like this fine-grained adapter updates tells, like, enables
us to fake the adapter content source layout manager
so that all it has to do is we ask it to lay
out twice, and then we predict all the rest
of the animations. So Layout Manager
almost doesn't need to know anything about that. OK. MALE SPEAKER 1: All right. We'll try and move some
of the-- through some of the other features here
a little bit more quickly. So, ItemDecorations. ListView has the ability, like
many other one-off features in ListView, to draw
dividers between items. And so we needed to make sure
that this sort of functionality was still preserved. So ItemDecorations
allow you to do custom drawing on the
RecyclerView's canvas itself. So this is in the context of
the RecyclerView, the parent of all your child items,
not necessarily just individual items within it. We could also go ahead and
add offsets to view bounds. Again, using the simple
test case of the divider, you need to make sure
that you have that couple pixels of space that the
divider consumes itself, rather than drawing necessarily
over or under the items views. And you can also have
multiple ItemDecorations. They stack. So these ItemDecorations
can affect views, again, in sort of a stack-like manner. ItemDecorations. So we can draw items
over our list items here. [INAUDIBLE] come on. All right. So another kind
of important thing here is the getItemOffsets. This is really what lets us add
space around particular items. So this can be nice if you're
doing some sort of card background around
several items at once, so like a grouping of some sort. But you can also do it
just on single items, too. So we can expand the space
around a particular item, because maybe that's an area
that we're going to draw in. So we get onDraw, which we'll
draw underneath the item views before we actually draw
the item views themselves. Which is then followed
by the items themselves. And then we get
onDrawOver, which allows us a chance to draw
on top of the item views, in case that becomes relevant
in your particular situation. So in this case, we're doing--
we're just otherwise decorating these things. So you need to be kind of
careful about ItemDecorations, though, because this
drawing phase here might be in the middle of
several other operations that RecyclerView view is doing. And as [INAUDIBLE]
just detailed, there are a lot of
things that RecyclerView does to sort of try and present
a consistent view of the world to adapters versus
the Layout Managers who are doing animations
and so on and so forth. So the adapter's
view of the world may not be what you're
seeing on screen right then. So you want to
make sure that you don't use the ItemDecorations
to access the adapter. Keep the necessary
information about things you need to draw in
the ViewHolder itself. The ItemDecoration can
access the ViewHolder at will because the ViewHolder is data
about what's being displayed on screen right now. And again, the general
rules around drawing apply. This is a pretty hot code path. Ideally, this is being
called 60 frames per second, if everything's going right. So try not to allocate memory,
do anything too expensive, so on and so forth. You can always go
ahead and use the recyclerView.getChildViewHolder
method to get the ViewHolder for a
particular ChildView that's attached to the
RecyclerView at the time. So the RecycledViewPool
is another component that's kind of
important to know about, if only because it allows you
to pull some pretty neat tricks, depending on the type of
UI that you're creating. So the Recycled View
Pool would normally just be an internal
implementation detail where we're holding
extra ViewHolders of the different types
like we mentioned earlier. But the cool thing about
the RecycledViewPool is that you can share it
between multiple RecyclerViews, or even just other
custom view groups that you've written that need to
do some similar operations that need uniform views of this type. Again, always make sure
that you keep these scopes to a single activity. These things are holding views. Views hold onto a context. Contexts were inflated
with your activity, and you can get
yourself into trouble if you end up leaking those
activity contexts by accident. So make sure that
these are scoped to a single activity, at most. But you see UIs like this in
terms of the Android Leanback Library for Android TV. You have multiple rows that
are actually RecyclerViews held within RecyclerViews. But the innermost child items
are all of a uniform type. So what we do there is we can
share the RecycledViewPool across all of these
individual rows. And this allows us
to be much, much more efficient in terms
of how we allocate those and manage those pools. MALE SPEAKER 2: OK. So as we start
adding more functions to RecyclerView, what we
tried to implement them as components. So [INAUDIBLE] Drag & Drop,
if you look at ListView, there is like a
bunch of libraries. You need to look
at the examples. And only some of them
work for your use case. So we wanted to learn
over from our mistakes. When we designed
ItemTouchHelper, we wanted it to be
independent of the layout. So want to use ItemTouchHelper,
it can do Drag & Drop, it can do Swipe to Dismiss. And all you do is you
provide this callback class. In the callback class,
[INAUDIBLE] getMovementFlags. This is the idea. I have a ViewHolder. How can I move this
View Holder [INAUDIBLE] up and down or left and right. And you could say as what should
I do when the item is moved. So let's say the user
moved it to somewhere else, and then now you need
to change the adapter. Similar swipe. The user is swiping
to the right. When it is done, we
will let you know. So all you have to do is tell us
how a ViewHolder can be moved. Tell-- actually do what you
need to change in your adapter when it is moved or swiped. So you can actually run Drag &
Drop and Swipe at the same time in the same Layout Manager. It just works. So this is a getMovementFlags. We provide [INAUDIBLE] Flags. So in this example,
I say, each item can be moved--
dragged up and down, and you can swipe
it towards then end. So it also supports RTF. [INAUDIBLE] if you say
[? and, ?] your callbacks will receive the same things, like an
RTL parameters if you use left and [? right, ?] your callbacks
will receive left and right. So you can also customize
how it is drawn. [INAUDIBLE] [? demos, ?] we have the sample
like you can make it fade away as you swipe. You just [? override ?]
[INAUDIBLE] in the callback. And you can also start the
Drag & Drop or Swipe yourself. Maybe you have a
custom button there. Try to go a little bit faster. OK. We'll go through
some tips and tricks. MALE SPEAKER 1: Yeah. Really quick here. So again, as we
talked about before, we wanted to be more efficient
with RecyclerView adapters than we were with
ListView adapters. So if you don't update
a particular item, you don't get onBind
for that item anymore. We're able to just reuse
that view in place. So even move
operations, we don't have to go back to the adapter
to ask for that data again, because, hey, it hasn't changed. Why should we do the extra work? So we don't get an
invalidate on that view. We make sure that cache works. We have happy kittens. And then what that
means, though, as a side effect is that, since
items can be added or removed around it and items can move,
when you bind your views, don't make assumptions
that the position that you were initially
bound for is always going to be the position
corresponding to that data item. Again, this is a perfect example
of what can end up going wrong. You want to make sure that
you access the position live from the ViewHolder itself. The ViewHolder knows
what's going on there. In this case, yeah, there we go. The ViewHolder knows what the
adapter position is right now. So we make sure and keep
that updated for you so that you don't have
to track it yourself. But make sure that
you don't accidentally close over the value of the
adapter position by accident. MALE SPEAKER 2: By the
way, Android Studio has [INAUDIBLE] now, so it
will not let you do that. But so use
itemChangeWithPayload. So when an item changes, like
the time change or a user liked it, use it
with the payload. It will make your onBind
a lot more efficient. You can run better animations. So in the onBind method, you can
just check if you [INAUDIBLE] payloads. So if there's no payloads,
[INAUDIBLE] ViewHolder. If there's payloads, we
guarantee that the ViewHolder is being reused
for that position so you don't have
to set everything. You only set what has changed,
and you know that information from the payload. It's very efficient. It's great for animations. Another mistake we see commonly,
like you have a header view. When we ask for the header
view, you return the same one. Don't do it. If you called onCreate, that
means create a new ViewHolder. Don't try to return us the
same [INAUDIBLE] you have, because you will have bugs. [? We ?] wouldn't call that
method if we didn't need it. [INAUDIBLE] something like this. If it asks for you to
create, create a new one. Adapter versus layout
positions is something we previously mentioned. But [? a ?] [? view-- ?]
let's say you moved an item. At this point, you can
see the adapter positions and layout positions
of some items are different until
the next layout is calculated, because they are
also calculated asynchronously. So once the [? View ?]
[? system ?] tells us, OK, you can refresh [INAUDIBLE]
[? refresh ?] [? it, ?] now all of the items have the same
layout position and adapter position. So adapter's position
is very good if you need to access your data, layout
position is very good if you want to know what is above
and below at the point [? when ?] user
clicks something. MALE SPEAKER 1: All right. And I think that's it. MALE SPEAKER 2: Thank you. Yes. [APPLAUSE] [MUSIC PLAYING]