自定义RecyclerView.Adapter

2016-05-29

前言

目前我司项目的viewHolder指定的itemView是一个customView,这类型View中可能有各种各样child view的点击事件需要实现,同时这些事件是和网络进行交互的,我司产品目前使用的是MVP的方案,review code的时候发现队友在customView中用new的方式创建了presenter实例,且队友把用户行为的统计代码也写在了里面,当时是很无语的,所以决定改造一番。

这张图中的itemView有分享和点赞的按钮,不同的开发者对这类型的itemView的处理方式可能都不一样

crosswall

下面是一段原始的createHolder的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public DemoHolder onCreateViewHolder{
View view = LayoutInflate.from(context).inflate(layout,parents,attachToRoot);
return new DemoHolder(view);
}

class DemoHolder extends RecyclerView.ViewHodler{
public DemoHolder(View itemView){
super(itemView)
initView();
}

void initView(){
//findViewById...
}
void setData(Object... object){
//bindingData...
//setOnClickListener...
}
}

问题:

(1) view缺乏统一的管理,想要知道view什么时候onAttachedToWindow | onDetachedFromWindow需要写更多的代码,且每写一个adapter就要重复实现一次,心都会累的

(2) 业务逻辑越来越复杂的情况下,ViewHolder会变得难看臃肿,维护起来就像噩梦

(3) 这样的view可复用程度是几乎为零

从上面的简单描述来看,我们需要的东西一点也不复杂:

(1) view理论上只负责展示,填充数据

(2) view在当前屏幕中显示状态(方便实现customView中管理某些特殊业务的生命周期、某些队列的add&remove等等)

(3) view与ViewHolder的关系(显示、数据源、事件)

(4) 解耦adapter与activity或者presenter的关系,各司其职互不干扰

自定义itemView

通过以上分析,我们知道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
public interface ItemViews {
/**
* 获得一个视图对象
* @return
*/
View getContentView();

/**
* 获得视图的子控件
* @return
*/
View findViewById(@LayoutRes int viewId);

/**
* View出现在当前屏幕内
* @return
*/
void onAttachedToWindow();

/**
* View从当前屏幕中被移除
* @return
*/
void onDetachedFromWindow();
}

我们需要抽象出一个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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public abstract class CleverItemView<T> implements ItemViews {

private static final String TAG = "CleverItemView";

private View view;

private Context context;


public CleverItemView(Context context){
this(context,null);
}

public CleverItemView(Context context, ViewGroup root){
this.context = context;
view = LayoutInflater.from(this.context).inflate(getLayoutRes(),root,false);
Log.d(TAG,"创建view");
initView();
}

public Context getContext(){
return context;
}

protected abstract int getLayoutRes();

protected abstract void initView();

protected abstract void setData(T... t);

@Override
public View getContentView() {
return view;
}

@Override
public View findViewById(@IdRes int viewId) {
return getContentView().findViewById(viewId);
}

@Override
public void onAttachedToWindow() {
Log.d(TAG,"onAttachToWindow");
}

@Override
public void onDetachedFromWindow() {
Log.d(TAG,"onDetachFromWindow");
}

接下来我们需要在adapter中通知viewHolder去管理itemView的进出屏幕状态,将数据传递给itemView,注册itemView的点击,childView的点击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface ItemClickListener {
/**
* itemView被点击后回调
* @return
*/
void onItemClick(View view, int position);

/**
* itemView的childView被点击后回调
* @return
*/
void onItemChildClick(View view, int position);

}

同样的原理,给adapter & 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
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
public abstract class CleverAdapter<VH extends CleverAdapter.CleverHolder> extends RecyclerView.Adapter<VH>{

private ItemClickListener itemClickListener;


public void setItemClickListener(ItemClickListener itemClickListener){
this.itemClickListener = itemClickListener;
}

@Override
public void onViewAttachedToWindow(VH holder) {
super.onViewAttachedToWindow(holder);
holder.onAttachedToWindow();
}

@Override
public void onViewDetachedFromWindow(VH holder) {
super.onViewDetachedFromWindow(holder);
holder.onDetachedFromWindow();
}


public abstract class CleverHolder<T,V extends CleverItemView> extends RecyclerView.ViewHolder implements ItemViews {
private int[] views;

private View itemView;

private V v;

public CleverHolder(V v) {
super(v.getContentView());
this.v = v;
this.itemView = v.getContentView();
views = bindItemChildClick();
if(views!=null && views.length>0){
registerChildClick();
}
this.itemView.setClickable(true);
this.itemView.setOnClickListener(itemViewClick);
}


public abstract void bindingData(T... t);

@Override
public View getContentView() {
return this.itemView;
}

@Override
public View findViewById(@IdRes int viewId) {
return this.itemView.findViewById(viewId);
}


@Override
public void onAttachedToWindow() {
if(v!=null){
v.onAttachedToWindow();
}
}

@Override
public void onDetachedFromWindow() {
if(v!=null){
v.onDetachedFromWindow();
}
}

/**
* childView的资源ID数组
* itemView中的子控件可能需要实现点击事件,为了实现点击管理,降低耦合和代码的侵入性实现统一事件管理
* @return
*/

public int[] bindItemChildClick(){
return null;
}

/**
* 为itemView下的childView绑定点击事件
*/

private void registerChildClick(){
for(int viewId : views){
itemView.findViewById(viewId).setOnClickListener(childClick);
}
}

/**
* 子控件的点击事件会回调给上层
* 回调给上层的参数是当前child,和当前adapterPosition
*/

private View.OnClickListener childClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
if(itemClickListener!=null){
itemClickListener.onItemChildClick(v,getAdapterPosition());
}
}
};

/**
* 为itemView实现点击事件
*/

private View.OnClickListener itemViewClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
if(itemClickListener!=null){
itemClickListener.onItemClick(v,getAdapterPosition());
}
}
};
}
}

Demo地址