Android 封装 BaseMultipleChoiceAdapter 快速实现列表多选编辑

在 Android 项目开发里,收藏、文件、本地记录等列表页面,几乎都需要编辑多选、批量删除功能。每次单独编写选中逻辑十分繁琐,于是封装了一款通用多选 Adapter 基类。 无需关心底层选中状态管理,子类只需要实现数据赋值和 UI 展示,即可快速实现全选、单选、批量获取选中数据,文末附带完整 Activity、布局及适配器实战代码,拿来即用。

BaseMultipleChoiceAdapter.java

scss 复制代码
public abstract class BaseMultipleChoiceAdapter<B extends ViewBinding, T> extends RecyclerView.Adapter<ViewBindingHolder> {
    protected Context context;
    protected List<Boolean> marks;  //标记的数组
    protected List<T> lists; //列表数据
    private OnMultipleChoiceDataListener listener; //多选监听

    public BaseMultipleChoiceAdapter(Context context) {
        this(context, new ArrayList<>());
    }

    public BaseMultipleChoiceAdapter(Context context, List<T> lists) {
        this.context = context;
        this.lists = lists;
        marks = new ArrayList<>();
    }

    public List<T> getData() {
        return lists;
    }

    public void setData(List<T> lists) { 
        this.lists = lists;
        marks.clear();
        for (T t : lists) {
            marks.add(false);
        }
        notifyDataSetChanged();
    }

    public void addData(List<T> lists) {
        if (this.lists == null) {
            setData(lists);
        } else {
            for (T t : lists) {
                marks.add(false);
            }
            int num = this.lists.size();
            this.lists.addAll(lists);
            notifyItemRangeInserted(num, lists.size());
        }
    }

    public void addMark(int position) {//添加/移除 选项
        if (!DataCheckUtil.isNull(marks)) {
            marks.set(position, !marks.get(position));

            if (listener != null) {
                int size = marks.size();
                int count = 0;
                for (int i = 0; i < marks.size(); i++) {
                    if (marks.get(i)) {
                        count++;
                    }
                }
                if (count == 0) {
                    listener.OnMultipleChoiceNone();
                } else if (count == size) {
                    listener.OnMultipleChoiceAll();
                } else {
                    listener.OnMultipleChoicePart();
                }
            }

            notifyDataSetChanged();
        }
    }

    public void choiceAll() {//全选
        marks.clear();
        for (T t : lists) {
            marks.add(true);
        }
        if (listener != null) {
            listener.OnMultipleChoiceAll();
        }
        notifyDataSetChanged();
    }

    public void choiceNone() {//全不选
        marks.clear();
        for (T t : lists) {
            marks.add(false);
        }
        if (listener != null) {
            listener.OnMultipleChoiceNone();
        }
        notifyDataSetChanged();
    }

    public List<Boolean> getMark() {
        if (marks == null) {
            return new ArrayList<>();
        }
        return marks;
    }

    public List<T> getMarkData() { //获取选择的数据 可用于删除
        List<T> l = new ArrayList<>();
        List<Boolean> marks = getMark();
        if (!DataCheckUtil.isNull(marks)) {
            for (int i = 0; i < marks.size(); i++) {
                if (marks.get(i)) {
                    l.add(lists.get(i));
                }
            }
        }
        return l;
    }

    public void setOnMultipleChoiceDataListener(OnMultipleChoiceDataListener listener) {
        this.listener = listener;
    }

    @NonNull
    @Override
    public ViewBindingHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        B binding = getViewBinding(parent.getContext(), parent);
        return new ViewBindingHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewBindingHolder holder, int position) {
        setShowData((B) holder.getBinding(), lists.get(position), position);
        onSelect((B) holder.getBinding(), marks.get(position));
        setListener((B) holder.getBinding(), lists.get(position), position);
    }

    protected abstract void setListener(B holder, T t, int position);

    protected abstract void setShowData(B holder, T t, int position);

    protected abstract void onSelect(B holder, boolean select);

    protected abstract B getViewBinding(Context context, ViewGroup parent);
}

