UltimateRecyclerView 是一个非常好用的 Android 第三方控件,刚开的新项目里一直在用,配合最新的 API 使用非常完美。这篇文章主要是介绍一下怎么给 UltimateRecyclerView 添加多选功能,多选的问题唯一的难点在于复用,当然方式有很多种,实现方式也都大同小异,之前写过一篇文章 GridView 的多选,那种方式现在看来还是有点太复杂了。
今天就记录两种比较简单的方式,同时因为是类 RecyclerView ,都需要使用 LinearLayoutManager ,因此扩展性就大大增强了,同时也支持 GridView 。同时,接口是在 ViewHolder 里定义的,而 UltimateRecyclerView 的 onBindViewHolder 方法参数里的 ViewHolder 也是继承于 RecyclerView.Holder ,因此这种方式对于一般的普通的 RecyclerView 也是适用的。
其实 twoway-view 的 ItemSelectionSupport 也是提供了多选的方法,同时还有其他的 ChoiceMode ,他的实现方式跟 ListView 是一样的,也是非常好的思路,各种判断也很完善。下面的几行代码就可以搞定。1
2
3ItemSelectionSupport itemSelection = ItemSelectionSupport.addTo(recyclerView);
itemSelection.setChoiceMode(ChoiceMode.MULTIPLE);
itemSelection.setItemChecked(2, true);
但是很遗憾并不适用于 UltimateRecyclerView 。第一是因为 UltimateRecyclerView 并不像 twoway-view 是继承 RecyclerView 的,当然通过修改 twoway-view 的源码 UltimateRecyclerView 也是可以实现的,就是稍微复杂一些。第二是可能因为这个类本身还有其他的不够完美的地方吧,对于新版本的 Android Studio , compile 引入库之后,并不能使用 ItemSelectionSupport 这个类,需要通过引入 module 包的形式引入。
先说第一种方式,跟传统思路一样,就是记住选中的 position ,然后在 Activity 或者 Fragment 里实现点击接口,刷新界面。基本也就下面几个步骤:
- 使用 SparseBooleanArray 去记录选中的 position ,这个类很神奇,实现了 Cloneable 接口,返回值刚好还是 int 类型;
- 然后在 ViewHolder 里定义条目的点击事件接口,需要实现 View.OnLongClickListener 接口,当然也可以同时定义长按事件接口,实现 View.OnLongClickListener ;
- 在 onBindViewHolder 里定义选中和非选中的状态;
- 在 adapter 里定义一个方法,获取所有的选中条目的 position ,直接返回 int ;
- 在 adapter 里定义一个是否选中的方法,直接返回选中的所有 position 是否包含当前 position 即可;
- 在 adapter 里定义一个切换选中状态的方法,然后 notify ;
- 在 Activity 或者 Fragment 里调用 adapter 里的切换选中状态的方法即可。
因为代码也都比较简单,话不多说,直接上代码吧。下面是我的条目布局文件 item_multi_selected.xml ,就一个 TextView 。1
2
3
4
5
6
7
8
9
10<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginBottom="20dp"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/app_name"
android:textSize="20sp" />
然后是 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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import cn.easydone.multiselectedrecyclerview.R;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Android Studio
* User: Ailurus(ailurus@foxmail.com)
* Date: 2015-07-12
* Time: 08:49
*/
@SuppressWarnings("unused")
public class MultiSelectRecyclerViewAdapter extends RecyclerView.Adapter {
private SparseBooleanArray selectedItems;
private ArrayList<String> mArrayList;
@SuppressWarnings("FieldCanBeLocal")
private Context context;
private ViewHolder.ClickListener clickListener;
public MultiSelectRecyclerViewAdapter(ArrayList<String> mArrayList, Context context, ViewHolder.ClickListener clickListener) {
this.mArrayList = mArrayList;
this.context = context;
this.clickListener = clickListener;
this.selectedItems = new SparseBooleanArray();
}
public boolean isSelected(int position) {
return getSelectedItems().contains(position);
}
public void switchSelectedState(int position) {
if (selectedItems.get(position, false)) {
selectedItems.delete(position);
} else {
selectedItems.put(position, true);
}
notifyItemChanged(position);
}
public void clearSelectedState() {
List<Integer> selection = getSelectedItems();
selectedItems.clear();
for (Integer i : selection) {
notifyItemChanged(i);
}
}
public int getSelectedItemCount() {
return selectedItems.size();
}
public List<Integer> getSelectedItems() {
List<Integer> items = new ArrayList<>(selectedItems.size());
for (int i = 0; i < selectedItems.size(); ++i) {
items.add(selectedItems.keyAt(i));
}
return items;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
@SuppressLint("InflateParams")
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_multi_selected, parent, false);
return new ViewHolder(itemLayoutView, clickListener);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((ViewHolder) holder).tvName.setText(mArrayList.get(position));
((ViewHolder) holder).tvName.setBackgroundColor(isSelected(position) ? Color.DKGRAY : Color.LTGRAY);
((ViewHolder) holder).tvName.setTextColor(isSelected(position) ? Color.RED : Color.BLUE);
}
@Override
public int getItemCount() {
return mArrayList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
public TextView tvName;
private ClickListener listener;
public ViewHolder(View itemLayoutView, ClickListener listener) {
super(itemLayoutView);
this.listener = listener;
tvName = (TextView) itemLayoutView.findViewById(R.id.tvName);
itemLayoutView.setOnClickListener(this);
itemLayoutView.setOnLongClickListener(this);
}
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClicked(getAdapterPosition());
}
}
@Override
public boolean onLongClick(View view) {
return listener != null && listener.onItemLongClicked(getAdapterPosition());
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}
}
最后,在 Activity 或者 Fragment 里实现 ClickListener 接口就好了。1
2
3
4@Override
public void onItemClicked (int position) {
mAdapter.switchSelectedState(position);
}
如果是要实现 GridView 的多选,只需要将 mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
更换为 mRecyclerView.setLayoutManager(new GridLayoutManager (this,3));
即可。
下面是实际的运行效果图,可以看到并没有复用的情况:
接下来介绍另一种投机取巧的方式,其实一讲出来就没什么神秘的了,代码实现起来非常简单,我在项目里也是用的这种方式。简单讲,就是在传入 adapter 的参数 List 集合里的实体添加一个字段 isSeleted ,关键点就在这里,这样一讲出来就实现起来非常容易了。可以通过在 ViewHolder 里定义条目点击的接口,再在 Activity 或者 Fragment 里实现接口,在 adapter 里定义切换选中状态的方法,在 Activity 或者 Fragment 里调用这个方法就好了,实际运行效果非常完美。因为实在是太简单,代码就不贴出来了。注意,问题的关键在于传入的 List 集合数据需要自己重新组织。
总之,两种方式说到底都是要记住选中的 position ,第一种是定义一个存储所有选中 position 的结构,第二种是让传入的 List 集合自己去记住选中的条目,两种方式传入的集合已经不是同一个数据集了。