解读好用的 RecyclerView 第三方库 Groupie

文章目录
  1. 1. 简介 Groupie
  2. 2. Demo 用法
  3. 3. 源码解读

剑如初,心如故,三千弱水繁华路。

Groupie 用来展示和管理复杂的 RecyclerView 布局,此第三方库在我厂的几款 App 中多个模块里都有所应用。个人也是从一开始的一无所知,到现在的大体了解,感觉还是蛮好用的。这期就来解读一番这个灵活的 RecyclerView 第三方库。

简介 Groupie

Groupie 帮助我们可以将展示的内容看作逻辑组,再处理变动的通知,认为该片段是有着头部、尾部、可展开的组和垂直列的块,甚至更多。其使得处理异步的内容更新和插入,及用户驱动的内容变化变得很容易。在 item 层,其抽象出 item 视图类型、item 布局、viewholders 和跨度大小。这个库目前支持 databinding 和 Kotlin 相关的,暂且先不管,我们目前先只看 Java 层的通常用法及 library 的源码。

以上来自 GitHub,此库由 MIT 高材生、原 Google 程序媛 Lisa Wray 所写。

Demo 用法

Demo 地址:GroupieDemo

引入 Gradle 依赖

1
compile 'com.xwray:groupie:2.0.0-alpha2'

MainActivity 类如下:

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
public class MainActivity extends AppCompatActivity {

private GroupAdapter mAdapter;
private RecyclerView mRvContent;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mRvContent = findViewById(R.id.rv_content);
mRvContent.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new GroupAdapter();
// 添加非空的组内容
mAdapter.addAll(getGroupList());
mRvContent.setAdapter(mAdapter);
}

private List<Item> getGroupList() {
List<Item> groupList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 遍历添加 ContentItem
groupList.add(new ContentItem());
}
return groupList;
}

}

ContentItem 继承自 Groupie 库里的 Item 类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ContentItem extends Item<ContentViewHolder> {

@NonNull
@Override
public ContentViewHolder createViewHolder(@NonNull View itemView) {
return new ContentViewHolder(itemView);
}

@Override
public void bind(@NonNull ContentViewHolder viewHolder, int position) {
viewHolder.setTitle();
viewHolder.setSubtitle();
}

@Override
public int getLayout() {
return R.layout.item_content;
}

}

getLayout() 方法里传入定义的布局,bind() 里绑定相应的数据。同样,ContentViewHolder 继承自 Groupie 里的 ViewHolder 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ContentViewHolder extends ViewHolder {

@NonNull
private TextView mTvTitle;
@NonNull
private TextView mTvSubtitle;

public ContentViewHolder(@NonNull View rootView) {
super(rootView);
mTvTitle = rootView.findViewById(R.id.tv_title);
mTvSubtitle = rootView.findViewById(R.id.tv_subtitle);
}

public void setTitle() {
mTvTitle.setText("Hello Google");
mTvTitle.setTextSize(28);
}

public void setSubtitle() {
mTvSubtitle.setText("Hello Android");
mTvSubtitle.setTextSize(20);
}

}

主要是作一些 View 层的操作。

运行程序如下:

或许有人觉得,就一个简单的 RecyclerView 使用嘛,何必大动干戈,再来看看这张库的 Readme 图:

配套功能还是可以的,从简单使用着手,慢慢了解、熟悉乃至灵活应用,就可以应付我们开发中的大部分需求啦。当然,不能仅限于会使用,接下来跟进去,读读这个库 library 的源码。

源码解读

首先,看 Group 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// adapter 里使用的一组 items
public interface Group {

// 获取 item 的数目
int getItemCount();

@NonNull
Item getItem(int position);

// 获取 item 的位置
int getPosition(@NonNull Item item);

// 注册组数据观察者
void registerGroupDataObserver(@NonNull GroupDataObserver groupDataObserver);

// 注销组数据观察者
void unregisterGroupDataObserver(@NonNull GroupDataObserver groupDataObserver);

}