数据检查工具类 DataCheckUtil

arduino 复制代码
public class DataCheckUtil {

    public static boolean isNull(String content) {
        return TextUtils.isEmpty(content);
    }

    public static boolean isNull(Object content) {
        if (content == null) {
            return true;
        } else {
            return false;
        }
    }

    public static <T> boolean isNull(List<T> content) {
        if (content == null || content.size() < 1) {
            return true;
        } else {
            return false;
        }
    }

    public static <K, V> boolean isNull(Map<K, V> content) {
        if (content == null || content.size() < 1) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean isNull(Parcelable[] content) {
        if (content == null || content.length < 1) {
            return true;
        } else {
            return false;
        }
    }

    public static <T> boolean isNotIndexOutOfBounds(List<T> content, int position) {
        int size = isNull(content) ? 0 : content.size();
        return position >= 0 && position <= size - 1;
    }

    public static <T> boolean isNotIndexOutOfBounds(String[] content, int position) {
        int size = isNull(content) ? 0 : content.length;
        return position >= 0 && position <= size - 1;
    }

    public static <T> boolean isNotIndexOutOfBounds(float[] content, int position) {
        int size = isNull(content) ? 0 : content.length;
        return position >= 0 && position <= size - 1;
    }
}

我自己项目中的具体案例 收藏功能如下:

activity_.collectxml 和 selector_check_yyts2

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/rl_title"
        android:layout_width="match_parent"
        android:layout_height="@dimen/x70">

        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="@dimen/x50"
            android:layout_height="@dimen/x50"
            android:layout_marginTop="@dimen/x11"
            android:layout_marginLeft="@dimen/x32"
            android:src="@mipmap/icon_back"/>

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:text="我的收藏"
            android:textColor="@color/text_3_3_3"
            android:textSize="@dimen/x26" />

        <TextView
            android:id="@+id/tv_gl_right"
            android:layout_width="@dimen/x96"
            android:layout_height="@dimen/x48"
            android:layout_alignParentRight="true"
            android:layout_marginRight="@dimen/x20"
            android:gravity="center"
            android:layout_centerVertical="true"
            android:includeFontPadding="false"
            android:text="管理"
            android:background="@drawable/shape_d6c9f1_38"
            android:textColor="@color/color_6743B2"
            android:textSize="@dimen/x21" />

        <TextView
            android:id="@+id/tv_cancel_right"
            android:layout_width="@dimen/x96"
            android:layout_height="@dimen/x48"
            android:layout_alignParentRight="true"
            android:layout_marginRight="@dimen/x20"
            android:gravity="center"
            android:layout_centerVertical="true"
            android:includeFontPadding="false"
            android:text="取消"
            android:background="@drawable/shape_d6c9f1_38"
            android:textColor="@color/color_6743B2"
            android:textSize="@dimen/x21"
            android:visibility="gone"/>

        <LinearLayout
            android:id="@+id/ll_check"
            android:layout_width="@dimen/x120"
            android:layout_height="match_parent"
            android:layout_marginLeft="@dimen/x20"
            android:gravity="center"
            android:orientation="horizontal"
            android:visibility="gone">

            <ImageView
                android:id="@+id/iv_check"
                android:layout_width="@dimen/x40"
                android:layout_height="@dimen/x40"
                android:src="@drawable/selector_check_yyts2" />

            <TextView
                android:id="@+id/tv_quanxuan"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/x15"
                android:includeFontPadding="false"
                android:text="@string/select_all"
                android:textColor="@color/text_3_3_3"
                android:textSize="@dimen/x26" />
        </LinearLayout>

    </RelativeLayout>


