ArrayAdapter custom view state repeats when scrolling

I have a ListView that is populated with custom view elements. A custom view consists of an icon, a label, and a check box. When I first create a list, everything will look as it should. If I scroll down the list, the icons and labels continue to be correct further down the list, but the states of the checkbox begin to mix, showing other elements that were checked, except for those that I selected.

Example. My list starts with the absence of checkboxes set as checked for any items. I see 10 elements on the screen. I switch the check box to item 10. It is updated accordingly. If I scroll down the list, I find that the checkbox for item 20, item 30, etc. It starts with the fact that the checkbox is already enabled, although they were never visible for interaction. If I scroll back and forth repeatedly, more and more elements in an unidentified template appear as marked.

List customization in my activity:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.default_list); profile = (Profile) i.getParcelableExtra("profile"); ArrayList<Application> apps = new ApplicationListRetriever(this).getApplications(true); adapter = new ApplicationsAdapter(this, R.layout.application_list_item, apps, getPackageManager(), profile); setListAdapter(adapter); } 

ApplicationsAdapter:

 public class ApplicationsAdapter extends ArrayAdapter<Application> { private ArrayList<Application> items; private PackageManager pm; private Profile profile; private ArrayList<ApplicationListener> listeners = new ArrayList<ApplicationListener>(); public ApplicationsAdapter(Context context, int textViewResourceId, ArrayList<Application> objects, PackageManager pm, Profile profile) { super(context, textViewResourceId, objects); this.pm = pm; items = objects; this.profile = profile; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater li = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = li.inflate(R.layout.application_list_item, null); } final Application info = items.get(position); if (info != null) { TextView text = (TextView) v.findViewById(R.id.label); CheckBox check = (CheckBox) v.findViewById(R.id.check); ImageView img = (ImageView) v.findViewById(R.id.application_icon); //see if the app already is associated and mark checkbox accordingly for (Application app : profile.getApps()) { if (info.getPackageName().equals(app.getPackageName())) { check.setChecked(true); break; } } check.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { for (ApplicationListener listener : listeners) { listener.applicationReceived(info, isChecked); } } }); try { text.setText(info.getName()); } catch (Exception ex) { Log.e("ApplicationsAdapter", "Label could not be set on adapter item", ex); } if (img != null) { try { img.setImageDrawable(pm.getApplicationIcon(info.getPackageName())); } catch (NameNotFoundException e) { e.printStackTrace(); } } } return v; } } 

List Layout:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/application_icon" android:layout_centerVertical="true" android:layout_alignParentLeft="true" android:layout_width="36dp" android:layout_height="36dp" android:paddingRight="3dp" android:adjustViewBounds="true" /> <TextView android:layout_width="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@+id/application_icon" android:layout_height="wrap_content" android:id="@+id/label" android:text="Application Name" /> <CheckBox android:id="@+id/check" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" /> </RelativeLayout> 

It's also worth noting that if I set a breakpoint on the line where I call check.setChecked(true); , it only gets to this point if the source element that I checked is on the screen, never for any other elements that appear as marked.

Any ideas why later elements will appear as verified or what can I try to fix?

+4
source share
1 answer

This is caused by the recycling of View. You must completely update the state of your view when calling getView. Although you refresh almost everything, you forgot one little thing.

Assume that 5 items are displayed in the list and the second item is marked. Then the user scrolls another 5 elements - however, due to viewing the recycling, they really are the same 5 types as before the user scrolls, so one of the elements on the screen will still be checked, even if it should not be, because in your In the code above, if the package is not matched, you do not select the check box to clear the check box (so that it remains installed), you assume that it is not already installed (which, due to the need to reuse the View, cannot be).

The fix is ​​simple: just check the box to clear the check box in front of your logic to check if it needs to be checked:

  // Do not assume the checkbox is unchecked to begin with, it might not be // if this view was recycled, so force it to be unchecked by default and only // check it if needed. check.setChecked(false); for (Application app : profile.getApps()) { if (info.getPackageName().equals(app.getPackageName())) { check.setChecked(true); break; } } 
+6
source

All Articles