RecyclerView ins and outs - Google I/O 2016

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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]
Info
Channel: Android Developers
Views: 91,537
Rating: undefined out of 5
Keywords: Android, RecyclerView, components, ListView, app architecture, code, developer, UX, UX flows, layouts, animation, app, apps, io16, product: android, Location: MTV, Team: Scalable Advocacy, Type: Live Event, GDS: Full Production, Other: NoGreenScreen
Id: LqBlYJTfLP4
Channel Id: undefined
Length: 46min 10sec (2770 seconds)
Published: Fri May 20 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.