上一篇说到Tabs+ViewPager+ListView是最常见的组合,这篇就议一议如何用RecyclerView快速实现列表页面。

如一个简单的列表场景:TodoList。

分页加载现有Todo
现有数据基础上增、删、改

RecyclerView的使用在此就不赘述了,本文主要讨论RecyclerView.Adapter的实现

使用最简单的ArrayList实现,如下:

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
class ListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
final ArrayList<Item> mData;
final LayoutInflater mLayoutInflater;
public SortedListAdapter(Context context) {
mLayoutInflater = LayoutInflater.from(context);
mData = new ArrayList<>();
}

public void addItem(Item item) {
mData.add(item); // 需要自己通知更新
}

@Override
public TodoViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
return new TodoViewHolder (
mLayoutInflater.inflate(R.layout.list_todo_item, parent, false));
}

@Override
public void onBindViewHolder(TodoViewHolder holder, int position) {
holder.bindTo(mData.get(position));
}

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

这样的Adapter一个显而易见的问题就是,如何做数据的去重

  1. 添加一项数据:最简单的是在addItem()之前,遍历一次mData,定位后再决定是插入还是更新现有数据,并调用notifyItemInserted(pos)
  2. 添加多个数据:多次重复上面的方法…

对于少量数据来说这样做并不见得有什么问题,而且写得多了,都有自己封装好的诸如ArrayObjectAdapter之类方便使用。

这样就够了吗?

答案肯定是不。Android Support Library 悄悄给我们提供了一个叫SortedList的工具类,它默默的藏在support库的角落中,鲜为人知。

SortedList?

文档对它的定义:

  1. 是一个有序列表
  2. 数据变动会触发回调SortedList.Callback的方法,如onChanged()

构造一个SortedList需要实现它的回调SortedList.Callback,并由其来定义数据的排序和数据的唯一性。
它有一个实现类SortedListAdapterCallback就是RecyclerView.AdapterSortedList交互的秘密武器。

示例

改造后的ListAdapter:

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
class SortedListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
final SortedList<Item> mData;
final LayoutInflater mLayoutInflater;
public SortedListAdapter(Context context) {
mLayoutInflater = LayoutInflater.from(context);
mData = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(this){
@Override
public int compare(Item t0, Item t1) {
// 实现这个方法来定义Item的显示顺序
int txtComp = t0.mText.compareTo(t1.mText);
if (txtComp != 0) {
return txtComp;
}
if (t0.id < t1.id) {
return -1;
} else if (t0.id > t1.id) {
return 1;
}
return 0;
}

@Override
public boolean areContentsTheSame(Item oldItem,
Item newItem) {
// 比较两个Item的内容是否一致,如不一致则会调用adapter的notifyItemChanged()
return oldItem.mText.equals(newItem.mText);
}

@Override
public boolean areItemsTheSame(Item item1, Item item2) {
// 两个Item是不是同一个东西,
// 它们的内容或许不一样,但id相同代表就是同一个
return item1.id == item2.id;
}
});
}

public void addItem(Item item) {
mData.add(item);
// 会通过SortedListAdapterCallback自动通知更新
}

...

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

虽然相对ListAdapter代码量变多了,但是调用者却再也不用关心数据的去重与通知更新的问题。这一切都有SortedListAdapterCallback帮你自动处理好了。

单单只这一个好处其实并不值得劳师动众去改掉现有Adapter使用SortedList,但它另外还有一个令人称赞并喜爱的功能:批量更新(Batched Updates)

就如实现添加多个数据:

1
2
3
4
5
void addItems(List<Item> items){
mData.beginBatchedUpdates(); // 开始批量更新
mData.addAll(items); // 更新一批数据
mData.endBatchedUpdates(); // 结束更新
}

批量删除:

1
2
3
4
5
6
7
void deleteItems(List<Item> items){
mData.beginBatchedUpdates(); // 开始批量更新
for(Item item : items){ // 删除一批数据
mData.remove(item);
}
mData.endBatchedUpdates(); // 结束更新
}

等等。

如例子所示,调用beginBatchedUpdates()之后,所有的对SortedList操作都会等到endBatchedUpdates()之后一起生效。

完整的示例见:官方Support库Samples ($ANDROID_SDK/extras/android/support/samples/Support7Demos)

More Tips

  1. 列表无序的情况,可以用其id或原始数据List的index来比较排序,只要确保能正确实现compare即可
  2. 如无需批量更新,或无频繁的增删改,其实用前面的ListAdapter比较好。

总之:如果你的列表需要批量更新或者频繁删改,且刚好有明确的先后顺序,快使用SortedList