再看所谓的组数据观察者 GroupDataObserver 接口:

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
public interface GroupDataObserver {

// 整个组变动
void onChanged(@NonNull Group group);

// 组里某位置插入 item
void onItemInserted(@NonNull Group group, int position);

// 组里某位置 item 的变动
void onItemChanged(@NonNull Group group, int position);

// 组里某位置 item 负载对象的变动
void onItemChanged(@NonNull Group group, int position, Object payload);

// 组里某位置移除 item
void onItemRemoved(@NonNull Group group, int position);

// 组里某个范围 items 的变动
void onItemRangeChanged(@NonNull Group group, int positionStart, int itemCount);

// 组里某个范围插入 items
void onItemRangeInserted(@NonNull Group group, int positionStart, int itemCount);

// 组里某个范围移除 items
void onItemRangeRemoved(@NonNull Group group, int positionStart, int itemCount);

// 组里某个范围移除 items
void onItemMoved(@NonNull Group group, int fromPosition, int toPosition);

}

紧接着,看剩下的几个接口,SpanSizeProvider:

1
2
3
4
5
6
public interface SpanSizeProvider {

// 获取跨度大小
int getSpanSize(int spanCount, int position);

}

ExpandableItem 接口:

1
2
3
4
5
6
public interface ExpandableItem {

// 设置可展开的组
void setExpandableGroup(@NonNull ExpandableGroup onToggleListener);

}

最后,就是两个 Listener 了,分别为普通的 item 点击 Listener 和 长点 Listener:

1
2
3
4
5
public interface OnItemClickListener {

void onItemClick(@NonNull Item item, @NonNull View view);

}
1
2
3
4
5
public interface OnItemLongClickListener {

boolean onItemLongClick(@NonNull Item item, @NonNull View view);

}

了解完相关的接口,接下来,重点看看一些类。先重点看 GroupAdapter,其持有 Group List,继承自 RecyclerView 的 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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
public class GroupAdapter<VH extends ViewHolder> extends RecyclerView.Adapter<VH> implements GroupDataObserver {

private final List<Group> groups = new ArrayList<>();
private OnItemClickListener onItemClickListener;
private OnItemLongClickListener onItemLongClickListener;
private int spanCount = 1;
private Item lastItemForViewTypeLookup;
// 提供每个 item 占据的跨度数目
private final GridLayoutManager.SpanSizeLookup spanSizeLookup = new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
try {
return getItem(position).getSpanSize(spanCount, position);
} catch (IndexOutOfBoundsException e) {
return spanCount;
}
}
};

// ...

// 在 item isClickable() 为 true 的地方注册点击监听器
public void setOnItemClickListener(@Nullable OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}

// 同理,在 item isLongClickable() 为 true 的地方注册长点击监听器
public void setOnItemLongClickListener(@Nullable OnItemLongClickListener onItemLongClickListener) {
this.onItemLongClickListener = onItemLongClickListener;
}

@Override
@NonNull
public VH onCreateViewHolder(@NonNull ViewGroup parent, int layoutResId) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
// 根据 layoutResId 获取给定 ViewType 的 item
Item<VH> item = getItemForViewType(layoutResId);
View itemView = inflater.inflate(layoutResId, parent, false);
return item.createViewHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull VH holder, int position) {

}

@Override
public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) {
Item contentItem = getItem(position);
contentItem.bind(holder, position, payloads, onItemClickListener, onItemLongClickListener);
}

@Override
public void onViewRecycled(@NonNull VH holder) {
Item contentItem = holder.getItem();
// 数据解绑
contentItem.unbind(holder);
}

@Override
public boolean onFailedToRecycleView(@NonNull VH holder) {
Item contentItem = holder.getItem();
// 可回收 contentItem
return contentItem.isRecyclable();
}

@Override
public int getItemViewType(int position) {
lastItemForViewTypeLookup = getItem(position);
if (lastItemForViewTypeLookup == null)
throw new RuntimeException("Invalid position " + position);
return lastItemForViewTypeLookup.getLayout();
}

@NonNull
public Item getItem(@NonNull VH holder) {
return holder.getItem();
}

