Search code examples
androidlistviewandroid-listviewandroid-scrollview

Android Listview: Updating invisible views or disabling recycling


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")?


Solution

  • 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:

    1. Update Soldier Object
    2. Call notifyDataSetChanged on your adapter which will tell your ListView it needs to redraw stuff
    3. ListView iterates through the visible Soldier items calling getView(int position, View convertView, ViewGroup parent) on each.
    4. 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.