    <RelativeLayout
        android:id="@+id/rl_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/rl_title"
        android:layout_marginLeft="@dimen/x40"
        android:layout_marginRight="@dimen/x40"
        android:padding="@dimen/x10">


        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/x20"
            android:layout_marginBottom="@dimen/x8"
            android:scrollbars="vertical" />

        <LinearLayout
            android:id="@+id/ll_no_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="@dimen/x80"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:layout_width="@dimen/x300"
                android:layout_height="@dimen/x240"
                android:src="@mipmap/icon_file_empty" />

            <TextView
                android:id="@+id/tv_tips"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/x2"
                android:includeFontPadding="false"
                android:text="还没有任何收藏~"
                android:textColor="@color/text_9_9_9"
                android:textSize="@dimen/x24" />

        </LinearLayout>

    </RelativeLayout>

    <FrameLayout
        android:id="@+id/fl_delete"
        android:layout_width="match_parent"
        android:layout_height="@dimen/x100"
        android:layout_alignParentBottom="true"
        android:visibility="gone">

        <TextView
            android:id="@+id/delete_bottom"
            android:layout_width="@dimen/x200"
            android:layout_height="@dimen/x60"
            android:layout_gravity="center"
            android:background="@drawable/shape_red30"
            android:gravity="center"
            android:includeFontPadding="false"
            android:text="删除"
            android:textColor="@color/white"
            android:textSize="@dimen/x28" />

    </FrameLayout>
</RelativeLayout>
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@mipmap/btn_yytsgl_gxd2" android:state_selected="true" />
    <item android:drawable="@mipmap/btn_yytsgl_wgxd2" android:state_selected="false" />
</selector>

CollectActivity.java 代码

scss 复制代码
/**
 * 收藏
 */
public class CollectActivity extends BaseBingActivity<ActivityCollectBinding> implements View.OnClickListener{

    CollectAdapter collectAdapter;
    boolean isEdit = false; //编辑状态

    List<CollectBean> list;

    public static void skip(Activity activity) {
        Intent intent = new Intent(activity, CollectActivity.class);
        activity.startActivity(intent);
        activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }

    @Override
    protected ActivityCollectBinding getViewBinding() {
        return ActivityCollectBinding.inflate(getLayoutInflater());
    }

    @Override
    protected void init() {
        list = new ArrayList<>();
    }

    @Override
    protected void listeners() {
        mBinding.ivBack.setOnClickListener(this);
        mBinding.deleteBottom.setOnClickListener(this);
        mBinding.tvCancelRight.setOnClickListener(this);
        mBinding.tvGlRight.setOnClickListener(this);
        mBinding.llCheck.setOnClickListener(this);
        mBinding.deleteBottom.setAlpha(0.5f);
        collectAdapter = new CollectAdapter(CollectActivity.this, isEdit);
        mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        mBinding.recyclerView.setAdapter(collectAdapter);
        collectAdapter.setOnItemClickCallback(new CollectAdapter.OnItemClickCallback() {
            @Override
            public void OnItemClick(int index) {
                //单击 item选中  进行你的业务逻辑
            }
        });
        //多选监听
        collectAdapter.setOnMultipleChoiceDataListener(new OnMultipleChoiceDataListener() {
            @Override
            public void OnMultipleChoiceAll() {
                mBinding.ivCheck.setSelected(true);
                updateUI();
            }

            @Override
            public void OnMultipleChoiceNone() {
                mBinding.ivCheck.setSelected(false);
                updateUI();
            }

            @Override
            public void OnMultipleChoicePart() {
                mBinding.ivCheck.setSelected(false);
                updateUI();
            }
        });
        loadData(); //加载列表数据
        
    }

    boolean isfirst = true;
    @Override
    protected void onResume() {
        super.onResume();
        if(isfirst){
            isfirst = false;
            return;
        }
        loadData();
    }

    private void updateUI(){
        if(collectAdapter.getMarkData().size() > 0){
            mBinding.deleteBottom.setText("删除 ("+collectAdapter.getMarkData().size()+")");
            mBinding.deleteBottom.setAlpha(1f);
        }else {
            mBinding.deleteBottom.setText("删除");
            mBinding.deleteBottom.setAlpha(0.5f);
        }
    }

