滚动时会重复ArrayAdapter自定义视图状态

时间:2011-09-24 17:05:36

标签: android

我有一个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);的行上设置了一个断点,它只会在我检查的原始项目在屏幕上时触及该点,而不会显示任何其他显示为已检查的项目。

为什么后面的项目会显示为已选中或我可以尝试修复它的任何想法?

1 个答案:

答案 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;
            }
        }