ListView RecyclerView ViewPager的大一统

本文主要解决ListView RecyclerView ViewPager的Adapter统一规划和使用问题。

每次使用以上控件,都需要重写Adapter,很烦。尤其是条目类型很多的时候,Adapter异常丑陋,窝心。既然如此我们就解决一下吧。

思路

条目的展示样式都是跟着数据走的,和Adapter关系不大,那么就把条目数据和数据处理类提取出来,Adapter在其中只做控制和转发。

以ListView为例, 我们来开始逐步进行分析和完善。首先进行抽离,Adapter需要负责一下几个事项:

  • 数据列表的持有;
  • View类型的计算;
  • 数据和处理类的关联;
  • 相关处理的转发;
    我们首先要分析一下数据和处理类的结构,数据类除了正常的展示数据,还需要提供一个处理类的相关信息,这里我们要求直接将处理类的类名返回。数据类的接口如下:
1
2
3
4
5
6
public interface IDataType {
/**
* @return
*/
String getViewHandlerName();
}

有了数据类接口,那么处理类要做的事情也很简单,提供View的样式,将相关数据加载到View上,处理类接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface IViewHandler<H, T> {
/**
*
* @return
*/
int getResId();

/**
*
* @param holder
* @param positon
* @param data
* @param parent
*/
void handleView(H holder, int positon, T data, ViewGroup parent);
}

其中 H为一个抽象泛型,负责View的初始化和持有,以ListView为例,H的具体内容为:

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 ListViewHolder {
private ViewFinder viewFinder;

private View mItemView;

private ListViewHolder(Context context, ViewGroup parent, int layoutId) {
LayoutInflater inflater = LayoutInflater.from(context);
mItemView = inflater.inflate(layoutId, parent, false);
viewFinder = new ViewFinder(mItemView);

mItemView.setTag(R.id.item_holder, this);
}

public View getItemView() {
return mItemView;
}

public ViewFinder getViewFinder() {
return viewFinder;
}

public static ListViewHolder get(Context context, View itemView, ViewGroup parent, int layoutId) {
if (itemView == null)
return new ListViewHolder(context, parent, layoutId);
else
return (ListViewHolder) itemView.getTag(R.id.item_holder);
}
}

有了IDataType和IViewHandler,还需要一个类来讲两者进行关联和加载,因为IDataType持有的是Handler的Name,所以这里需要用到反射,为了降低放射带来的性能消耗,需要我们对Handler进行缓存,于是就有了HandlerFactory

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
public class ViewHandlerFactory {
private static final String TAG = ViewHandlerFactory.class.getSimpleName();
/**
* cached handler
*/
private static Map<String, IViewHandler> mHandlerMap = new ArrayMap<>();

/**
*
* @param viewHandlerClazz
*/
public static <T extends IViewHandler> T getViewHandler(String viewHandlerClazz) {
IViewHandler result = mHandlerMap.get(viewHandlerClazz);

if (result == null) {
try {
Class clazz = Class.forName(viewHandlerClazz);

Assert.judge(IViewHandler.class.isAssignableFrom(clazz), "IViewHandler not implemented");

result = (IViewHandler) clazz.newInstance();
mHandlerMap.put(viewHandlerClazz, result);
} catch (Exception e) {
Log.e(TAG, "Exception!", e);
}
}

if (result == null)
throw new RuntimeException("IViewHandler create failed:" + viewHandlerClazz);

return (T) result;
}
}

万事具备,接下来只需要在Adapter中将所有的东西关联起来就可以啦,以ListView为例,具体如下:

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
public class LEasyAdapter<T extends IDataType> extends BaseAdapter {

/**
* ListView item type count
*/
private int typeCount;

/**
* auto generate item type
*/
private int itemIdOffset = 0;

/**
* cache item type
*/
private Map<String, Integer> itemTypeArr;

private List<T> dataList;

public LEasyAdapter(List<T> dataList) {
this(dataList, 1);
}

public LEasyAdapter(List<T> dataList, int typeCount) {
this.dataList = dataList;
this.typeCount = typeCount;
itemTypeArr = new ArrayMap<>();
}

@Override
public int getCount() {
return dataList == null ? 0 : dataList.size();
}

@Override
public int getItemViewType(int position) {
IViewHandler handler = ViewHandlerFactory.getViewHandler(getItem(position).getViewHandlerName());

Integer id = itemTypeArr.get(handler.getClass().getName());

if (id == null) {
id = itemIdOffset++;
itemTypeArr.put(handler.getClass().getName(), id);
}

return id;
}

@Override
public int getViewTypeCount() {
return typeCount;
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public T getItem(int position) {
return dataList.get(position);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
IViewHandler handler = ViewHandlerFactory.getViewHandler(getItem(position).getViewHandlerName());
ListViewHolder holder = ListViewHolder.get(parent.getContext(), convertView, parent, handler.getResId());

handler.handleView(holder, position, getItem(position), parent);

return holder.getItemView();
}
}

这里需要注意一点:对于ListView,需要提前制定ViewTypeCount,RecyclerView则不需要。

以上实现全部是以ListView为例来进行的梳理,对于RecyclerView,实现上也是大同小异。但是对于ViewPager,我们则需要不同的处理。ViewPagerAdapter是没有自己处理View缓存的,所以需要我们先在ViewPagerAdapter上包装一层,处理缓存。

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 abstract class CacheViewAdapter<T extends IDataType> extends PagerAdapter {

public Map<Integer, List<View>> viewMap;


public CacheViewAdapter() {
viewMap = new HashMap<>();
}

@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
View view;

List<View> viewList = viewMap.get(getItemViewType(position));
if (viewList == null) {
viewList = new ArrayList<>();
viewMap.put(getItemViewType(position), viewList);
}

if (viewList.size() != 0) {
view = getView(position, viewList.remove(0), container);
} else {
view = getView(position, null, container);
}
view.setTag(R.id.item_viewtype, getItemViewType(position));
container.addView(view);

return view;
}

protected abstract int getItemViewType(int position);

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;

container.removeView(view);
int itemType = (int) view.getTag(R.id.item_viewtype);

List<View> cacheList = viewMap.get(itemType);
if (cacheList == null) {
cacheList = new ArrayList<>();
viewMap.put(itemType, cacheList);
}
cacheList.add(view);
}

/**
* 创建新的条目
*
* @return
*/
public abstract View getView(int position, View convertView, ViewGroup parent);


@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}

通过自己建立一个View的缓存Map,来管理缓存,这里需要注意的是,在destroyItem中我们没有根据position,获取data,然后判断ViewType,是和ViewPager自身实现有关的,当dataList更新后,destroyItem才会触发,根据position获取的datatype和view本身的viewtype是不对应的。导致出现view缓存错乱。

通过以上改造,我们后续使用以上控件的时候,不再需要创建新的Adapter类,只需要创建相关数据的Handler即可,代码解耦和复用也得到了提升。

具体代码请参考: Github 项目地址 EasyAdapter