    private void loadData(){
        list.clear();
        //你的数据加载逻辑
        //加载完成之后setListUI
        setListUI();
    }

    private void setListUI(){
        if(list != null && list.size() > 0){
            mBinding.recyclerView.setVisibility(View.VISIBLE);
            mBinding.llNoData.setVisibility(View.GONE);
            mBinding.tvGlRight.setVisibility(View.VISIBLE);
            collectAdapter.setData(list);
        }else {
            mBinding.tvGlRight.setVisibility(View.GONE);
            mBinding.recyclerView.setVisibility(View.GONE);
            mBinding.llNoData.setVisibility(View.VISIBLE);
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    finish();
                }
            }, 1000);
        }
    }

    private DeleteDialog deleteDialog;
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.iv_back: //返回
                finish();
                break;
            case R.id.delete_bottom: //批量删除
                
                if(collectAdapter.getMarkData().size() > 0){
                    deleteDialog = new DeleteDialog(CollectActivity.this,"取消", "删除");
                    deleteDialog.setQuXiaoClickLister(new DeleteDialog.QuXiaoClickListener() {
                        @Override
                        public void onQuXiaoClick() { //取消

                        }
                    });
                    deleteDialog.setQueDingClickLister(new DeleteDialog.QueDingClickListener() {
                        @Override
                        public void OnQueDingClick() {//确定 删除
                            List<CollectBean> list = collectAdapter.getMarkData();
                            StringBuffer sb = new StringBuffer("");
                            for (int i = 0; i < list.size(); i++) {
                                sb.append(list.get(i).getId()+",");
                            }
                            //delete(sb.toString()); //你的删除业务逻辑
                            mBinding.tvCancelRight.performClick();
                        }
                    });
                    if(!isFinishing()){
                        deleteDialog.show(); 
                    }
                }
                break;
            case R.id.tv_cancel_right: //取消管理
                isEdit = false;
                collectAdapter.setEnable(false);
                collectAdapter.choiceNone();
                mBinding.ivBack.setVisibility(View.VISIBLE);
                if(list != null && list.size() > 0){
                    mBinding.tvGlRight.setVisibility(View.VISIBLE);
                }
                mBinding.llCheck.setVisibility(View.GONE);
                mBinding.tvCancelRight.setVisibility(View.GONE);
                mBinding.flDelete.setVisibility(View.GONE);

                break;
            case R.id.tv_gl_right: //管理
                isEdit = true;
                collectAdapter.setEnable(true);
                mBinding.ivBack.setVisibility(View.GONE);
                mBinding.tvGlRight.setVisibility(View.GONE);
                mBinding.llCheck.setVisibility(View.VISIBLE);
                mBinding.tvCancelRight.setVisibility(View.VISIBLE);
                mBinding.flDelete.setVisibility(View.VISIBLE);
                break;
            case R.id.ll_check: //全选
                if (mBinding.ivCheck.isSelected()) {
                    collectAdapter.choiceNone();
                } else {
                    collectAdapter.choiceAll();
                }
                break;

        }
    }

    private void delete(String ids){
        
    }

    @Override
    protected void loaddata() {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        collectAdapter = null;
        // 3. 销毁时关闭对话框
        if (deleteDialog != null && deleteDialog.isShowing()) {
            deleteDialog.dismiss();
            deleteDialog = null;
        }
        // 清空数据集,释放数据引用
        if (list != null) {
            list.clear();
            list = null;
        }
        mBinding = null;
    }
}

CollectAdapter.java 代码

java 复制代码
public class CollectAdapter extends BaseMultipleChoiceAdapter<ItemMyCollectBinding, CollectBean> {

    private boolean enable; //开启编辑
    private Context mContext = null;
    private OnItemClickCallback itemClickCallback;

