, the ubiquitous UI element in which a list of information is presented in a linear, scrolling view. However, I do not think they do enough at explaining just why they behave in the way they do, especially with respect to stateful UI components.
The problem is, people do not quite understand what it means for views to be reused in the context of a ListView. This blog post is intended to explain what that means, and what consequences that has for your list views and your adapters.
Before you go on to the next few tasks, you need to understand how tables draw their cells. You might expect each cell in a table to be a component. However, for performance reasons, Swing tables are implemented differently.
Instead, a single cell renderer is generally used to draw all of the cells that contain the same type of data. You can think of the renderer as a configurable ink stamp that the table uses to stamp appropriately formatted data onto each cell.
Why would the designers implement the rendering this way? Imagine if each row of the table (or list) were a separate object. Each row would hold references to its own UI widgets, e.g. for TextViews (labels), ImageViews (images). If there were N rows and each row takes on average M bytes of memory, you’d have an average memory footprint of N*M bytes. Given that these lists need to be able to display a large number of items, this would be an unacceptable memory footprint. Furthermore, since most of the list would be offscreen, most of the memory would be completely wasted. While it would be simpler to implement, it would bring any Android phone to its virtual knees.
The designers, borrowing from the Swing designers, chose instead a much more memory efficient way of rendering the lists – if only X items are visible on the screen, where X is much less than N, the total memory footprint would be X*M bytes. It takes more work in the ListView class to calculate exactly which rows are visible and to ensure that only their views are rendered, but it’s worthwhile. Each visible row uses its own “rubber stamp”, filling in the details specific to that row.
What you do by defining your adapter view in XML is define the raw features that make up your rubber stamp.
This is the basic rubber stamp that might be used for displaying tweets in a Twitter application. There are four elements: an image, a username label, a paragraph text label, and another label for how the tweet was posted.
Let’s say that at most four tweets can fit on the screen at once. When the first row needs to be rendered, the view is null (hasn’t been instantiated) and must be inflated from XML. This process is repeated for the second and third row. When the next tweet needs to be loaded on the screen, it can reuse the view (rubber stamp) of the view that’s just left the screen at the top.
Row views are reused
This leads us into what trips a lot of Android developers up. You cannot maintain state within the UI components alone
. What do I mean? You cannot use, for instance, Checkboxes
in each row and hope to have them keep track of which items are checked or not. Why? This will work for a small number of rows, but once you get past the number that can fit on one screen, you’ll start to see weird problems. If you can fit X rows on the screen, then item 1, X+1, 2X+1, … will all share the same checkbox. Thus by changing the state of item 1, you will change the status of all the rows further down the line that are a multiple of X away from it. The correct way of handling this is to have each row data object, that which is being visualized by the Adapter, maintain this state. You can see an example of this in a tutorial on how to use checkboxes in listviews
. I see this question
come up repeatedly
on StackOverflow, and it stems from the programmer not understanding how these views are reused in each row.
The metaphor of the rubber stamp fits more with that of JTable rendering, where there truly is a single component that gets reused over and over again, whereas there are multiple reused row View objects in the case of Android. Why isn’t the single component approach used for Android? At first I assumed it was, until I read this great explanation on android.amberfog
which illustrates how there are actually as many views inflated as there are potential rows visible at any time. The reason as far as I can imagine is that the user needs to be able to interact with each rows. If all that was required was the static view of each row, then it would be fine to use a single rubber stamp, render a row, change an offset in the graphics context, and render the next row. As it stands, however, each row needs to be able to detect when it is clicked on, long clicked on, etc. Furthermore, each row could have UI elements which themselves could be interactive. Thus it is insufficient to present static views of the data; the user would be unable to interact with the rows in the way he expects.
I hope this explanation is helpful, and if I’ve gotten anything wrong, I’d love to hear about it in the comments.
(All images were created with the use of Balsamiq, a great tool for mockups).