Listview Reusing Views When ... I Don't Want It To

ListView reusing views when ... I don't want it to

Android recycles list items for performance purposes. It is highly recommended to reuse them if you want your ListView to scroll smoothly.

For each list item the getView function of your adapter is called. There, is where you have to assign the values for the item the ListView is asking for.

Have a look at this example:

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;

if ( convertView == null )
{
/* There is no view at this position, we create a new one.
In this case by inflating an xml layout */
convertView = mInflater.inflate(R.layout.listview_item, null);
holder = new ViewHolder();
holder.toggleOk = (ToggleButton) convertView.findViewById( R.id.togOk );
convertView.setTag (holder);
}
else
{
/* We recycle a View that already exists */
holder = (ViewHolder) convertView.getTag ();
}

// Once we have a reference to the View we are returning, we set its values.

// Here is where you should set the ToggleButton value for this item!!!

holder.toggleOk.setChecked( mToggles.get( position ) );

return convertView;
}

Notice that ViewHolder is a static class we use to recycle that view. Its properties are the views your list item has. It is declared in your adapter.

static class ViewHolder{
ToggleButton toggleOk;
}

mToggles is declared as a private property in your adapter and set with a public method like this:

public void setToggleList( ArrayList<Boolean> list ){
this.mToggles = list;
notifyDataSetChanged();
}

Have a look at other custom ListView examples for more information.

Hope it helps.

Force Listview not to reuse views (Checkbox)

Ah, I see what the problem is.

Make a new class that extends SimpleCursorAdapter, say CheckboxSimpleCursorAdapter, and override getView as such:

public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
CheckBox checkBox = (CheckBox) view.findViewById(R.id.CheckBox1);
checkBox.setChecked(getIsThisListPositionChecked(position));
return view;
}

As you're not using a layout whose top-level View implements Checkable, you have to do everything yourself. That includes clearing state (in this case), as the default implementation re-used a View that was checked--as you correctly intuited.

Edit: use this new code, and implement a protected boolean getIsThisListPositionChecked(int position) method that returns whether or not the item is currently checked (or something like that). I hope I'm being clear enough--you need to figure out if the item should be checked according to your model, and then set that when you create the View.

Recycling views in a listview, worth it?

A couple of reasons to recycle views:

  • Object creation is relatively expensive. Every additional object that is created needs to be dealt with by the garbage collection system, and at least temporarily increases your memory footprint
  • This is more important for more complex views, but inflating and laying out the view objects can be expensive. Most often, you are only making minor changes to the view in getView that won't affect the layout (e.g, setting text) so you might be able to avoid the layout overhead.
  • Remember that Android is designed to be run in a resource constrained environment.
  • Finally, its already done for you, and it certianly doesn't hurt anything, so why not use it?

ListView is reusing views and I lose information after scrolling

In your getView method you have to check whether the row was already clicked, so your code should be something like this:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
try {
Station item = getItem(position);
View v = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(layoutResourceId, null);
} else {
v = convertView;
}

TextView stationName = (TextView) v.findViewById(R.id.stationName);
stationName.setText(item.getStationName());

if (head == position) { //first click
v.setBackgroundColor(Color.GREEN);
} else if (terminal == position){ //second click
terminal = position;
v.setBackgroundColor(Color.RED);
} else {
v.setBackgroundColor(Color.WHITE);
}

ImageView line_image = (ImageView) v.findViewById(R.id.line);

line_image.setBackgroundColor(Color.parseColor(setLineColor(line.getLineName())));
line_image.setImageResource(R.drawable.ic_line_simple);

return v;
} catch (Exception ex) {
Log.e(LOG_TAG, "error", ex);
return null;
}
}

And the code of the setOnItemClickListener:

lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {

station = (Station)parent.getItemAtPosition(position);
if (head ==-1) { //first click
head = position;
myAdapter.setHead(head);
view.setBackgroundColor(Color.GREEN);
}
else if (terminal ==-1){ //second click
terminal = position;
myAdapter.setTerminal(terminal);
view.setBackgroundColor(Color.RED);
}
}