@NonNull
public Item getItem(int position) {
int count = 0;
for (Group group : groups) {
if (position < count + group.getItemCount()) {
return group.getItem(position - count);
} else {
count += group.getItemCount();
}
}
throw new IndexOutOfBoundsException("Requested position " + position + "in group adapter " +
"but there are only " + count + " items");
}

public int getAdapterPosition(@NonNull Item contentItem) {
int count = 0;
for (Group group : groups) {
int index = group.getPosition(contentItem);
if (index >= 0) return index + count;
count += group.getItemCount();
}
return -1;
}

// 展开 list 中组开始 item 的位置
public int getAdapterPosition(@NonNull Group group) {
int index = groups.indexOf(group);
int position = 0;
for (int i = 0; i < index; i++) {
position += groups.get(i).getItemCount();
}
return position;
}

@Override
public int getItemCount() {
int count = 0;
for (Group group : groups) {
count += group.getItemCount();
}
return count;
}

public int getItemCount(int groupIndex) {
if (groupIndex >= groups.size()) {
throw new IndexOutOfBoundsException("Requested group index " + groupIndex + " but there are " + groups.size() + " groups");
}
return groups.get(groupIndex).getItemCount();
}

// 清除操作
public void clear() {
for (Group group : groups) {
group.registerGroupDataObserver(null);
}
groups.clear();
// 原生的,通知注册的观察者数据集发生变化
notifyDataSetChanged();
}

// 添加组
public void add(@NonNull Group group) {
if (group == null) throw new RuntimeException("Group cannot be null");
int itemCountBeforeGroup = getItemCount();
group.registerGroupDataObserver(this);
groups.add(group);
notifyItemRangeInserted(itemCountBeforeGroup, group.getItemCount());
}

// 顺序添加组列表的内容,list 中的组须是非空的
public void addAll(@NonNull Collection<? extends Group> groups) {
if (groups.contains(null)) throw new RuntimeException("List of groups can't contain null!");
int itemCountBeforeGroup = getItemCount();
int additionalSize = 0;
for (Group group : groups) {
additionalSize += group.getItemCount();
group.registerGroupDataObserver(this);
}
this.groups.addAll(groups);
notifyItemRangeInserted(itemCountBeforeGroup, additionalSize);
}

// ... 一些移除操作的方法

public void add(@NonNull int index, Group group) {
if (group == null) throw new RuntimeException("Group cannot be null");
group.registerGroupDataObserver(this);
groups.add(index, group);
int itemCountBeforeGroup = getItemCountBeforeGroup(index);
notifyItemRangeInserted(itemCountBeforeGroup, group.getItemCount());
}

// 获取列表中组的位置
@NonNull
private Group getGroup(int position) {
int previous = 0;
int size;
for (Group group : groups) {
size = group.getItemCount();
if (position - previous < size) return group;
previous += group.getItemCount();
}
throw new IndexOutOfBoundsException("Requested position " + position + "in group adapter " +
"but there are only " + previous + " items");
}

// ... 组操作

@Override
public void onChanged(@NonNull Group group) {
notifyItemRangeChanged(getAdapterPosition(group), group.getItemCount());
}

// ... item 变动操作

@Override
public void onItemMoved(@NonNull Group group, int fromPosition, int toPosition) {
int groupAdapterPosition = getAdapterPosition(group);
notifyItemMoved(groupAdapterPosition + fromPosition, groupAdapterPosition + toPosition);
}

// 找到给定视图类型的 model,再为其创建 viewholder,依赖 RecyclerView 的实现细节,
// getItemViewType() 在 onCreateViewHolder() 之前调用。对查找过的视图类型作缓存,
// 除非实现变化了,否则希望能快速到相应的 model。为了保障安全,折中查找所有匹配视图类型
// 的 model,虽然慢且或许不必要,但却是 RecyclerView 行为变化的保障
private Item<VH> getItemForViewType(@LayoutRes int layoutResId) {
// 是最后查找 ViewType 的 item
if (lastItemForViewTypeLookup != null
&& lastItemForViewTypeLookup.getLayout() == layoutResId) {
// We expect this to be a hit 100% of the time
return lastItemForViewTypeLookup;
}

// 保障 RecyclerView 实现细节变化时的安全
for (int i = 0; i < getItemCount(); i++) {
Item item = getItem(i);
if (item.getLayout() == layoutResId) {
return item;
}
}

throw new IllegalStateException("Could not find model for view type: " + layoutResId);
}

}

