RecyclerView/ListView嵌套CheckBox选中状态错乱解决方案

ListView复用的原理:ListView中的每一个Item显示都需要Adapter调用一次getView的方法,这个方法会传入一个convertView的参数,返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)
Android提供了一个叫做Recycler(反复循环器)的构件,就是当ListView的Item从上方滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View,所以说如果能重用这个convertView,就会大大改善性能。
如果一个屏幕最多显示7个Item,当Item1滑出屏幕,此时Item1 的View被添加进Recycler中,相应的在下部要产生一个Item8,这时调用getView方法,convertView参数就是Item1 的View。 其原理图如下:
这里写图片描述
ListView的复用虽然大大提升了性能,但是却也带来很多问题。比如在加载图片时,由于下边的item复用了上边的item,造成下边item刚加载出来时显示的还是上边被复用的item的图片,等到这个新的item加载图片完毕时才会正常显示,这就是convert view复用造成listview图片加载错乱的问题。
与上边问题相似,在listview的item中存在CheckBox时也会由于复用convert view导致CheckBox的选中状态错乱,本片内容将解决由于复用导致CheckBox选中状态错乱的问题。
先看下存在问题的效果图
这里写图片描述

上图中只选中了北京市和天津市,当下滑ListView时发现下边的河南省和山东省也被选中了,再往下滑四川省和台湾省也被选中。其实可以发现一个规律,每一屏都会有两个条目被选中,其实这两个被选中的条目就是因为复用了第一屏的两个被选中的条目所导致的。
先看下ListView没有优化前的代码:

public class MainActivity extends AppCompatActivity {
    private ListView mListView;
    private List<String> mStringList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mStringList=new ArrayList<>();
        mStringList.add("北京市");mStringList.add("天津市");mStringList.add("上海市");mStringList.add("重庆市");
        mStringList.add("河北省");mStringList.add("山西省");mStringList.add("辽宁省");mStringList.add("河南省");
        mStringList.add("山东省");mStringList.add("湖北省");mStringList.add("湖南省");mStringList.add("江西省");
        mStringList.add("福建省");mStringList.add("陕西省");mStringList.add("四川省");mStringList.add("台湾省");
        mListView= (ListView) findViewById(R.id.lv_main);
        MyAdapter adapter=new MyAdapter(this,mStringList);
        mListView.setAdapter(adapter);

    }
}

MainActivity中的代码为ListView适配数据和适配Adapter,不作过多解释。

/**
 * Created by zhpan on 2016/9/24.
 */

public class MyAdapter extends BaseAdapter {

    List<String> mStringList;
    Context mContext;

    public MyAdapter(Context context, List<String> stringList) {
        mStringList = stringList;
        mContext=context;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        MyViewHolder holder;
        if(convertView==null){
            convertView=View.inflate(mContext,R.layout.item,null);
            holder=new MyViewHolder();
            holder.mTextView= (TextView) convertView.findViewById(R.id.tv_item);
            holder.mCheckBox= (CheckBox) convertView.findViewById(R.id.cb_item);
            convertView.setTag(holder);
        }else {
            holder= (MyViewHolder) convertView.getTag();
        }

        holder.mTextView.setText(mStringList.get(position));

        return convertView;
    }

    @Override
    public int getCount() {
        return mStringList.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    public static class  MyViewHolder {
        TextView mTextView;
        CheckBox mCheckBox;
    }
}

在Adapter中只是复用了convertView,没有对CheckBox做任何处理,那么这样写的代码是存在上图中的CheckBox选中状态错乱问题的。为了解决这个问题我们需要对CheckBox的选中状态做下保存,可以在Adapter中声明一个Map集合用来保存被选中的CheckBox。修改后代码如下:

/**
 * Created by zhpan on 2016/9/24.
 */

public class MyAdapter extends BaseAdapter {

    List<String> mStringList;
    Context mContext;
    private Map<Integer,Boolean> map=new HashMap<>();// 存放已被选中的CheckBox

    public MyAdapter(Context context, List<String> stringList) {
        mStringList = stringList;
        mContext=context;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        MyViewHolder holder;
        if(convertView==null){
            convertView=View.inflate(mContext,R.layout.item,null);
            holder=new MyViewHolder();
            holder.mTextView= (TextView) convertView.findViewById(R.id.tv_item);
            holder.mCheckBox= (CheckBox) convertView.findViewById(R.id.cb_item);
            convertView.setTag(holder);
        }else {
            holder= (MyViewHolder) convertView.getTag();
        }

        holder.mTextView.setText(mStringList.get(position));
        holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked==true){
                    map.put(position,true);
                }else {
                    map.remove(position);
                }
            }
        });

        if(map!=null&&map.containsKey(position)){
            holder.mCheckBox.setChecked(true);
        }else {
            holder.mCheckBox.setChecked(false);
        }

        return convertView;
    }

    @Override
    public int getCount() {
        return mStringList.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    public static class  MyViewHolder {
        TextView mTextView;
        CheckBox mCheckBox;
    }
}

针对这个问题我们子Adapter中加入了一个Map集合,其中Map的key用来存储条被选中的checkbox的position,value用来存储checkbox被选中。代码中还添加了checkbox的监听事件,在监听事件中判断点击的checkbox是否被选中,如果被选中了则将position添加到集合,并设置状态未true,否则就将该checkbox从集合中移除。然后通过if语句判断集合中是否存在该checkbox,如果存在则证明是被选中的,遂将该checkbox设置为选中状态setChecked(true),否则证明checkbox没有选中则设置setChecked(false)。这样就解决了checkbox选中状态错乱的问题。
看下优化后的效果图
这里写图片描述

可以看到优化后CheckBox的选中状态不会再出现错乱问题了。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,当 ListView 滚动时,会导致 CheckBox选中状态消失。这是因为 ListView 会对其子项进行复用,从而导致 CheckBox状态丢失。 为了解决这个问题,可以在 Adapter 中使用 ViewHolder 模式来缓存 ListView 的子项,从而避免 CheckBox 状态的丢失。具体来说,ViewHolder 模式可以通过以下步骤来实现: 1. 在 Adapter 中定义一个 ViewHolder 类,用于缓存 ListView 的子项: ```java private static class ViewHolder { TextView nameTextView; CheckBox checkBox; } ``` 2. 在 getView 方法中使用 ViewHolder 对象来缓存 ListView 的子项: ```java @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); viewHolder = new ViewHolder(); viewHolder.nameTextView = convertView.findViewById(R.id.name_text_view); viewHolder.checkBox = convertView.findViewById(R.id.checkbox); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } // 设置子项的文本和 CheckBox 状态 MyItem item = getItem(position); viewHolder.nameTextView.setText(item.getName()); viewHolder.checkBox.setChecked(item.isChecked()); // 返回子项视图 return convertView; } ``` 在这个例子中,我们首先通过 convertView 参数来获取 ListView 的子项视图。如果 convertView 为 null,说明当前子项没有被缓存,我们就需要创建一个新的 ViewHolder 对象,并使用 setTag 方法将其绑定到 convertView 上。如果 convertView 不为 null,说明当前子项已经被缓存了,我们就可以直接从 convertView 中获取 ViewHolder 对象。 在 getView 方法中,我们通过 ViewHolder 对象来设置子项的文本和 CheckBox 状态。由于 CheckBox状态已经被缓存到 ViewHolder 中,因此即使 ListView 滚动,CheckBox状态也不会丢失。 最后,我们返回子项视图 convertView。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值