caiocpricci2
caiocpricci2

Reputation: 7798

Why listviews with custom adapters sometimes behave strangely?

I've ran into this issue several times and it's easy to find many similar issues with a quick search. Sometimes a dynamic change on a ListView row layout affects other rows too, sometimes it does not.

As a solution to this problem I usually keep track of the whole set of items in the list and every time I have a change, I reset all items on the list.

Ex: If i have a list with TextViews and checkboxes, i have to keep an array of booleans indicating the state of each checkbox, and on my getView() override I reset all the checkboxes based on that array.

Is this expected? I can find several questions about this issue, and all the solutions seems to be similar to mine.

Now i'm facing an issue where I need to keep track of several items at once (Background, checkbox and text) in a really long list. I'm wondering if there is any other approach for this problem.

Upvotes: 1

Views: 546

Answers (2)

MattDavis
MattDavis

Reputation: 5178

This is the expected behavior of ListViews in Android. Your method of having underlying data used to populate the Views in the List is right on track.

Android uses a technique called View Recycling when creating a ListView because inflating views, compared to populating them with data, is an intensive operation. Android keeps the inflation to a minimum (with the help of the programmer) by only creating the Views that the user sees on the screen. When the user scrolls up the List, Views that move off the screen are placed in a pool, to be reused by the new items that are about to be shown. A View from this pool is passed to getView as the second argument. This view will retain the exact state as when it was popped off of the list, so it's up to the getView method wipe clean any of the old data's state, and repopulate it based on the new state from your underlying data. Here's an example of the structure an implementation of getView() should have:

@Override
public View getView (int position, View convertView, ViewGroup parent)
{
    //The programmer has two responsibilities in this method.

    //The first is to inflate the view, if it hasn't been
    //convertView will contain a view from the aforementioned pool, 
    //    but when first creating the list, the pool is empty and convertView will be null
    if(convertView == null)
    {
        //If convertView is null, inflate it, something like this....
        convertView = layoutInflator.inflate(R.layout.mylistview, null);
    } 

    //If convertView was not null, it has previously been inflated by this method

    //Now, you can use the position argument to find this view's data and populate it
    //It is important in this step to reset the entire state of the view.
    //If the view that was popped off the list had a checked CheckBox, 
    //    it will still be selected, EditTexts will not be cleared, etc.

    //Finally, once that configuration is done, return convertView
    return convertView;
}

There are also many other methods from the Adaptor class that help manage your lists and allow you to do clever things that leverage view recycling, such as getItem() for managing your underlying data, and getViewType() and getViewTypeCount() for lists with multiple view types, but the above is the basic technique and the minimum you need for your Views to run smoothly.

It sounds like you were on the right track, I hope this helps answer some of your questions. Please let me know if anything was unclear of you'd like more information.

Upvotes: 2

Sababado
Sababado

Reputation: 2532

You're doing it the right way. In Android you should be keeping track of the state of each individual list item. When getView is called you have the option to reuse or create a new view for the row. By keeping track of the state of an item (text, is checked, background etc) you can easily reset a view that already exists. Keeping track of the detailed state will help you make sure no other rows are affected by one row's change

When you call adapter.notifyDataSetChanged() the only items that are redrawn are the ones on screen (or very close to being on screen).

Upvotes: 1

Related Questions