再看我们 ContentItem 所继承的 Item,其实现了前文的 Group 和 SpanSizeProvider 接口:

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
public abstract class Item<VH extends ViewHolder> implements Group, SpanSizeProvider {

private static AtomicLong ID_COUNTER = new AtomicLong(0);
protected GroupDataObserver parentDataObserver;
private final long id;
// ViewHolder 里存储的键值对集,以利于区分同样视图类型的 items
private Map<String, Object> extras = new HashMap<>();

public Item() {
this(ID_COUNTER.decrementAndGet());
}

protected Item(long id) {
this.id = id;
}

@NonNull
public VH createViewHolder(@NonNull View itemView) {
return (VH) new ViewHolder(itemView);
}

@CallSuper
public void bind(@NonNull VH holder, int position, @NonNull List<Object> payloads,
@Nullable OnItemClickListener onItemClickListener,
@Nullable OnItemLongClickListener onItemLongClickListener)
{

holder.bind(this, onItemClickListener, onItemLongClickListener);
bind(holder, position, payloads);
}

public abstract void bind(@NonNull VH viewHolder, int position);

// 不在实现里指定如何处理 payloads 的话,其将被忽略,且 adapter 作全面的重新绑定
public void bind(@NonNull VH holder, int position, @NonNull List<Object> payloads) {
bind(holder, position);
}

// 作清理以求 viewholder 被重用
@CallSuper
public void unbind(@NonNull VH holder) {
holder.unbind();
}

public boolean isRecyclable() {
return true;
}

// ... 一些 get 操作

@Override
public void registerGroupDataObserver(@NonNull GroupDataObserver groupDataObserver) {
this.parentDataObserver = groupDataObserver;
}

@Override
public void unregisterGroupDataObserver(@NonNull GroupDataObserver groupDataObserver) {
parentDataObserver = null;
}

@Override
public int getPosition(@NonNull Item item) {
return this == item ? 0 : -1;
}

public boolean isClickable() {
return true;
}

public boolean isLongClickable() {
return true;
}

public void notifyChanged() {
if (parentDataObserver != null) {
parentDataObserver.onItemChanged(this, 0);
}
}

public void notifyChanged(@Nullable Object payload) {
if (parentDataObserver != null) {
parentDataObserver.onItemChanged(this, 0, payload);
}
}

// ViewHolder 里存储的键值对集,以利于区分同样视图类型的 items
public Map<String, Object> getExtras() {
return extras;
}

// 若不指定 id,则每个 item 会自动生成一个特殊的负值 id,几乎不会与 model IDs 冲突。
// 最好重写其,给定一个 model 对象的 id,比如数据库中一个对象代表的基键,用来判定同样
// 视图类型的 items,在使用 DiffUtil 的比较下,是否真正相同。
public long getId() {
return id;
}

}

接着看 ViewHolder,其继承自 RecyclerView 的 ViewHolder:

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
public class ViewHolder extends RecyclerView.ViewHolder {

private Item item;
private OnItemClickListener onItemClickListener;
private OnItemLongClickListener onItemLongClickListener;

private View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(@NonNull View v) {
// viewholder 被移除则放弃点击,但仍然在点击移除的动画过程中
if (onItemClickListener != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
onItemClickListener.onItemClick(getItem(), v);
}
}
};

private View.OnLongClickListener onLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(@NonNull View v) {
// viewholder 被移除则放弃长点击,但仍然在长点击移除的动画过程中
if (onItemLongClickListener != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
return onItemLongClickListener.onItemLongClick(getItem(), v);
}
return false;
}
};

public ViewHolder(@NonNull View rootView) {
super(rootView);
}

