I learned Android's ArrayAdapter today, and find there is a commom pattern which uses a ViewHolder to hold Views' reference instead of calling findViewById everytime.
But how does it work? Adapter is usually used to display a list of View(Group)s, If I cache the View, why don't they all reference to the oldest one?
If you want the best explanation on how the ViewHolder works, check out Romain Guy's Google I/O 2009 talk in youtube , specially the first 15 minutes.
In short, the Adapter functions as a link between the underlying data and the ViewGroup. It will render as many Views as required to fill the screen. Upon scrolling or any other event that pushes a View is out of the screen, the Adapter will reuse that View, filled with the correct data, to be rendered at the screen.
The getView(int pos, View view, ViewGroup parent) method will use the right View at any time, regardless of your layout. I do not know the internals of this, but I'm sure you can browse the source code for any adapter (such as ArrayAdapter.java) if you're interested.
The ViewHolder just keeps a pointer to the Views as obtained by view.findViewById(int id). It is the Adapter responsibility to return the right data corresponding to any position.
Slides 11 to 13 of Romain's presentation will make it a lot more clear than anything I can write.
Sorry but denis' answer may be wrong.
In fact, the view instances(and ViewHolders) are as many as your screen can display.
If your screen looks like:
[list view]
the first item
the second item
the third item
the fourth item
You will have 4 instances of views. If you scroll screen, the first will disappear but be pass to getItem() as convertView for you to create the fifth item.
So you can use the references in first ViewHolder.
I believe the work beneath the list view is something like this (considering we have only one item view type):
do once:
inflate item view from layout, cache it
repeat for every item:
ask adapter to fill the data into the view
draw the view on the screen
move to next item
so you have the view which is inflated from xml layout and can be reused for drawing multiple list items. ViewHolder speeds it up a bit more by saving getViewById lookups.
Related
I have watched a lot of videos on RecyclerView but I am very confused on whether the ViewHolder is an adapter that changes the view or does it really just represent each item that is displayed on the screen.
How can I understand the concept more?
Let's see if this helps.
The general job of any list-style view would be to display a long chain of views, each representing a piece of data, most likely from a list.
Now imagine we consider the simplest implementation, where it draws all those views when it is created and allows you to scroll through them. This is obviously very inefficient for performance as a long list would require a lot of processing all at once.
RecyclerView aims to solve this and only creates enough views to fit on the screen and when scrolling, changes the content of those views seamlessly to reflect more data.
Now these views are created initially as empty blueprints and the RecyclerView wraps them inside something called a ViewHolder, which can not only hold the view but also pointers to different parts of the view, which saves doing even more work every time new data is displayed. Then initially and when scrolling, the RecyclerView 'binds' the relevant data to view holders.
The job of the adapter is to tie this process together and has three methods that require you to provide a concrete implementation:
getItemCount - expected to return how many items there are in the full dataset
onCreateViewHolder - create a view holder representing a generic row
onBindViewHolder - bind data to a view holder, therefore updating the rows content when given the view holder and the position in the dataset that should be bound
I am designing an android game, and I'm trying to use ListView. The list uses a BaseAdapter, and is filled with an ArrayList. When the user pressed the ok button, I scroll to the top of the list, and then iterate through each soldier in the list. I set the background drawable of one of the child views of the soldier so that it's a short animation that displays "hit" or "miss". I used a Handler.postDelayed() so that each animation for the one before it to finish.
The problem is that I cannot modify the views that are invisible. I will have up to 13 soldiers in my list at a time, but only a maximum of 5 can be displayed. So once I hit the sixth soldier, I get a null pointer exception from using ListView.getChildAt(soldierArrayIndex). My solution was to add smoothScrollTo(soldierArrayIndex) before the getChildAt() call so that it would become visible, but the problem persisted.
So my question isn't exactly "how to fix my code?". I'm more wondering if there is a way to disable the recycling that ListView does. The reason I'm using getChildAt() is because I need to use findViewById() on the view it returns, and then modify the view that was found by ID. However, if the view was never recycled, getChildAt() wouldn't return null.
Another idea I had was to just use a scroll view, enter 13 instances of the Soldier View that I created, and then set the ones I'm not currently using to "gone". My only problem is I don't know how to iterate through those views.
TL;DR: How to update views that are not currently visible in listview (they are currently "recycled")?
I think you are confusing Model and View here.
Adapter Views take an adapter that usually has access to the whole dataset. So even though your ListView will only render as many items as are visible on the screen, you should still be able to access your off-screen items by calling getItem(position) on your adapter.
It seems like you are calling getView directly on your adapter.
I’m suggesting you shouldn’t do this since this is not how AdapterViews and Adapters are designed. Instead, you need to indicate to your ListView when something in your model is changed (via notifyDataSetChanged) and leave it up to your ListView to call your adapter’s getView method on your behalf.
I imagine the sequence of events being something like this:
Update Soldier Object
Call notifyDataSetChanged on your adapter which will tell your ListView it needs to redraw stuff
ListView iterates through the visible Soldier items calling getView(int position, View convertView, ViewGroup parent) on each.
Your getView implementation in your adapter gets the Soldier at the specified position and determines what the View should look like at that point in time.
For "offscreen" Soldiers (ones that are not visible in the scroll area of the ListView,) there is no need for the ListView to render them, so it will not call getView on those positions.
However, if a Soldier that was offscreen is now scrolled into view, the ListView will call your adapter's getView method with that Soldier's position in your array.
Finally, if you want finer-grained control over how an AdapterView should update itself, you might consider RecyclerView as it allows you to notify changes on an item by item basis.
I understand the idea and usage of Viewholder pattern, but still I have one question:
Assume we have one TextView in the viewholder, and 10 items to display ("item0, item1....").
If I call findViewById once, as I understand I have one object of that TextView.
So at first call to getView I inflate the view, find the reference and set text "item0".
At second call I get same TextView and set text "item1" to the same TextView.
Why item 0 text doesn't change?
Is there any cloning in the background?
Is there any cloning in the background?
Android preallocate a number of views that are enough to fill the screen of the device where you are running the app ( a pool of views ), identical from the content perspective but differently from the reference perspective
Assuming that you implement your ViewHolder inside an adapter class and you use the holder in the getView() method, the only thing that is for sure, is that the TextView in your case , describe a slot of the parent structure (e.g. ListView). Once you have defined the slot in an xml, that is inflated from your adapter, there is no cloning or something like that.
According to Google Documentation the holder idea is described as :
Your code might call findViewById() frequently during the scrolling of
ListView, which can slow down performance. Even when the Adapter
returns an inflated view for recycling, you still need to look up the
elements and update them. A way around repeated use of findViewById()
is to use the "view holder" design pattern.
A ViewHolder object stores each of the component views inside the tag
field of the Layout, so you can immediately access them without the
need to look them up repeatedly. First, you need to create a class to
hold your exact set of views.
There is not cloning , only reusability of the view
I have one question for list view. At the time of creating list item in getView() method, which is a good option for list view. Creating views through coding or inflating view through xml. I am thinking about memory utilization & performance of list view.
Normally list item contain one product image with their name & 3 line description. Means one Image View & two text view.
Creating Views through code is usually not recommended and is justified only in cases when you can't deal without it. Using XML is always best practice so you should use this approach no matter where you're using your Views. Hope this helps.
You need to recycle views. Are you using a VieHolder?. My suggestion is use a view holder inflate using xml. ViewHolder will increase performance of ListView.
http://www.youtube.com/watch?v=wDBM6wVEO70. Check this link.
I've been working with a custom ExpandableList (see example picture below) where each item always has one child. This child consists of three parts. Each part has a header (red bars) and below that an Empty item OR a list of items. The length of this list will vary.
The first way I tried to do this is by adding a ViewStub below the empty item, which I inflated with a custom view, which also contained a ViewStub at the end which I inflated in turn for the next item, thus adding items recursively to create this sort of list of items. Sadly this resulted in StackOverflowError's when the list became too long (With short lists this worked perfectly).
So on my second try I tried using a ListView with a custom adapter instead. Sadly this list only used a small part of my screen and the rest of the items where occluded behind the header of the next part (This mini list looked scrollable as a second scrollbar appeared next to it, but it did not scroll. Not that I would consider this scrolling inside a scrolling list to be a good solution, but just wanted to mention this).
Can anyone tell me how I can tell this list of items to not be scrollable and take up all the room that it needs (does it know what size it is going to be when the child node is created??) or help me with a alternative solution to my problem? (Ooh, and I have considered putting an unholy amount of ViewStubs inside my layout, but that just seems idiotic and really bad practice. Correct me if I'm wrong)
If I understood you correctly, then why don't you just take an ExpandableList + Adapter that implements ExpandableListAdapter? It's a somewhat ugly approach but it works and isn't much hassle.
MyAdapter implements ExpandableListAdapter
#Override
public View getChildView(int groupPosition, int childPosition,boolean isLastChild, View convertView, ViewGroup parent) {
}
In this method you could simply figure out if the current childPosition would be one of the headers or one of the children and inflate the appropriate View from xml.