When a list is shown and scrolled around, ListAdapter.getView() either creates a new list item view, or retools an existing view passed to it (as "convertView) to display new content.
I'm wondering, if there's a way to get all the views created by getView()? E.g. if the list item views needs to free some resource when the activity is destroyed, it would be nice to go through all list item views and tell them to release the resource.
Well, it's not as simple as iterating through the ListView using ListView#getChildAt(int) - the ListView is an AbsListView subclass - and AbsListView implements the RecycleBin used to hold Views that will be reused.
Before a View is placed in the RecycleBin its detached from its parent (the list view) using ViewGroup#detachViewFromParent(View), thus rendering it unaccessible using getChildAt(int).
What you should implement is the RecyclerListener interface that will notify you when a View is moved to the RecycleBin.
Since ListView is a ViewGroup, you should be able to iterate on child views like this:
for (int i=0; i < myListView.getChildCount(); i++) {
View v = myListView.getChildAt(i);
//Check to see if this view has the resource, then release it
}
Something similar was needed in this question: Android: Access child views from a ListView
It is java. You can't order the application or instance to be really destroyed so as to free resources. With finalize() you can only rise the probability that java machine will do it.
Unless your adapter is used outside of this Activity (in which case, you don't really know that you no longer need these views right?), your only reference(s) to this adapter is within the Activity. That means that if your Activity is garbage collected your adapter will be garbage collected, which means that your views will get collected. I wouldn't bother trying to optimize this unless you have a measurable problem with this behavior.
Related
How do I get a reference to the ViewHolder of the item in the RecyclerView that has been marked dirty? RecyclerView.OnChildAttachStateChangeListener just gives a reference to the view.
I want to know when the view is dirty and not recycled. See the official definition of the two below:
Recycle (view): A view previously used to display data for a specific
adapter position may be placed in a cache for later reuse to display
the same type of data again later. This can drastically improve
performance by skipping initial layout inflation or construction.
Scrap (view): A child view that has entered into a temporarily
detached state during layout. Scrap views may be reused without
becoming fully detached from the parent RecyclerView, either
unmodified if no rebinding is required or modified by the adapter if
the view was considered dirty.
Dirty (view): A child view that must be rebound by the adapter before
being displayed.
By dirty, do you mean recycled? If so then the following listener can help you.
See RecyclerListener
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'm trying to show an animation with all Views that I've created from an adapter. When I scroll down, it shows the animation correctly, but when I scroll up, I see these Views recreate themselves and show the animation again. Then, when I scroll down, it happens again.
My assumption is that the mechanism of creating a View from an adapter is to load the View into memory; just the group of Views which are on screen right now (but above and below views are not loaded into memory). These will be loaded again when I scroll to these views, right?
Is there any way to fix this problem?
PS: Sorry for my English, I hope you understand my problem.
My assumption is that the mechanism of creating a View from an adapter
is to load the View into memory; just the group of Views which are on
screen right now (but above and below views are not loaded into
memory)
That's somewhat correct: a ListView will not try to visualize any data that isn't (at least partially) visible. It also 'recycles' views, meaning that any view that isn't currently used to present data to the user and is of the same 'type' as the next data item, may get reused.
Hence you shouldn't rely on persisting data with or make any assumptions about the existence of particular views. In stead, use something that's separate from the views; e.g. the dataset you're visualizing.
Quite often, you'll supply a list of POJOs to a BaseAdapter or ArrayAdapter. You could simply add a boolean to the POJO indicating whether it should animate or not, and change that whenever the animation for that particular item finishes. Alternatively, you could keep track of these values in a separate collection (which is probably the more straightforward approach if you're dealing with a Cursor as data source rather than POJOs).
I am implementing a ListView using custom adapters. To improve the performance I am returning the newly inflated View if convertView is null, else returning the recycled View.
Now in this process I used the ViewHolder pattern and used the setTag() method of the View. Initially I thought this is some kind of bookmarking but I've not completely understood the use of it. I've checked different blog post where they just used this in the code.
Can someone please explain me the use of the setTag() method?
Basically you can store any kind of object as tag (and cast it back when calling getTag). This can be a simple ID or some complex data. It's some information which you associate with this view.
In the case of lists and the view holder pattern it's a simple object which contains references to views of the tagged view (group). So you don't have to call findViewById every time when you're updating the content of the view. It's just an performance optimization.
Can we store data of list item in the view tag?
No. Because of view recycling you have (e.g.) 10 views which are reused for 1000 list items. Storing data in the tag makes no sense here. It's better to use an custom data object to store the list item state (probably the same array which contains the displayed data) or you persist it right away on list item change.
See also setTag documentation.
I tried to add these views to list view using this kind of factory but everytime I try and add the view to a ListActivity, it comes up with nothing. What am I doing wrong? I set my list views like so:
List<View> views = new ArrayList<View>();
for(int x =0;x<tagg_views.size();x++){
lv.addHeaderView(views.get(x));
}
It looks like you are trying to add x number of headers to your ListView. That doesn't make sense.
A ListView should contain x number of copies of the same view, with different information on each line.
Hello ListView gives a good example of the correct usage of a ListView.
Why are you adding the Views to the list yourself? I would highly recommend using any kind of apropriate Adapter for the List. The adapter will handle the creating and recycling of views while the user is scrolling etc. If you use an Adapter it is discouraged to save references to the view yourself like you are doing it in the views list.
The addHeaderView method you are using is made to one single header to the list that always will appear on the top of the list. This means calling it in a loop will not have a reasonable result.
Look into the helloListView example Mayra mentions to understand how a list in android is working. To see how a custom listadapter works have a look at this tutorial looks promising despite the bad code formatting.
A ListView is linked with and Adapter. The Adapter is responsible for the data displayed in the ListView. Take into account that internally ListView creates a pool of itmes (or a pool for each type of item that can be displayed in your case).
For this purpose your adapter needs to implement the following methods:
int getItemViewType(int position): Get the type of View that will be created by getView(int, View, ViewGroup) for the specified item. So you need to identify you types.
int getViewTypeCount(): Returns the number of types of Views that will be created by getView(int, View, ViewGroup). This is used to create a pool for each type of item.