public void bind(@NonNull Item item, @Nullable OnItemClickListener onItemClickListener, @Nullable OnItemLongClickListener onItemLongClickListener) {
this.item = item;

// 只设置顶层的点击监听器当其存在,且点击 enabled,这确保不会和用户设置的点击监听器
// 产生干扰。最好将监听器设置为总是 attached,在创建 viewholder 时再设置监听器,但是
// 要注意的是,同样布局类型的不同 items 无法有着同样的点击监听器,设置决定是否是可以点击的。
if (onItemClickListener != null && item.isClickable()) {
itemView.setOnClickListener(onClickListener);
this.onItemClickListener = onItemClickListener;
}

if (onItemLongClickListener != null && item.isLongClickable()) {
itemView.setOnLongClickListener(onLongClickListener);
this.onItemLongClickListener = onItemLongClickListener;
}
}

public void unbind() {
// 先前设置监听器的话,此处将顶级的点击监听器设置为 null,避免取消任何点击监听器,
// 用户或许会设置其为和 viewholder 生命周期一致
if (onItemClickListener != null && item.isClickable()) {
itemView.setOnClickListener(null);
}
if (onItemLongClickListener != null && item.isLongClickable()) {
itemView.setOnLongClickListener(null);
}
this.item = null;
this.onItemClickListener = null;
this.onItemLongClickListener = null;
}

// ... 一些 get 操作

}

