上次讲到实现RecyclerView的功能扩展,本次就接着介绍针对RecyclerView.Adapter的封装的一些想法。

1、功能介绍

随着移动端的蓬勃发展,复杂的产品布局也变成了家常便饭,复杂的布局排列组合也成了常态。

比如在拿到这个需求之后,如果放在之前我大多是用ScrollView或者ListView的多Type类型来实现。

如今,采用RecyclerView可以实现一个性能更优的方案,RecyclerView提供了一种插拔式的架构设计,高度的解耦,异常的灵活,也因为这些特性需要调用者自己来实现的代码就相对多一些,所以这也激发了自己想进一步封装一个更好用并且不失灵活性的Adapter

当然,对于实现以上需求还配合了ItemDecoration来实现间隔,这里就先按下不表。

  • 支持数据源的添加/删除/重置
  • 支持多类型布局
  • 添加/移除头部和尾部
  • 支持添加点击事件
  • 配合TurboRecyclerView上拉/左滑加载

接下来,针对以上需求展开介绍。

2、数据源的添加/删除/重置

大家知道RecyclerView.Adapter是RecyclerView与数据之间的桥梁,所以针对数据源我的想法是在Adapter中维护一个List<T>,这样可以不依赖外部数据源,保证List对Adapter的可见性,在Adapter的封装中仅提供增、删以及重置的方法,所有对数据源操作都通过Adapter中提供的接口来实现增删。

1
2
3
4
5
6
protected List<T> mData;
...
public BaseTurboAdapter(Context context, List<T> data) {
this.mData = data == null ? new ArrayList<T>() : new ArrayList<T>(data);
...
}

下面我们来看看添加数据的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void add(T item) {
boolean isAdd = mData.add(item);
if (isAdd)
notifyItemInserted(mData.size() + getHeaderViewCount());
}

public void add(int position, T item) {
if (position < 0 || position > mData.size()) {
Log.e(TAG, "add position = " + position + ", IndexOutOfBounds, please check your code!");
return;
}
mData.add(position, item);
notifyItemInserted(position + getHeaderViewCount());
}

public void addData(List<T> data) {
if (data != null) {
this.mData.addAll(data);
}
notifyDataSetChanged();
}

这里需要提一下,针对add(postion,T)中的position是整个Adapter中getItemCount()后需要减去头部的计数,后面会讲到。

删除数据的接口与添加类似,就不在贴代码了,这里我们又提供一个重置数据的方法:

1
2
3
4
public void resetData(List<T> data) {
mData.clear();
addData(data);
}

一般有重新刷新时,请调用这个方法重置数据源。

3、添加/移除头部和尾部

RecyclerView是一个性能更优的控件,所以对多布局的支持也更好。Header和Footer是配合getItemViewType(position)来实现,我们知道Header、Footer的特点,所以对于头部和尾部的Type类型处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public final int getItemViewType(int position) {
if (mHeaderView != null && position == 0) {
return TYPE_HEADER_VIEW;
} else if (position == mData.size() + getHeaderViewCount()) {
if (mLoading) {
...
} else if (mFooterView != null) {
return TYPE_FOOTER_VIEW;
}
}
...
}

添加/移除头部和尾部会对ItemCount有影响,所以我们需要实现getItemCount,先来看看源码上的注解。

1
2
3
4
5
6
/**
* Returns the total number of items in the data set hold by the adapter.
*
* @return The total number of items in this adapter.
*/

public abstract int getItemCount();

来看看具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public int getItemCount() {

int count;
if (mLoading) { //if loading ignore footer view
count = mData.size() + 1 + getHeaderViewCount();
} else {
count = mData.size() + getHeaderViewCount() + getFooterViewCount();
}
...
return count;
}

在数据源的基础上计算Header以及Footer的数量并返回。

最后我们来看下针对Header添加、移除(Footer同理):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private View mHeaderView;
...
public void addHeaderView(View header) {
if (header == null) {
Log.e(TAG, "header is null!!!");
return;
}
this.mHeaderView = header;
this.notifyDataSetChanged();
}

public void removeHeaderView() {
if (mHeaderView != null) {
this.mHeaderView = null;
this.notifyDataSetChanged();
}
}

4、点击事件的处理

在刚开始写这个项目的初期,其实我期望是用一种更简洁的更优雅的方式来处理item点击事件,期间也尝试使用RecyclerView.OnItemTouchListener来实现点击事件,虽然功能上实现了点击事件,但对点击效果的支持没能有效的解决,后来不得不放弃的这一方案。最终,这里还是通过itemView的点击事件来实现。

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
...
vh = onCreateDefViewHolder(parent, viewType);
dispatchItemClickListener(vh);
...
private void dispatchItemClickListener(final BaseViewHolder vh) {
if (mOnItemClickListeners != null && mOnItemClickListeners.size() > 0) {
if (!(vh.itemView instanceof AdapterView)) {
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 0; i < mOnItemClickListeners.size(); i++) {
final OnItemClickListener listener = mOnItemClickListeners.get(i);
listener.onItemClick(vh, vh.getLayoutPosition() - getHeaderViewCount());
}
}
});
}
}

if (mOnItemLongClickListeners != null && mOnItemLongClickListeners.size() > 0) {
if (!(vh.itemView instanceof AdapterView)) {
vh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
for (int i = 0; i < mOnItemLongClickListeners.size(); i++) {
final OnItemLongClickListener listener = mOnItemLongClickListeners.get(i);
listener.onItemLongClick(vh, vh.getLayoutPosition() - getHeaderViewCount());
}
return true;
}
});
}
}
}

Adapter抽象封装代码上并不复杂,只是基于这些抽象及封装,使用起来更方便快捷。也欢迎支持一下这个项目~ 持续维护~😊