    public CollectAdapter(Context context, boolean enable) {
        super(context);
        mContext = context;
        this.enable = enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
        notifyDataSetChanged();
    }

    @Override
    protected void setListener(ItemMyCollectBinding holder, CollectBean book, int position) {
        if(enable){
            holder.ivCheck.setImageResource(R.drawable.selector_check_yyts2);
        }else {
            holder.ivCheck.setImageResource(R.mipmap.icon_headset);
        }
        holder.getRoot().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(enable){
                    addMark(position);
                }else {
                    if(itemClickCallback!=null){
                        itemClickCallback.OnItemClick(position);
                    }
                }
            }
        });
    }

    @Override
    protected void setShowData(ItemMyCollectBinding holder, CollectBean book, int position) {
        holder.tvName.setText(book.getName());
        holder.tvSn.setText(position+1+"");
    }

    @Override
    protected void onSelect(ItemMyCollectBinding holder, boolean select) {
        if(enable){
            holder.ivCheck.setSelected(select);
        }
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    @Override
    public int getItemCount() {
        return lists.size();
    }

    @Override
    protected ItemMyCollectBinding getViewBinding(Context context, ViewGroup parent) {
        return ItemMyCollectBinding.inflate(LayoutInflater.from(context), parent, false);
    }

    public void setOnItemClickCallback(OnItemClickCallback onItemClickCallback) {
        this.itemClickCallback = onItemClickCallback;
    }

    public interface OnItemClickCallback {
        void OnItemClick(int index);
    }
}

适配器的布局文件 item_my_collect

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/x80"
        android:layout_marginLeft="@dimen/x30"
        android:layout_marginRight="@dimen/x40">

        <ImageView
            android:id="@+id/iv_check"
            android:layout_width="@dimen/x43"
            android:layout_height="@dimen/x43"
            android:layout_centerVertical="true"
            android:src="@drawable/selector_check_yyts2"/>

        <TextView
            android:id="@+id/tv_sn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/text_3_3_3"
            android:layout_toRightOf="@id/iv_check"
            android:layout_marginLeft="@dimen/x33"
            android:includeFontPadding="false"
            android:layout_centerVertical="true"
            android:textSize="@dimen/x24" />

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/tv_sn"
            android:layout_marginLeft="@dimen/x35"
            android:textColor="@color/text_3_3_3"
            android:includeFontPadding="false"
            android:layout_centerVertical="true"
            android:textSize="@dimen/x24" />

        <TextView
            android:layout_width="match_parent"
            android:layout_alignLeft="@id/iv_check"
            android:layout_height="@dimen/x1"
            android:layout_alignParentBottom="true"
            android:background="@color/color_E2EFEF"/>
    </RelativeLayout>


</RelativeLayout>

相关推荐
波诺波1 小时前
最小 SOFA XML 场景结构 0-base.scn
xml·java·前端
李少兄1 小时前
深入理解 Web 服务器、Servlet 容器与现代 Java Web 架构
java·服务器·servlet
weixin_399380691 小时前
Tongweb7049m10适配skywalking(by lqw)
java·skywalking
写了20年代码的老程序员1 小时前
企业微信、飞书、钉钉 Webhook 接入,后端代码为什么总是越写越丑
java·微信
解决问题no解决代码问题1 小时前
设计模式分类介绍
java·开发语言·设计模式
码不停蹄的玄黓1 小时前
SpringBoot 自动装配原理
java·spring boot·后端
白露与泡影2 小时前
Java虚拟线程实战:从线程池痛点到性能优化全流程
java·开发语言·性能优化
码上有光2 小时前
c++模板进阶知识讲解(对模板的进一步的运用与理解)
java·前端·c++·特化·模板进阶·偏特化
IT空门:门主2 小时前
Java 单例模式详解:7 种实现方式 + volatile 原理 + 反射与序列化问题
java·开发语言·单例模式