Demo 中常用的 GroupAdapter、Item 和 ViewHolder 介绍完毕,延伸来看实现 Group 和 GroupDataObserver 接口的 NestedGroup:

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// 支持 Groups 随意深度的嵌套,可以使得嵌套 Group 只包含 items,其包含 Groups 或混合物
// 此外,其提供子 groups 发生变化传送给 adapter 的通知。
public abstract class NestedGroup implements Group, GroupDataObserver {

private final GroupDataObservable observable = new GroupDataObservable();

public int getItemCount() {
int size = 0;
for (int i = 0; i < getGroupCount(); i++) {
Group group = getGroup(i);
size += group.getItemCount();
}
return size;
}

// ... 一些 get 操作

@NonNull
public Item getItem(int position) {
int previousPosition = 0;

for (int i = 0; i < getGroupCount(); i++) {
Group group = getGroup(i);
int size = group.getItemCount();
if (size + previousPosition > position) {
return group.getItem(position - previousPosition);
}
previousPosition += size;
}

throw new IndexOutOfBoundsException("Wanted item at " + position + " but there are only "
+ getItemCount() + " items");
}

public final int getPosition(@NonNull Item item) {
int previousPosition = 0;

for (int i = 0; i < getGroupCount(); i++) {
Group group = getGroup(i);
int position = group.getPosition(item);
if (position >= 0) {
return position + previousPosition;
}
previousPosition += group.getItemCount();
}

return -1;
}

public abstract int getPosition(@NonNull Group group);

@Override
public final void registerGroupDataObserver(@NonNull GroupDataObserver groupDataObserver) {
observable.registerObserver(groupDataObserver);
}

@Override
public void unregisterGroupDataObserver(@NonNull GroupDataObserver groupDataObserver) {
observable.unregisterObserver(groupDataObserver);
}

@CallSuper
public void add(@NonNull Group group) {
group.registerGroupDataObserver(this);
}

// ... 一些添加操作

@CallSuper
public void addAll(int position, @NonNull Collection<? extends Group> groups) {
for (Group group : groups) {
group.registerGroupDataObserver(this);
}
}

@CallSuper
public void remove(@NonNull Group group) {
group.unregisterGroupDataObserver(this);
}

@CallSuper
public void removeAll(@NonNull Collection<? extends Group> groups) {
for (Group group : groups) {
group.unregisterGroupDataObserver(this);
}
}

// group 里的 item 仍然存在,但其数据发生了变化
@CallSuper
@Override
public void onChanged(@NonNull Group group) {
observable.onItemRangeChanged(this, getItemCountBeforeGroup(group), group.getItemCount());
}

// ...

@CallSuper
@Override
public void onItemMoved(@NonNull Group group, int fromPosition, int toPosition) {
int groupPosition = getItemCountBeforeGroup(group);
observable.onItemMoved(this, groupPosition + fromPosition, groupPosition + toPosition);
}

// group 使用该方法通知其自己发生了变化
@CallSuper
public void notifyItemRangeInserted(int positionStart, int itemCount) {
observable.onItemRangeInserted(this, positionStart, itemCount);
}

// ...

@CallSuper
public void notifyItemRangeChanged(int positionStart, int itemCount) {
observable.onItemRangeChanged(this, positionStart, itemCount);
}

// 反序迭代以防任一观察者在它们的回调中决定移除其自身
private static class GroupDataObservable {
final List<GroupDataObserver> observers = new ArrayList<>();

// ... 一些变动操作

void registerObserver(GroupDataObserver observer) {
synchronized (observers) {
if (observers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
observers.add(observer);
}
}

void unregisterObserver(GroupDataObserver observer) {
synchronized (observers) {
int index = observers.indexOf(observer);
observers.remove(index);
}
}
}

}

再看继承自嵌套 Group 的 Section:

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
public class Section extends NestedGroup {

@Nullable
private Group header;

@Nullable
private Group footer;

@Nullable
private Group placeholder;

private final ArrayList<Group> children = new ArrayList<>();

private boolean hideWhenEmpty = false;

private boolean isHeaderAndFooterVisible = true;

private boolean isPlaceholderVisible = false;

public Section() {
this(null, new ArrayList<Group>());
}

// ...

public Section(@Nullable Group header, @NonNull Collection<? extends Group> children) {
this.header = header;
addAll(children);
}

@Override
public void add(int position, @NonNull Group group) {
super.add(position, group);
children.add(position, group);
final int notifyPosition = getHeaderItemCount() + getItemCount(children.subList(0, position));
notifyItemRangeInserted(notifyPosition, group.getItemCount());
refreshEmptyState();
}

// ... add() 操作

@Override
public void add(@NonNull Group group) {
super.add(group);
int position = getItemCountWithoutFooter();
children.add(group);
notifyItemRangeInserted(position, group.getItemCount());
refreshEmptyState();
}

@Override
public void remove(@NonNull Group group) {
super.remove(group);
int position = getItemCountBeforeGroup(group);
children.remove(group);
notifyItemRangeRemoved(position, group.getItemCount());
refreshEmptyState();
}

// ...

// section's body 为空时设置 placeholder
public void setPlaceholder(@NonNull Group placeholder) {
if (placeholder == null)
throw new NullPointerException("Placeholder can't be null. Please use removePlaceholder() instead!");
if (this.placeholder != null) {
removePlaceholder();
}
this.placeholder = placeholder;
refreshEmptyState();
}

public void removePlaceholder() {
hidePlaceholder();
this.placeholder = null;
}

private void showPlaceholder() {
if (isPlaceholderVisible || placeholder == null) return;
isPlaceholderVisible = true;
notifyItemRangeInserted(getHeaderItemCount(), placeholder.getItemCount());
}

private void hidePlaceholder() {
if (!isPlaceholderVisible || placeholder == null) return;
isPlaceholderVisible = false;
notifyItemRangeRemoved(getHeaderItemCount(), placeholder.getItemCount());
}

protected boolean isEmpty() {
return children.isEmpty() || getItemCount(children) == 0;
}

private void hideDecorations() {
if (!isHeaderAndFooterVisible && !isPlaceholderVisible) return;
int count = getHeaderItemCount() + getPlaceholderItemCount() + getFooterItemCount();
isHeaderAndFooterVisible = false;
isPlaceholderVisible = false;
notifyItemRangeRemoved(0, count);
}

protected void refreshEmptyState() {
boolean isEmpty = isEmpty();
if (isEmpty) {
if (hideWhenEmpty) {
hideDecorations();
} else {
showPlaceholder();
showHeadersAndFooters();
}
} else {
hidePlaceholder();
showHeadersAndFooters();
}
}

private void showHeadersAndFooters() {
if (isHeaderAndFooterVisible) return;
isHeaderAndFooterVisible = true;
notifyItemRangeInserted(0, getHeaderItemCount());
notifyItemRangeInserted(getItemCountWithoutFooter(), getFooterItemCount());
}

private int getBodyItemCount() {
return isPlaceholderVisible ? getPlaceholderItemCount() : getItemCount(children);
}

// ... 一些 get 数目操作

private int getPlaceholderCount() {
return isPlaceholderVisible ? 1 : 0;
}

@Override
@NonNull
public Group getGroup(int position) {
if (isHeaderShown() && position == 0) return header;
position -= getHeaderCount();
if (isPlaceholderShown() && position == 0) return placeholder;
position -= getPlaceholderCount();
if (position == children.size()) {
if (isFooterShown()) {
return footer;
} else {
throw new IndexOutOfBoundsException("Wanted group at position " + position +
" but there are only " + getGroupCount() + " groups");
}
} else {
return children.get(position);
}
}

@Override
public int getGroupCount() {
return getHeaderCount() + getFooterCount() + getPlaceholderCount() + children.size();
}

@Override
public int getPosition(@NonNull Group group) {
int count = 0;
if (isHeaderShown()) {
if (group == header) return count;
}
count += getHeaderCount();
if (isPlaceholderShown()) {
if (group == placeholder) return count;
}
count += getPlaceholderCount();

int index = children.indexOf(group);
if (index >= 0) return count + index;
count += children.size();

if (isFooterShown()) {
if (footer == group) {
return count;
}
}

return -1;
}

// header, footer 和 placeholder 是否显示的布尔方法

// header 操作
public void setHeader(@NonNull Group header) {
if (header == null)
throw new NullPointerException("Header can't be null. Please use removeHeader() instead!");
int previousHeaderItemCount = getHeaderItemCount();
this.header = header;
notifyHeaderItemsChanged(previousHeaderItemCount);
}

public void removeHeader() {
int previousHeaderItemCount = getHeaderItemCount();
this.header = null;
notifyHeaderItemsChanged(previousHeaderItemCount);
}

private void notifyHeaderItemsChanged(int previousHeaderItemCount) {
int newHeaderItemCount = getHeaderItemCount();
if (previousHeaderItemCount > 0) {
notifyItemRangeRemoved(0, previousHeaderItemCount);
}
if (newHeaderItemCount > 0) {
notifyItemRangeInserted(0, newHeaderItemCount);
}
}

// footer 操作
public void setFooter(@NonNull Group footer) {
if (footer == null)
throw new NullPointerException("Footer can't be null. Please use removeFooter() instead!");
int previousFooterItemCount = getFooterItemCount();
this.footer = footer;
notifyFooterItemsChanged(previousFooterItemCount);
}

public void removeFooter() {
int previousFooterItemCount = getFooterItemCount();
this.footer = null;
notifyFooterItemsChanged(previousFooterItemCount);
}

private void notifyFooterItemsChanged(int previousFooterItemCount) {
int newFooterItemCount = getFooterItemCount();
if (previousFooterItemCount > 0) {
notifyItemRangeRemoved(getItemCountWithoutFooter(), previousFooterItemCount);
}
if (newFooterItemCount > 0) {
notifyItemRangeInserted(getItemCountWithoutFooter(), newFooterItemCount);
}
}

// ... item 变动方法

}

同样看看继承自嵌套 Group 的可展开的 ExpandableGroup:

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
public class ExpandableGroup extends NestedGroup {

private boolean isExpanded = false;
private final Group parent;
private final List<Group> children = new ArrayList<>();

public ExpandableGroup(Group expandableItem) {
this.parent = expandableItem;
((ExpandableItem) expandableItem).setExpandableGroup(this);
}

public ExpandableGroup(Group expandableItem, boolean isExpanded) {
this.parent = expandableItem;
((ExpandableItem) expandableItem).setExpandableGroup(this);
this.isExpanded = isExpanded;
}

@Override
public void add(@NonNull Group group) {
super.add(group);
if (isExpanded) {
int itemCount = getItemCount();
children.add(group);
notifyItemRangeInserted(itemCount, group.getItemCount());
} else {
children.add(group);
}
}

@Override
public void addAll(@NonNull Collection<? extends Group> groups) {
if (groups.isEmpty()) return;
super.addAll(groups);
if (isExpanded) {
int itemCount = getItemCount();
this.children.addAll(groups);
notifyItemRangeInserted(itemCount, getItemCount(groups));
} else {
this.children.addAll(groups);
}
}

@Override
public void addAll(int position, @NonNull Collection<? extends Group> groups) {
if (groups.isEmpty()) {
return;
}
super.addAll(position, groups);
if (isExpanded) {
int itemCount = getItemCount();
this.children.addAll(position, groups);
notifyItemRangeInserted(itemCount, getItemCount(groups));
} else {
this.children.addAll(position, groups);
}
}

public boolean isExpanded() {
return isExpanded;
}

@NonNull
public Group getGroup(int position) {
if (position == 0) {
return parent;
} else {
return children.get(position - 1);
}
}

@Override
public int getPosition(@NonNull Group group) {
if (group == parent) {
return 0;
} else {
return 1 + children.indexOf(group);
}
}

public int getGroupCount() {
return 1 + (isExpanded ? children.size() : 0);
}

// 触发展开
public void onToggleExpanded() {
int oldSize = getItemCount();
isExpanded = !isExpanded;
int newSize = getItemCount();
if (oldSize > newSize) {
notifyItemRangeRemoved(newSize, oldSize - newSize);
} else {
notifyItemRangeInserted(oldSize, newSize - oldSize);
}
}

private boolean dispatchChildChanges(Group group) {
return isExpanded || group == parent;
}

// ... item 变动的一些方法

}

还有继承自嵌套 Group 的 UpdatingGroup:

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
// 接受一列表 items 的 group,其和之前的内容不同,生成相应的移除、添加、移动和变动通知给其父观察者,
// 创建带动效的 item 层更新。(1) 通过 Item.getId() 判断 items 是否相同;(2) 通过 Item.equals()
// 判断内容是否相同。若未自定义 getId() 和 equals() 方法,则默认的实现将返回 false,意味着 Group
// 考虑每一完整事项的更新。
public class UpdatingGroup extends NestedGroup {

// List 更新回调
private ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}

@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}

