RecyclerView+CheckBox的使用

1.需求前言

需要做一个勾选列表,提供不同的选项给用户进行勾选,并将结果保存在本地
效果图:


image.png

2.实现思路

采用Recycleview+Checkbox的方式 每个recycleview的每个item就是一个选项,可以勾选,再次点击取消勾选
进入勾选页面时,先读取本地的设置,每个item设置是否已经勾选
难点:recycleview的holder复用导致勾选混乱

3.实现

3.1 布局

这里采用极简的方式快速实现,所以布局非常简单,xml文件就不贴出来了
这是item的布局,我使用了RelativeLayout,但其实可以使用LinearLayout
左边一个TextView 右边一个CheckBox


item.png

然后activity的布局就一个RecyclerView就可以

1
2
3
4
5
6
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView_product_type"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/item_preference_product"
        />

3.2 Activity和Adapter的代码实现

我尽量注释清楚,以下代码是从项目中摘取出来的代码,保留灵魂,舍弃整体。不能直接使用,但是如果读懂了代码的话,是可以很轻松的自己实现的

3.2.1 Adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class ProdectTypeAdapter extends DefaultAdapter<ProductTypeBean> {

    /**Set<String>是用于保存已经勾选的商品列表
     * SharedPreferences能保存的列表只能是Set<String>类型
     * 所以这里采用Set<String>来记录勾选的商品列表
     * SharedPreferences需要保存更复杂的bean对象的话可以转换为JSON的字符串来保存
     * */
    //mProductPreferences之前已经勾选的商品列表 在打开界面时我们需要默认勾选上
    Set<String> mProductPreferences;

    //ischoose 商品名称以及对应是否已经勾选
    Map<String, Boolean> ischoose = new HashMap<>();

    public ProdectTypeAdapter(List<ProductTypeBean> infos, Set<String> mProductPreferences) {
        super(infos);
        this.mProductPreferences = mProductPreferences;
    }

   
    @Override
    public BaseHolder<ProductTypeBean> getHolder(View v, int viewType) {
        //这里根据所有的商品名称和mProductPreferences作比较
        //如果mProductPreferences存在则已勾选 否则不勾选 比较结果存入ischoose
        for (ProductTypeBean productTypeBean : getInfos()) {
            if (mProductPreferences.contains(productTypeBean.getProductTypeId())) {
                ischoose.put(productTypeBean.getProductTypeId(), true);
            } else {
                ischoose.put(productTypeBean.getProductTypeId(), false);
            }

        }
        ProdectTypeHolder holder = new ProdectTypeHolder(v, ischoose);
       
        //!!!这句代码非常关键 这里禁止holder的复用 直接杜绝了勾选混淆的可能性
        //代价是如果选项特别巨大,会导致recycleview滑动卡顿 但一般是不会有太多选项的
        holder.setIsRecyclable(false);
       
        return holder;
    }

    @Override
    public int getLayoutId(int viewType) {
        return R.layout.item_preference_product;
    }
//holder中填充每个item的数据
    class ProdectTypeHolder extends BaseHolder<ProductTypeBean> {

        @BindView(R.id.tv_product_name)
        TextView productName;//显示每个商品名称
        @BindView(R.id.checkbox_product)
        CheckBox checkbox;//每个商品的选择框
        Map<String, Boolean> ischoose;


        public ProdectTypeHolder(View itemView, Map<String, Boolean> ischoose) {
            super(itemView);
            this.ischoose = ischoose;
        }
    //setData填充每个item的数据
        @Override
        public void setData(ProductTypeBean data, int position) {
            //显示商品名称
            productName.setText(data.getVatRatio() + "%" + itemView.getContext().getString(data.getDescRes()));
            //刚开始启动 勾选框是否默认已勾选(根据ischoose)
            checkbox.setChecked(ischoose.get(data.getProductTypeId()));
            //我们不需要勾选框的监听 所以设置不可点击
            checkbox.setClickable(false);
        }
    }
}

3.2.2 Activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class ProductTypePreferenceSettingActivity extends BaseActivity {
    //SharedPreferences存储对象 用于将选择结果保存到本地内存
    @Inject
    SharedPreferences mSharedPreferences;

    @BindView(R.id.recyclerView_product_type)
    RecyclerView recyclerView;

    ProdectTypeAdapter mAdapter;//recycleview适配器
    LinearLayoutManager mManager;//recycleview的布局管理器 用于单独获取item中的checkbox对象
    List<ProductTypeBean> allProductType;//所有的可以勾选的商品列表

    /**Set<String>是用于保存已经勾选的商品列表
     * SharedPreferences能保存的列表只能是Set<String>类型
     * 所以这里采用Set<String>来记录勾选的商品列表
     * SharedPreferences需要保存更复杂的bean对象的话可以转换为JSON的字符串来保存
     * */
    Set<String> mProductPreferences = new HashSet<>();//之前已经勾选了并保存在本地的商品

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_product_type_preference_setting);
        //所有的可以勾选的商品列表
        allProductType = Constants.allProductTypeList;
        //读取本地保存的已经勾选的商品
        mProductPreferences = mSharedPreferences.getStringSet(Constants.PRODUCTPREFERENCES, mProductPreferences);
        //实例化适配器
        mAdapter = new ProdectTypeAdapter(allProductType, mProductPreferences);
        //实例化布局管理器 这里是线性布局
        mManager = new LinearLayoutManager(this);
        //设置布局
        recyclerView.setLayoutManager(mManager);
        //设置适配器
        recyclerView.setAdapter(mAdapter);
        //设置recycleview中每个item的监听
        mAdapter.setOnItemClickListener(new DefaultAdapter.OnRecyclerViewItemClickListener() {
            @Override
            public void onItemClick(View view, int viewType, Object data, int position) {
                //根据点击的位置获取到item的布局文件
                View itemview = mManager.findViewByPosition(position);
                LinearLayout layout = (LinearLayout) itemview;
                //获取到每个item中的checkbox对象
                CheckBox checkbox = (CheckBox) layout.findViewById(R.id.checkbox_product);
                //根据点击的位置读取到商品对象
                ProductTypeBean product = allProductType.get(position);
                //进一步读取到商品的id
                String productId = product.getProductTypeId();
               
                //如果这个商品已经勾选了 设置checkbox为未勾选 从mProductPreferences移除
                //反之 将checkbox设置为已勾选 加入mProductPreferences
                if (mProductPreferences.contains(productId)) {
                    mProductPreferences.remove(productId);
                    checkbox.setChecked(false);
                } else {
                    mProductPreferences.add(productId);
                    checkbox.setChecked(true);
                }
               
            }

        });
    }

    @Override
    protected void onDestroy() {
        //退出时 进行保存写入
        mSharedPreferences.edit().putStringSet(Constants.PRODUCTPREFERENCES, mProductPreferences).commit();
        setResult(RESULT_OK);
        super.onDestroy();
    }
}

到这里我们就基本上完成了多选列表,再次强调,以上代码不可直接使用,但是注释写的非常清楚,可以看懂了,根据自己的业务需求来实现。
这只是最简单的多选框,如果要加入一键全选 ,一键取消 ,单选,反选等等,可以自己扩展

4.难点总结

难点1:holder的复用导致勾选混乱 比如我勾了第1个商品,但是第11个也会勾上,这个可以靠单独的数据源来解决,比如代码中的ischoose(会有其他问题,比如勾选了,滑动导致 holder刷新,勾选状态又没了),当然也可以简单除暴holder.setIsRecyclable(false);禁止holder的复用。
难点2:获取单个item中的单个view,这里的view是checkbox。通过布局管理器来获取到view对象,跟着代码里来做就可以