我有一个ListView,其中填充了自定义视图项。自定义视图由图标,标签和复选框组成。当我第一次创建列表时,一切都按原样显示。如果我向下滚动列表,图标和标签继续在列表的下方继续正确,但复选框状态开始混淆,显示除我选择的项目之外的其他项目。
示例:我的列表首先没有设置为已选中任何项目的复选框。我在屏幕上看到10个项目。我切换了第10项的复选框。它会适当更新。如果我向下滚动列表,我会发现项目20,项目30等的复选框从已经切换的复选框开始,即使它们从未可见以进行交互。如果我反复滚动,则会检查越来越多的不可识别图案中的项目。
在我的活动中列出设置:
@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;
}
}
列出项目布局:
<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>
另外值得注意的是,如果我在我调用check.setChecked(true);
的行上设置了一个断点,它只会在我检查的原始项目在屏幕上时触及该点,而不会显示任何其他显示为已检查的项目。
为什么后面的项目会显示为已选中或我可以尝试修复它的任何想法?
答案 0 :(得分:6)
这是由View回收引起的。调用getView时,应该完全刷新View的状态。虽然几乎所有东西都令人耳目一新,但你已经忘记了一件小事。
假设列表在屏幕上显示5个项目,并检查第二个项目。用户然后向下滚动另外5个项目 - 但是由于视图回收,它们实际上只是与用户滚动之前相同的5个视图,因此即使它不应该是屏幕上的其中一个项目仍将被检查,因为在上面的代码中,如果没有匹配的包你没有设置复选框未选中(所以它会保持检查),你假设它已经取消选中(由于查看回收可能不是)。
修复很简单:只需将复选框设置为在逻辑之前取消选中,以检查是否需要检查:
// 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;
}
}