@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}

@Override
public void onChanged(int position, int count, Object payload) {
notifyItemRangeChanged(position, count);
}
};

private List<Item> items = new ArrayList<>();

public void update(@NonNull List<? extends Item> newItems) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UpdatingCallback(newItems));
super.removeAll(items);
items.clear();
super.addAll(newItems);
items.addAll(newItems);
diffResult.dispatchUpdatesTo(listUpdateCallback);
}

@Override
@NonNull
public Group getGroup(int position) {
return items.get(position);
}

@Override
public int getGroupCount() {
return items.size();
}

@Override
public int getPosition(@NonNull Group group) {
if (group instanceof Item) {
return items.indexOf(group);
} else {
return -1;
}
}

// 更新回调
private class UpdatingCallback extends DiffUtil.Callback {

private List<? extends Item> newList;

UpdatingCallback(List<? extends Item> newList) {
this.newList = newList;
}

@Override
public int getOldListSize() {
return items.size();
}

@Override
public int getNewListSize() {
return newList.size();
}

@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Item oldItem = items.get(oldItemPosition);
Item newItem = newList.get(newItemPosition);
if (oldItem.getLayout() != newItem.getLayout()) {
return false;
}
return oldItem.getId() == newItem.getId();
}

@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Item oldItem = items.get(oldItemPosition);
Item newItem = newList.get(newItemPosition);
return oldItem.equals(newItem);
}
}

}

最后的最后,是一个触碰回调类 TouchCallback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class TouchCallback extends ItemTouchHelper.SimpleCallback {

public TouchCallback() {
super(0, 0);
}

@Override
public int getSwipeDirs(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
// 调用 ViewHolder 里的方法
return ((ViewHolder) viewHolder).getSwipeDirs();
}

@Override
public int getDragDirs(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
// 调用 ViewHolder 里的方法
return ((ViewHolder) viewHolder).getDragDirs();
}

}

至此,第三方库 Groupie 的简单解读到此完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流