The reason for this behaviour is that when row disappears and comes back, the getView method will be called again. So in your getView method you have to take into account the state of the row. Therefore you need the state variables head and terminal in your adapter.

Prevent re-use of some views in ListView (custom cursor-adapter)

If you want to prevent the reuse of view at a certain position the do the following

@Override
public int getItemViewType(int position) {
mCursor.moveToPosition(position);
if (mCursor.getLong(mCursor.getColumnIndex(TodoTable.COLUMN_ID)) == ts.getRunning()){
return IGNORE_ITEM_VIEW_TYPE;
}
return super.getItemViewType(position);
}

If you return IGNORE_ITEM_VIEW_TYPE in getItemViewType(position) then the view at that position will not be recycled. More info about IGNORE_ITEM_VIEW_TYPE is here

How to clear the views which are held in the ListView's RecycleBin?

If I remember correctly a call to invalidate on a ListView widget will empty the cache of Views which are currently stored. I would advise against emptying the cache of views of a ListView because of the potential performance issues.

If you're not going to use the convertView item then you'll end up with having to build the row view each time(resulting in a lot of objects constructed each time the user scrolls) + the extra memory occupied by the recycled views from the RecycleBin which will never be used anyway.

Prevent the adapter from recycling views on scroll ( Edit do not ever do this.)


Prevent the adapter from recycling views on scroll

Just don't use the convertView param passed to getView() and always return a freshly generated View.

However, this is a bad solution in terms of performance. Instead, your goal should not be to prevent recycling but to recycle correcltly: Your getView() should reset the convertView to it's pristine state.

So, if there's a change that some of your Button's properties are changed from their non-default values, reset them back to defaults in getView().

ListView: Prevent a view from recycling

For pre Jellybean, I think you can just use setRecyclerListener on ListView and when RecyclerListener#onMovedToScrapHeap(View view) is called, clear the animation on the view who has been recycled and directly do the final job which was supposed to be done when animation ends.

The code inside onMovedToScrapHeap(View view) depends on how you implement the animation, e.g. you can call View#clearAnimation() if you previously used View#startAnimation to start animation.

listview recycling strange empty space

So, essentially your problem is with recycling the views. When your views get inflated from XML they look at the images and decide how tall to be. This works fine because the inflater can cause the height to be "wrap_content" aka however tall the tallest child component is - this is dynamic.

However, when your getview recycles the old views it (for some reason) isn't updating the height of the rows, and neither are you (which you never had to do before because the inflater handles it automatically from "wrap_content"). It would appear from what you described that you just get a row with whatever the height of the view happened to be set at previously (this previous value was set by the inflater automatically).

Your two solutions are:

  1. Don't recycle the views - just inflate a new one every time. You will see a performance hit which will probably only be noticeable at lists with ~50 elements or more. If it is a small list this may be the easiest (code-wise) option, although there it will still operate slightly slower.
  2. Keep recycling the views - if you pick this you will have to set the height of the row (or text view, whatever is determining it) yourself based on the image heights. This is doable but will require more work. I'd suggest
  3. secret option 3 - normalize the image heights - If you would a.) like to continue recycling views but b.) don't want to write the code to set the row heights manually and c.) resizing the images wouldn't kill your app, you could use the setMaxHeight(int) method to make sure all of your images are the same height (or use a method like this to resize the images to a predetermined size)

Note: I am pretty sure (almost certain) that, normally, if height is set to "wrap_content" it would wrap to the new content size (since it normally updates the size when the content is updated). I couldn't say why this is not happening for you - it may be that something inside the "wrap_content" has its size set to a concrete value and thus isn't resizing, so the "wrap_content" isn't changing even though there is empty space.



Related Topics



Leave a reply



Submit