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.
Related
My problem is similar to ListView getChildAt returning null for visible children, but despite searching I cannot find a solution.
I have a ListView with a Scroll. The ListView has 10 items, 7 of which are visible and 3 are hidden by scroll. I also have an external method (out of adapter) that must get all of the children from this ListView (e.g. using getChildAt()).
I need all 10 of the items, but the last 3 are null objects. I've tried code like the following:
getListView().smoothScrollToPosition();
But this doesn't work.
I think that I don't need to post the rest of my code, as the description says everything?
As you have already seen you can't get all the child row views from a ListView simply because a ListView holds only the views for the visible rows(plus some recycled rows but you can't reach those). The correct way to do what you want is to store whatever data in the adapter's data and retrieve it from there.
But the ListView doesn't keep the current values from RadioGroup in
running time.
I've seen that you have some problems with this so I've adapted some old code to build a basic example, code that you can find here.
I don't think so you need to add scroll view for a listView. Scroll automatically works on ListView. Try your application without adding scroll view and I'm sure it'll work as you needed.
The reason those children are null it's because they really do not exist and they will never exist, if only 7 children are on the screen at one time, the system will only create 7 and re-use by passing the convertView back to the adapter getView() method.
If you want to grab information regarding your whole dataset you should search on the dataset itself, instead of the views on the screen. E.g. if it's an ArrayAdapter, loop the array; if it's a CursorAdapter, loop the cursor; etc.
The non-visible children of a listView don't actually exist. When they become visible, one of the redundant views is recycled or a new view is generated. So you can't actually access all the views. Why do you want to? Whatever changes you want to make should be made to the data that populates the views rather than the views themselves.
There are a few point that you need to take care of:
1. List view provides inbuilt scroll functionality, So don't use Scroll view. It will only mess up things.
2. List view doesn't contain ALL the children. When you scroll it, it creates only visible items on run time.
3. If you want to get all the children altogether, Better keep an ArrayList of the child objects that your list has. You can add or remove children to this ArrayList as per requirement.
I have a ListView that displays a list of items, each of which has an icon and a few bits of text.
I am making use of the "convertView" parameter of the ListAdapter.getView() method, altering an existing view rather than creating a new one when the parameter is non-null.
I had expected the ListView to recycle old views only after they had scrolled out of the visible viewport, but this appears not to be the case. It appears that the ListView is providing the same object in the "convertView" parameter on each invocation of ListAdapter.getView(). The single view is rendered to the screen, and then sent in again on the next call to getView().
This poses a significant problem for me, as I wish to modify previously rendered views. I have a background thread retrieving the icons for items, which takes "considerable" time and would be an unacceptable user interface burden to place within the ListAdapter.getView() view rendering code.
Is there any means to make the ListView not reuse views which are currently displayed on the screen? I'd like to realize the performance/efficiency gains of view reuse and be able to load the icons in a background thread.
I think you're having a similar problem I had for a while: Old items visible a while, in listview or gridview, when recycling
You have to "reset" the recycled items of the list at the beginning of getView(). Set them back to progress bar, or invisible, whatever the initial state is. Until they fetch the correct data.
I've found the issue is that Android is creating an additional, never-rendered temporary view for use in layout/measurement under certain conditions. My assumption that this view was rendered to the screen was not correct.
I am going through this tutorial:
http://docs.xamarin.com/android/tutorials/User_Interface/grid_view
I am an experienced programmer but newer to Android so that is probably the issue.
I understand what is happening but I do not see/understand how the GetView() in the ImageAdapter is being called for each individual resource in thumbs[].
I debugged and stepped through the code but still do not see what/how it is calling the GetView() function to generate a view for each thumb and how it is incrementing the count/position counter that calls GetView().
Any help would be appreciated.
The GridView/ListView/ExpandableListView/Gallery classes in Android provide important optimizations that allow you to display long lists or grids of content without drawing the entire list at the same time.
These AdapterView classes have an underlying adapter that provides the AdapterView with a view for any given cell. So even though a ListView may contain 100 items, if only 6 items are visible in the view at a time, then the AdapterView may only draw ~8 items.
The getView() method in your Adapter will be called by the AdapterView when the user scrolls to the position of that item in the AdapterView. The count should not be being incremented, unless the number of "cells" you intend to be visible in the AdapterView is increasing.
Generally in your implementation of getView(), you will want to recycle the existing view if possible and only update the text/images with the data from your model.
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.
I know it's somehow possible to make a ListView that loads more data when the user has reached the bottom of the list. However, I'm working with a ScrollView, which I have a LinearLayout in, and these two components works great with the scrolling and so. But I don't know how I'm supposed to do so it gets an infinite scroll.
I suppose I need to add something that reads what is shown of the LinearLayout on the screen, and when it calculates that it is the bottom of the LinearLayout that is being shown (by using the current position and the height of the View), it triggers an event.
But as I said, I don't know how to accomplish this. So, if anyone can give me some help I would be very grateful.
EDIT: I found this post here on StackOverflow How to trigger an event when scrollView reach the bottom with Android?, but I don't know what to do with the answer:
Given the requirements, you'll likely be extending BaseAdapter (as opposed to CursorAdapter which utilizes a different mechanism).
Here's a snippet for that:
public View getView(int position, View convertView, ViewGroup parent) {
if (position == backingLinkedList.size()) {
//get more items and add them to the backingLinkedList in a background thread
notifyDataSetChanged();
}
}
If you can trigger an event when you reach the bottom of a scrollview, You could try creating your own type of view to hold your data. When you reach the bottom of the view load the next data into your view class and add it into the layout of the scroll view.
So basically create a class that holds a section of what your showing. Add the first one into the scrollview. When you hit the bottom create another view holding your next data and use scrollviews_layout.addview(view_holding_the_new_data)
I know this was solved a long time ago, but I wanted to share my solution for future infinite scrollers.
I used the onScroll method within the OnScrollListener to trigger a background thread to grab more data and adapter.notifyDataSetChanged to notify the ListView of more data to load and create an infinite scroll. For some reason, onScrollStateChanged wasn't triggering, but onScroll did what I needed so I didn't need to bother.
For the longest time I didn't understand how to keep the ListView from reloading and placing me back at the top of the list, but then I realized I only need to initialize a new adapter and call setListAdapter once, and every time after that, only call notifyDataSetChanged. Before, I was also calling setListAdapter, thereby placing me back at the top.