在 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>