Android封装单选与多选

作者: 键盘上的麒麟臂 | 来源:发表于2017-09-25 21:49 被阅读494次

    平时开发的时候我们总会碰到这样的需求。

    image.png

    有时是多选,有时是单选,这样的页面基本都是用RecyclerView来做的,而如果每次做操作的时候都要去写这个单选框/多选框的逻辑,那就太麻烦了,所以我就想把这样的单选/多选列表功能给封装起来

    一.思路

    这种功能的基本布局页面都是这样

    image.png

    或者有些需要把选择框放在右边

    image.png

    我的思路是外层还是用RecyclerView来做,里层(就是实线内)我打算用ViewModel来做,这样既能封装外层的选择框逻辑,对内层也低耦合。

    二.效果展示

    1.单选模式
    15063446875651506344674549.gif
    2.多选模式
    15063446848301506344654342.gif

    PS:左边的布局是个ViewModel可以自由定义,因为时间的问题我没时间再写另外的布局来展示。

    三.调用方法

    调用方法很简单:

    1.定义自己的ViewModel
    2.调用
     checkRecyclerView = new CheckRecyclerView.Builder<ChoseShopEntity>(this,"com.berchina.o2omerchant.ui.viewModel.shop.ChoseShopViewModel")
                    .setItemDecoration(new XXXItemDecoration(10))
                    .setBackgroupColorResID(R.color.divider_grey)
    //                .setCheckboxResID(R.drawable.cb_shop_create)
                    .setIsRadio(false)
                    .builder();
            framelayout.addView(checkRecyclerView.getContentView());
    

    Builder中的第二个参数我传的就是ViewModel的类名。

    3.设置数据
    checkRecyclerView.setAdapter(datalist);
    

    封装好了用起来就是快,不会每次都要重复去写单选和复选的逻辑。

    四.CheckRecyclerView内的操作

    我这里把RecyclerView、Adapter和ViewHolder都写在一个类中,仿照RecyclerView源码的写法,当然分开写也行。

    先贴全部代码吧

    public class CheckRecyclerView<T extends CheckRecyclerView.CheckRecyclerEntity> {
    
        protected Context context;
        protected RecyclerView recyclerView;
        protected static CheckRecyclerAdapter adapter;
        // 0 表示都不显示,1表示显示左边,2表示显示右边
        protected static int isShow;
        protected static String vmName;
        protected static int checkboxResID;
        protected static boolean isRadio;
        protected static RadioObserver radioObserver; // 监听单选
        protected static MultiObserver multiObserver; // 监听多选
    
        protected Builder builder;
        // 单选情况下的观察者
        private static Action1<Integer> observer1 = new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                if (adapter != null){
                    adapter.checkChange(integer);
                }
            }
        };
        // 多选情况下的观察者
        private static Action1<Integer> observer2 = new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                if (adapter != null){
                    adapter.multiChose(integer);
                }
            }
        };
    
        private CheckRecyclerView(Context context,Builder builder){
            this.context = context;
            this.builder = builder;
    
            this.isShow = builder.isShow;
            this.vmName = builder.vmName;
            this.checkboxResID = builder.checkboxResID;
            this.isRadio = builder.isRadio;
            this.radioObserver = builder.radioObserver;
            this.multiObserver = builder.multiObserver;
    
            initView();
        }
    
        private void initView(){
            recyclerView = new RecyclerView(context);
            recyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            recyclerView.setBackgroundResource(builder.backgroupColorResID);
            recyclerView.setLayoutManager(builder.layoutManager);
            if (builder.itemDecoration != null) {
                recyclerView.addItemDecoration(builder.itemDecoration);
            }
        }
    
        /**
         *  设置适配器
         */
        public void setAdapter(){
            List<T> datalist = builder.datalist;
            if (datalist == null){
                datalist = new ArrayList<T>();
            }
            adapter = new CheckRecyclerAdapter(context,datalist);
    
            recyclerView.setAdapter(adapter);
        }
    
        /**
         *  设置适配器
         */
        public void setAdapter(List<T> datalist){
            adapter = new CheckRecyclerAdapter(context,datalist);
    
            recyclerView.setAdapter(adapter);
        }
    
        /**
         * get and set 方法
         */
        public RecyclerView getContentView() {
            return recyclerView;
        }
        public static CheckRecyclerAdapter getAdapter() {
            return adapter;
        }
        public void setIsShow(int isShow) {
            this.isShow = isShow;
        }
        public static void setVmName(String vmName) {
            CheckRecyclerView.vmName = vmName;
        }
    
        /**
         *  获取到选择的结果
         *  规则:-1表示没有选择
         */
        // 单选左
        public int getLeftRadio(){
            if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
                return adapter.getLeftRadio();
            }
            return  -1;
        }
        // 单选右
        public int getRightRadio(){
            if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
                return adapter.getRightRadio();
            }
            return  -1;
        }
        // 多选左   返回被选中的数组
        public List<Integer> getLeftMulti(){
            List<Integer> multiList = new ArrayList<>();
            if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
                for (int i = 0; i < adapter.getDatas().size(); i++) {
                    if (((CheckRecyclerEntity)adapter.getDatas().get(i)).leftCheck == true) {
                        multiList.add(i);
                    }
                }
            }
            return multiList;
        }
        // 多选右   返回被选中的数组
        public List<Integer> getRightMulti(){
            List<Integer> multiList = new ArrayList<>();
            if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
                for (int i = 0; i < adapter.getDatas().size(); i++) {
                    if (((CheckRecyclerEntity)adapter.getDatas().get(i)).rightCheck == true) {
                        multiList.add(i);
                    }
                }
            }
            return multiList;
        }
    
        /**
         *  判断在多选的情况下是否全部选择
         */
        public boolean isAllChose(){
            Boolean isAllChose = true;
            if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0) {
                if (isShow == 1) {
                    for (int i = 0; i < adapter.getDatas().size(); i++) {
                        if (((CheckRecyclerEntity)adapter.getDatas().get(i)).leftCheck == false){
                            return false;
                        }
                    }
                } else if (isShow == 2) {
                    for (int i = 0; i < adapter.getDatas().size(); i++) {
                        if (((CheckRecyclerEntity)adapter.getDatas().get(i)).rightCheck == false){
                            return false;
                        }
                    }
                } else {
                    return false;
                }
            }else {
                isAllChose = false;
            }
            return isAllChose;
        }
    
        /**
         *  外部控制内部进行单选的操作
         */
        public void setRadio(int position){
            if (adapter != null){
                adapter.checkChange(position);
            }
        }
        /**
         *  设置全选/全不选
         *  规则:true表示全选 false表示全不选
         */
        public void setAllMulti(Boolean bol){
            if (adapter != null){
                adapter.setAllMulti(bol);
            }
        }
    
        /**
         *  recycelerView 的 adapter
         */
        public static class CheckRecyclerAdapter<T extends CheckRecyclerEntity> extends XXXRecyclerViewBaseAdapter<T>{
            // 记录单选的选项(用空间换时间)
            private int leftRadio = -1;
            private int rightRadio = -1;
    
            public CheckRecyclerAdapter(Context context, List dataSource) {
                super(context, dataSource);
            }
    
            @Override
            protected BerRecyclerViewHolder getViewHolder(ViewGroup parent) {
                return new CheckRecyclerViewHolder(LayoutInflater.from(context).inflate(R.layout.item_check_recycler,parent,false),context);
            }
    
            /**
             * 单选
             */
            public void checkChange(int position){
                if (position < dataSource.size()) {
                    // 先将所有都设为未点击
                    for (int i = 0; i < dataSource.size(); i++) {
                        dataSource.get(i).leftCheck = false;
                        dataSource.get(i).rightCheck = false;
                    }
                    // 再设置点击的Item
                    if (isShow == 1) {
                        dataSource.get(position).leftCheck = true;
                        leftRadio = position;
                    }else if (isShow == 2){
                        dataSource.get(position).rightCheck = true;
                        rightRadio = position;
                    }
    
                    // 做响应
                    if (radioObserver != null) {
                        radioObserver.radioClick(position, isShow);
                    }
    
                    this.notifyDataSetChanged();
                }
            }
    
            /**
             *  多选
             */
            public void multiChose(int position){
                // 点击后展示相反的情况
                if (isShow == 1) {
                    dataSource.get(position).leftCheck = !dataSource.get(position).leftCheck;
                }else if (isShow == 2){
                    dataSource.get(position).rightCheck = !dataSource.get(position).rightCheck;
                }
    
                // 做响应
                if (multiObserver != null) {
                    multiObserver.multiClick(position, isShow);
                }
    
                this.notifyDataSetChanged();
            }
    
            /**
             * 设置全选
             */
            public void setAllMulti(Boolean bol){
                if (isShow == 1) {
                    for (int i = 0; i < dataSource.size(); i++) {
                        dataSource.get(i).leftCheck = bol;
                    }
                    this.notifyDataSetChanged();
                }else if (isShow == 2){
                    for (int i = 0; i < dataSource.size(); i++) {
                        dataSource.get(i).rightCheck = bol;
                    }
                    this.notifyDataSetChanged();
                }
            }
    
            // get and set
            public int getLeftRadio() {return leftRadio;}
            public int getRightRadio() {return rightRadio;}
        }
    
        /**
         *  recycelerView 的 viewholder
         */
        public static class CheckRecyclerViewHolder<T extends CheckRecyclerEntity> extends XXXRecyclerViewHolder<T>{
    
            @InjectView(R.id.cb_left)
            CheckBox cbLeft;
            @InjectView(R.id.cb_right)
            CheckBox cbRight;
            @InjectView(R.id.fl_content)
            FrameLayout flContent;
    
            private BaseViewModel viewmodel;
    
    
            public CheckRecyclerViewHolder(View itemView, Context context) {
                super(itemView, context);
                ButterKnife.inject(this,itemView);
    
                if (isShow == 0){
                    cbLeft.setVisibility(View.GONE);
                    cbRight.setVisibility(View.GONE);
                }else if (isShow == 1){
                    cbLeft.setVisibility(View.VISIBLE);
                    cbRight.setVisibility(View.GONE);
                }else if (isShow == 2){
                    cbLeft.setVisibility(View.GONE);
                    cbRight.setVisibility(View.VISIBLE);
                }
                // 设置CheckBox的样式
                if (checkboxResID != 0) {
                    cbLeft.setButtonDrawable(checkboxResID);
                    cbRight.setButtonDrawable(checkboxResID);
                }
                // 应反射创建viewmodel对象
                try {
                    Class<?> myClass = Class.forName(vmName);//完整类名
                    Class[] paramTypes = { Context.class };
                    Object[] params = {context};
                    Constructor con = myClass.getConstructor(paramTypes);
                    viewmodel = (BaseViewModel) con.newInstance(params);
                    flContent.addView(viewmodel.getContentView());
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
    
            }
    
            @Override
            public void setDataToView() {
                super.setDataToView();
                // 处理选择框的点击 , 只做数据展示,逻辑放在外边处理
                cbLeft.setChecked(data.leftCheck);
                cbRight.setChecked(data.rightCheck);
                //为viewmodel添加数据
                viewmodel.setData(data);
            }
    
            /**
             *  接下来做 单选 和 多选 的操作  todo 先测试单选看看能不能用
             */
            @OnClick({R.id.cb_left,R.id.cb_right})
            public void itemClick(View v){
                switch (v.getId()){
                    case R.id.cb_left:
                        if (isRadio) {
                            // 单选
                            Observable.just(getAdapterPosition()).subscribe(observer1);
                        }else {
                            // 多选
                            Observable.just(getAdapterPosition()).subscribe(observer2);
                        }
                        break;
                    case R.id.cb_right:
                        if (isRadio) {
                            Observable.just(getAdapterPosition()).subscribe(observer1);
                        }else {
                            Observable.just(getAdapterPosition()).subscribe(observer2);
                        }
                        break;
                }
            }
    
        }
    
        /**
         *  定义两个数据来记录是否点击选择框
         *  规则:调用封装的数据类要继承这个类
         */
        public static class CheckRecyclerEntity{
            public boolean leftCheck;
            public boolean rightCheck;
        }
    
        /**
         *  =======================================================================
         *   Builder方法
         *  ========================================================================
         */
        public static class Builder<T extends CheckRecyclerView.CheckRecyclerEntity>{
    
            private Context context;
            private int isShow = 1; // 默认为显示左边的
            private String vmName;
            private List<T> datalist;
            //RecyclerView相关
            private RecyclerView.ItemDecoration itemDecoration;
            private RecyclerView.LayoutManager layoutManager;
            private int backgroupColorResID; //RecyclerView背景颜色,默认为白色
            // checkbox相关
            private int checkboxResID = 0;
            private boolean isRadio = true;// 定义单选(true)和多选(true)
            private RadioObserver radioObserver; // 监听单选
            private MultiObserver multiObserver; // 监听多选
    
            // 这个一定要传完整 包名+类名
            public Builder(Context context,String vmName){
                this.context = context;
                this.vmName = vmName;
    
                layoutManager = new LinearLayoutManager(context);
                backgroupColorResID = R.color.white;
            }
    
            public Builder setIsShow(int isShow) {
                this.isShow = isShow;
                return this;
            }
    
            public Builder setDatalist(List<T> datalist) {
                this.datalist = datalist;
                return this;
            }
    
            public Builder setItemDecoration(RecyclerView.ItemDecoration itemDecoration) {
                this.itemDecoration = itemDecoration;
                return this;
            }
    
            public Builder setLayoutManager(RecyclerView.LayoutManager layoutManager) {
                this.layoutManager = layoutManager;
                return this;
            }
    
            public Builder setCheckboxResID(int checkboxResID) {
                this.checkboxResID = checkboxResID;
                return this;
            }
    
            public Builder setBackgroupColorResID(int backgroupColorResID) {
                this.backgroupColorResID = backgroupColorResID;
                return this;
            }
    
            public Builder setIsRadio(boolean radio) {
                isRadio = radio;
                return this;
            }
    
            public Builder setRadioObserver(RadioObserver radioObserver) {
                this.radioObserver = radioObserver;
                return this;
            }
    
            public Builder setMultiObserver(MultiObserver multiObserver) {
                this.multiObserver = multiObserver;
                return this;
            }
    
            public CheckRecyclerView builder(){
               return new CheckRecyclerView(context,this);
            }
    
        }
    
        /**
         *  单选时的响应
         */
        public interface RadioObserver{
            public void radioClick(int position,int isShow);
        }
        /**
         *  多选时的响应
         */
        public interface MultiObserver{
            public void multiClick(int position,int isShow);
        }
    
    }
    
    

    代码也不能说长,500行这样,其实还好。也只封装了一些基本的功能操作,之后还可以扩展。其实我还算挺良心的,加了很多注解,我个人习惯先写注解再写方法。

    基类的XXXRecyclerViewBaseAdapter和XXXRecyclerViewHolder是我自己写的Adapter和ViewHolder的基类


    1.基本的流程

    我有注解也不一行一行解释,主要是用了Builder模式,在外边使用时可以addView,用自定义写XML控件也行。

    定义单选和多选的规则:

    (1)protected static int isShow; 0 表示都不显示,1表示显示左边,2表示显示右边,默认为1
    (2)protected static boolean isRadio; 表示单选/多选,true表示单选,false表示多选

    定义RecyclerView

    private void initView(){
            recyclerView = new RecyclerView(context);
            recyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            recyclerView.setBackgroundResource(builder.backgroupColorResID);
            recyclerView.setLayoutManager(builder.layoutManager);
            if (builder.itemDecoration != null) {
                recyclerView.addItemDecoration(builder.itemDecoration);
            }
        }
    

    数据要继承

     public static class CheckRecyclerEntity{
            public boolean leftCheck;
            public boolean rightCheck;
        }
    
    

    表示左边是否点击和右边是否点击

    最关键是ViewHolder的两个方法:
    (1)initView

    public CheckRecyclerViewHolder(View itemView, Context context) {
                super(itemView, context);
                ButterKnife.inject(this,itemView);
    
                if (isShow == 0){
                    cbLeft.setVisibility(View.GONE);
                    cbRight.setVisibility(View.GONE);
                }else if (isShow == 1){
                    cbLeft.setVisibility(View.VISIBLE);
                    cbRight.setVisibility(View.GONE);
                }else if (isShow == 2){
                    cbLeft.setVisibility(View.GONE);
                    cbRight.setVisibility(View.VISIBLE);
                }
                // 设置CheckBox的样式
                if (checkboxResID != 0) {
                    cbLeft.setButtonDrawable(checkboxResID);
                    cbRight.setButtonDrawable(checkboxResID);
                }
                // 应反射创建viewmodel对象
                try {
                    Class<?> myClass = Class.forName(vmName);//完整类名
                    Class[] paramTypes = { Context.class };
                    Object[] params = {context};
                    Constructor con = myClass.getConstructor(paramTypes);
                    viewmodel = (BaseViewModel) con.newInstance(params);
                    flContent.addView(viewmodel.getContentView());
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
    

    首先判断isShow 是单选/多选/隐藏 来展示页面。然后设置CheckBox的样式。
    最后创建ViewModel对象添加到最上边图中实线的区域中。但是我们展示的页面并不是固定的,也就是说我们会在不同的情况创建不同的viewmodel对象,所以这里不能直接写,我用了反射来添加viewmodel,所以在上边调用的地方传了viewmodel的包名+类名,是为了用类名创建相应的viewmodel对象添加到区域中。

    (2)setDataToView

    @Override
            public void setDataToView() {
                super.setDataToView();
                // 处理选择框的点击 , 只做数据展示,逻辑放在外边处理
                cbLeft.setChecked(data.leftCheck);
                cbRight.setChecked(data.rightCheck);
                //为viewmodel添加数据
                viewmodel.setData(data);
            }
    

    这个就很简单了,根据leftCheck和rightCheck的值设置左右checkbox的展示,然后给viewmodel设置数据。

    2.点击时的处理

    做完展示时的处理之后就要做点击时的逻辑处理。

    @OnClick({R.id.cb_left,R.id.cb_right})
            public void itemClick(View v){
                switch (v.getId()){
                    case R.id.cb_left:
                        if (isRadio) {
                            // 单选
                            Observable.just(getAdapterPosition()).subscribe(observer1);
                        }else {
                            // 多选
                            Observable.just(getAdapterPosition()).subscribe(observer2);
                        }
                        break;
                    case R.id.cb_right:
                        if (isRadio) {
                            Observable.just(getAdapterPosition()).subscribe(observer1);
                        }else {
                            Observable.just(getAdapterPosition()).subscribe(observer2);
                        }
                        break;
                }
            }
    

    先判断点左还是点右,再判断是单选模式还是多选模式,我这里用了观察者模式。

     // 单选情况下的观察者
        private static Action1<Integer> observer1 = new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                if (adapter != null){
                    adapter.checkChange(integer);
                }
            }
        };
        // 多选情况下的观察者
        private static Action1<Integer> observer2 = new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                if (adapter != null){
                    adapter.multiChose(integer);
                }
            }
        };
    

    点击之后会在adapter中做响应操作。

    /**
             * 单选
             */
            public void checkChange(int position){
                if (position < dataSource.size()) {
                    // 先将所有都设为未点击
                    for (int i = 0; i < dataSource.size(); i++) {
                        dataSource.get(i).leftCheck = false;
                        dataSource.get(i).rightCheck = false;
                    }
                    // 再设置点击的Item
                    if (isShow == 1) {
                        dataSource.get(position).leftCheck = true;
                        leftRadio = position;
                    }else if (isShow == 2){
                        dataSource.get(position).rightCheck = true;
                        rightRadio = position;
                    }
    
                    // 做响应
                    if (radioObserver != null) {
                        radioObserver.radioClick(position, isShow);
                    }
    
                    this.notifyDataSetChanged();
                }
            }
    
            /**
             *  多选
             */
            public void multiChose(int position){
                // 点击后展示相反的情况
                if (isShow == 1) {
                    dataSource.get(position).leftCheck = !dataSource.get(position).leftCheck;
                }else if (isShow == 2){
                    dataSource.get(position).rightCheck = !dataSource.get(position).rightCheck;
                }
    
                // 做响应
                if (multiObserver != null) {
                    multiObserver.multiClick(position, isShow);
                }
    
                this.notifyDataSetChanged();
            }
    

    注释都有,算法也不难,不用详细去讲了。主要封装的逻辑其实也就这些,而其他的方法基本都是和外边交互时用到的方法,我都写了注释。
    比如

    /**
         *  外部控制内部进行单选的操作
         */
        public void setRadio(int position){
            if (adapter != null){
                adapter.checkChange(position);
            }
        }
    

    这个就是外面要求里面单选哪个。比如说有些需求是要默认选第一个,所以可以在外面调用checkRecyclerView.setRadio(0);

    /**
         *  设置全选/全不选
         *  规则:true表示全选 false表示全不选
         */
        public void setAllMulti(Boolean bol){
            if (adapter != null){
                adapter.setAllMulti(bol);
            }
        }
    

    这是设置全选,比如外面点击哪个按钮后里面显示全选,那就在外面调用checkRecyclerView.setAllMulti(true);

    其它的什么我都加了注释,就不都说了。

    3.Builder的属性

    简单介绍在Builder中定义的属性吧
    (1)isShow 显示选择框在左还是右
    (2)datalist需要适配的数据
    (3)itemDecoration RecyclerView的分割线
    (4)layoutManager RecyclerView的布局
    (5)backgroupColorResID RecyclerView的颜色
    (6)checkboxResID Checkbox的样式ID
    (7)isRadio 单选还是多选
    (8)radioObserver 单选的监听,要实现这个接口
    (9)multiObserver 多选的监听
    (10)vmName viewmodel的包名+类名
    注意要传包名+类名,只传类名不行吗?不行,因为不同的包下可以有相同的类名,我怎么懂是要哪个。

    其实我觉得代码也不算多,也不难理解,最近没时间也没能写个demo放到github上,之后闲下来再弄吧。然后也没经过严格的测试,可能一些地方写得不是很好。


    更新

    后来我用这个东西发现一个错的地方,就是内部类和成员都不要使用static,不然在多处使用的时候会出错。当初习惯性的会下意思使用静态内部类,后来发现用着用着就有问题了。还是太粗心了。
    把代码中的静态内部类和静态成员改成非静态的就行。

    相关文章

      网友评论

      本文标题:Android封装单选与多选

      本文链接:https://www.haomeiwen.com/subject/mhndextx.html