您的位置:首页 > 其它

真实项目运用-RecyclerView封装

2016-11-27 09:07 441 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]
前言
项目结构
RecyclerView基础
ViewHolder
多Item布局实现
一种item布局的实现
加载更多以及header 和footer的添加
添加section分区操作
Section的使用
Section的代码实现

参考链接

原文链接:从零开始搭建Android框架系列

项目地址:MVPCommon

前言

很久没有发表从零开始搭建android框架系列这个系列的文章了 。由于最近工作确实有点忙碌,也在脚踏实地的花时间研究android方面自己很多不懂的东西。但是写博客确实是一个坚持不懈和自我提高的过程,也希望在保持文章更新的同时能够保持文章的质量 。之前翻译了一些文章,有兴趣的小伙伴可以去看一下。今天这篇文章来谈一谈RecyclerView的封装,对RecyclerView的一些使用点进行总结,以及如何将RecyclerView的adapter进一步简化。平时开发使用的RecyclerView
Adapter是来自鸿洋大神的为RecyclerView打造通用Adapter 让RecyclerView更加好用以及对应的github项目baseAdapter github.但是有个问题是他这篇文章写的时间比较早,项目一直在维护,所以本篇文章也算是对整个项目的思路的再梳理。

刚好解决了昨天在鸿洋博客下看到的这个小问题。哈哈。希望对大家有帮助。



项目结构

首先看看我的项目结构,项目分为common 和module模块,这里对之前整个项目的框架进行了改造,没有了之前的library,取而代之的是将所有公用组件放在了common包中,这是每个项目都可以copy使用的部分。在module包中就是具体每个项目的每个模块。比如这个示例项目中,包含



recyclerView组件作为每个项目中都可以使用的组件,这里放在common-widgets-recyclerview包下。



这里可以看到的recyclerView组件这里添加了adapter,base,divider,section,utils,wrapper包。下面来进行深入的讲解以及怎样在项目开发中进行使用吧。



RecyclerView基础

RecyclerView is a more advanced and flexible version of ListView. This widget is a

Container for large sets of views that can be recycled and scrolled very efficiently. Use the RecyclerView widget when you have lists with elements that change dynamically. 

RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好。RecyclerView与ListView原理是类似的:都是仅仅维护少量的View并且可以展示大量的数据集。在RecyclerView标准化了ViewHolder类似于ListView中convertView用来做视图缓存。

请直接参考 Android RecyclerView 使用完全解析 体验艺术般的控件

ViewHolder

ViewHolder是google在优化ListView性能的技巧上就提到的,虽然google并没有强制使用,但事实上它已经成为ListView的编写规范。在RecyclerView上,ViewHolder的使用成为了一种强制手段了。接下来对封装的ViewHolder进行分析:

首先来看看ViewHolder的用法,这是一个简单的获取String数组并展现到TextView上。通过数组的大小来创建item的数量。

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    public String[] datas = null;
    public MyAdapter(String[] datas) {
        this.datas = datas;
    }
    //创建新View,被LayoutManager所调用
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false);
        ViewHolder vh = new ViewHolder(view);
        return vh;
    }
    //将数据与界面进行绑定的操作
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        viewHolder.mTextView.setText(datas[position]);
    }
    //获取数据的数量
    @Override
    public int getItemCount() {
        return datas.length;
    }
    //自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;
        public ViewHolder(View view){
        super(view);
            mTextView = (TextView) view.findViewById(R.id.text);
        }
    }
}
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


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
当然这里只是简单的一个TextView,但是当数据多起来之后,很多TextView,ImageView,以及代码段

View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false);
ViewHolder vh = new ViewHolder(view);
1
2


1
2
都可以进行稍微修改。现在ViewHolder修改如下:

public class ViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
private View mConvertView;
private Context mContext;

ImageLoaderUtil imageLoaderUtil;

public ViewHolder(Context context, View itemView) {
super(itemView);
mContext = context;
mConvertView = itemView;
mViews = new SparseArray<>();
imageLoaderUtil = new ImageLoaderUtil();
}

public static ViewHolder createViewHolder(Context context, View itemView) {
return new ViewHolder(context, itemView);
}

public static ViewHolder createViewHolder(Context context,
ViewGroup parent, int layoutId) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
false);
return new ViewHolder(context, itemView);
}

/**
* 通过viewId获取控件
*
* @param viewId
* @return
*/
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}

public View getConvertView() {
return mConvertView;
}

/****以下为辅助方法*****/

/**
* 设置TextView的值
*
* @param viewId
* @param text
* @return
*/
public ViewHolder setText(int viewId, String text) {
TextView tv = getView(viewId);
tv.setText(text);
return this;
}

public ViewHolder setImageResource(int viewId, int resId) {
ImageView view = getView(viewId);
view.setImageResource(resId);
return this;
}

public ViewHolder setImageUrl(int viewId, String url) {
ImageView view = getView(viewId);
ImageLoader.Builder builder = new ImageLoader.Builder();
ImageLoader img = builder.url(url)
.imgView(view).strategy(ImageLoaderUtil.LOAD_STRATEGY_ONLY_WIFI).build();
imageLoaderUtil.loadImage(mContext, img);
return this;
}

public ViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
ImageView view = getView(viewId);
view.setImageBitmap(bitmap);
return this;
}

public ViewHolder setImageDrawable(int viewId, Drawable drawable) {
ImageView view = getView(viewId);
view.setImageDrawable(drawable);
return this;
}
......
......

/**
* 关于事件的
*/
public ViewHolder setOnClickListener(int viewId,
View.OnClickListener listener) {
View view = getView(viewId);
view.setOnClickListener(listener);
return this;
}

......

}
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


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
这里需要关注的是getView方法,直接返回当前view的类型。

所以我们可以在使用viewholder的时候

holder.setText(R.id.text_view, "text");
1


1
就完成了textView的setText操作。而没有进行类型转换。当然这里省去了findViewById的步骤的同时,使用
private SparseArray<View> mViews
进行所有view的保存,也就是在牺牲一定内存性能的情况下,确保了代码的整洁性。还需要关注上面的

public ViewHolder setImageUrl(int viewId, String url) {
ImageView view = getView(viewId);
ImageLoader.Builder builder = new ImageLoader.Builder();
ImageLoader img = builder.url(url)
.imgView(view).strategy(ImageLoaderUtil.LOAD_STRATEGY_ONLY_WIFI).build();
imageLoaderUtil.loadImage(mContext, img);
return this;
}
1
2
3
4
5
6
7
8


1
2
3
4
5
6
7
8
这里只需要传入ImageView的id,和url就可以解析网络图片并完成加载。使用的是Glide进行图片加载。具体可以参考之前的文章网络图片加载的封装.这样封装还有一个好处是当你遇到奇葩的服务器返回字段,比如说我们前段时间遇到的每次返回的textView的text都是有问题的,需要我们自己处理,比如说时间需要截取并返回刚刚,几小时前,我们都可以在ViewHolder进行统一的处理,而不需要在每个数据获取的时候进行处理。

多Item布局实现

这也是我们使用RecyclerView和ListView中过程中经常遇到的问题。看看网易新闻的列表样式,顶部大图,标题+三张图片,标题+左侧图片,视频样式,广告样式……. 这种情况我们怎么便捷快速处理呢?



看看通常处理itemView的type类型不同的方法:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ViewHolder0 extends RecyclerView.ViewHolder {
...
}

class ViewHolder2 extends RecyclerView.ViewHolder {
...
}

@Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
return position % 2 * 2;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolder0(...);
case 2: return new ViewHolder2(...);
...
}
}
}
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


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
这里对方法getItemViewType()进行重写, 并且在 onCreateViewHolder()针对不同的viewType进行不同的ViewHolder创建。

同时,这样使用不同的type 来处理不同的位置的数据,也能解决ListView中经常遇到的一个问题,那就是header和footer 的view的添加。我们只需要针对首尾位置进行itemViewType 的处理并且返回header和footer的view就行了。

这里也对这种情况进行了处理的封装:

看看实际项目中的效果,一个adapter就完成了所有的不同的item类型操作。

整个类继承自
MultiItemTypeAdapter<T>


/**
* Created by Anthony
* <p/>
*
*/
public class NewsMultiAdapter extends MultiItemTypeAdapter<NewsItem> {

public NewsMultiAdapter(Context context) {
super(context);
addItemViewDelegate(new TodayTopicDelegate());// docType = 5,  今日头条样式
addItemViewDelegate(new JustTitleDelegate());//   docType = 4,  纯文字样式
addItemViewDelegate(new OtherTypeDelegate());//  docType = 0/1,  默认左侧图片 + 右侧标题,描述字段样式
//        addItemViewDelegate(new BigPicTypeDelegate()); //docType = 2,  顶部标题 + 一张大横图样式

}

/*
docType = 5,  今日头条样式
docType = 0,  默认左侧图片 + 右侧标题,描述字段样式
docType = 1,  顶部标题 + 三张图片样式
docType = 2,  顶部标题 + 一张大横图样式
docType = 3,  默认样式 + 图集图标 -->点击进入图集细览详情
docType = 4,  纯文字样式
docType = 5,  今日头条样式
docType = 6,  专题样式
*/
public class TodayTopicDelegate implements ItemViewDelegate<NewsItem> {
@Override
public int getItemViewLayoutId() {
return R.layout.gz_tab1_item_today_topic;
}

@Override
public boolean isForViewType(NewsItem item, int position) {
return item.getType() == 5;
}

@Override
public void convert(ViewHolder holder, NewsItem item, int position) {
holder.setText(R.id.tv_title_center, item.getTitle());
holder.setText(R.id.tv_news_date, item.getTime());
}
}

public class OtherTypeDelegate implements ItemViewDelegate<NewsItem> {

@Override
public int getItemViewLayoutId() {
return R.layout.gz_tab1_item_normal_news;
}

@Override
public boolean isForViewType(NewsItem item, int position) {
return item.getType() == 0;
}

@Override
public void convert(ViewHolder holder, NewsItem item, int position) {
holder.setText(R.id.tv_title_center, item.getTitle());
holder.setText(R.id.tv_news_source, item.getSummary());
holder.setText(R.id.tv_news_date, item.getTime());

if (item.getImgs() != null) {
String url = item.getImgs().get(0);
holder.setImageUrlInGZ(R.id.img_news_image, url);

}
}
}

public class JustTitleDelegate implements ItemViewDelegate<NewsItem> {

@Override
public int getItemViewLayoutId() {
return R.layout.gz_tab1_item_just_title;
}

@Override
public boolean isForViewType(NewsItem item, int position) {
return item.getType() == 4;
}

@Override
public void convert(ViewHolder holder, NewsItem item, int position) {
holder.setText(R.id.tv_title_center, item.getTitle());
holder.setText(R.id.tv_news_date, item.getDate());
}
}
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


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
来看看MultiItemTypeAdapter

/**
* Created by zhy on 16/4/9.
*/
public class MultiItemTypeAdapter<T> extends RecyclerView.Adapter<ViewHolder>
{
protected Context mContext;
protected List<T> mDatas;

protected ItemViewDelegateManager mItemViewDelegateManager;
protected OnItemClickListener<T> mOnItemClickListener;
public int offset = 0;

public MultiItemTypeAdapter(Context context, List<T> datas)
{
mContext = context;
mDatas = datas;
mItemViewDelegateManager = new ItemViewDelegateManager();
}

public MultiItemTypeAdapter(Context context)
{
this(context, new ArrayList<T>());
}

@Override
public int getItemViewType(int position)
{
if (!useItemViewDelegateManager()) return super.getItemViewType(position);
return mItemViewDelegateManager.getItemViewType(mDatas.get(position), position);
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
int layoutId = mItemViewDelegateManager.getItemViewLayoutId(viewType);
ViewHolder holder = ViewHolder.createViewHolder(mContext, parent, layoutId);
setListener(parent, holder, viewType);
return holder;
}

public void convert(ViewHolder holder, T t)
{
mItemViewDelegateManager.convert(holder, t, holder.getAdapterPosition());
}

protected boolean isEnabled(int viewType)
{
return true;
}

protected void setListener(final ViewGroup parent, final ViewHolder viewHolder, int viewType)
{
if (!isEnabled(viewType)) return;
viewHolder.getConvertView().setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
if (mOnItemClickListener != null)
{
int position = viewHolder.getAdapterPosition();
mOnItemClickListener.onItemClick(v, viewHolder, mDatas.get(position - offset), position);
}
}
});

viewHolder.getConvertView().setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
if (mOnItemClickListener != null)
{
int position = viewHolder.getAdapterPosition();
return mOnItemClickListener.onItemLongClick(v, viewHolder, mDatas.get(position - offset), position);
}
return false;
}
});
}

@Override
public void onBindViewHolder(ViewHolder holder, int position)
{
convert(holder, mDatas.get(position));
}

@Override
public int getItemCount()
{
int itemCount = mDatas.size();
return itemCount;
}

public List<T> getDatas()
{
return mDatas;
}

public MultiItemTypeAdapter addItemViewDelegate(ItemViewDelegate<T> itemViewDelegate)
{
mItemViewDelegateManager.addDelegate(itemViewDelegate);
return this;
}

public MultiItemTypeAdapter addItemViewDelegate(int viewType, ItemViewDelegate<T> itemViewDelegate)
{
mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate);
return this;
}

protected boolean useItemViewDelegateManager()
{
return mItemViewDelegateManager.getItemViewDelegateCount() > 0;
}

public interface OnItemClickListener<T>
{
void onItemClick(View view, RecyclerView.ViewHolder holder, T o, int position);

boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, T o, int position);
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener)
{
this.mOnItemClickListener = onItemClickListener;
}

public void addDataAll(List data) {
mDatas.addAll(data);
}

public void clearData() {
mDatas.clear();
}
}
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


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
MultiItemTypeAdapter<T>
,这里主要完成了List形式添加数据,数据类型使用泛型操作,只需要在构造函数中,或者public方法
addDataAll
就可以添加列表类型数据。利用
ItemViewDelegateManager
完成不同类型type的管理.而添加不同的type是对接口
ItemViewDelegate
的实现。
ItemViewDelegateManager
起到了一个中间管理者和代理者的作用。具体看下面的代码:

/**
* Created by zhy on 16/6/22.
*/
public interface ItemViewDelegate<T>
{

int getItemViewLayoutId();

boolean isForViewType(T item, int position);

void convert(ViewHolder holder, T t, int position);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14


1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.app.gzgov.common.widgets.recyclerview.base;

import android.support.v4.util.SparseArrayCompat;

/**
* Created by zhy on 16/6/22.
*/
public class ItemViewDelegateManager<T>
{
SparseArrayCompat<ItemViewDelegate<T>> delegates = new SparseArrayCompat();

public int getItemViewDelegateCount()
{
return delegates.size();
}

public ItemViewDelegateManager<T> addDelegate(ItemViewDelegate<T> delegate)
{
int viewType = delegates.size();
if (delegate != null)
{
delegates.put(viewType, delegate);
viewType++;
}
return this;
}

public ItemViewDelegateManager<T> addDelegate(int viewType, ItemViewDelegate<T> delegate)
{
if (delegates.get(viewType) != null)
{
throw new IllegalArgumentException(
"An ItemViewDelegate is already registered for the viewType = "
+ viewType
+ ". Already registered ItemViewDelegate is "
+ delegates.get(viewType));
}
delegates.put(viewType, delegate);
return this;
}

public ItemViewDelegateManager<T> removeDelegate(ItemViewDelegate<T> delegate)
{
if (delegate == null)
{
throw new NullPointerException("ItemViewDelegate is null");
}
int indexToRemove = delegates.indexOfValue(delegate);

if (indexToRemove >= 0)
{
delegates.removeAt(indexToRemove);
}
return this;
}

public ItemViewDelegateManager<T> removeDelegate(int itemType)
{
int indexToRemove = delegates.indexOfKey(itemType);

if (indexToRemove >= 0)
{
delegates.removeAt(indexToRemove);
}
return this;
}

public int getItemViewType(T item, int position)
{
int delegatesCount = delegates.size();
for (int i = delegatesCount - 1; i >= 0; i--)
{
ItemViewDelegate<T> delegate = delegates.valueAt(i);
if (delegate.isForViewType( item, position))
{
return delegates.keyAt(i);
}
}
throw new IllegalArgumentException(
"No ItemViewDelegate added that matches position=" + position + " in data source");
}

public void convert(ViewHolder holder, T item, int position)
{
int delegatesCount = delegates.size();
for (int i = 0; i < delegatesCount; i++)
{
ItemViewDelegate<T> delegate = delegates.valueAt(i);

if (delegate.isForViewType( item, position))
{
delegate.convert(holder, item, position);
return;
}
}
throw new IllegalArgumentException(
"No ItemViewDelegateManager added that matches position=" + position + " in data source");
}

public int getItemViewLayoutId(int viewType)
{
return delegates.get(viewType).getItemViewLayoutId();
}

public int getItemViewType(ItemViewDelegate itemViewDelegate)
{
return delegates.indexOfValue(itemViewDelegate);
}
}
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


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
这里也就解决了多种itemViewType的问题。实现了方便的添加不同的类型的item数据。泛型数据降低了代码的耦合度。

一种item布局的实现:

这里提供一种item布局,就只需要对MultiItemTypeAdapter进行限定一种layout类型。并且isForViewType方法返回true,代表着始终返回当前的layout。

那么对于只有一种类型的列表数据

/**
* Created by zhy on 16/4/9.
*/
public abstract class CommonAdapter<T> extends MultiItemTypeAdapter<T> {
protected Context mContext;
protected int mLayoutId;
protected List<T> mDatas;
protected LayoutInflater mInflater;

public CommonAdapter(final Context context, final int layoutId) {
this(context, layoutId, new ArrayList<T>());
}

public CommonAdapter(final Context context, final int layoutId, List<T> datas) {
super(context, datas);
mContext = context;
mInflater = LayoutInflater.from(context);
mLayoutId = layoutId;
mDatas = datas;

addItemViewDelegate(new ItemViewDelegate<T>() {
@Override
public int getItemViewLayoutId() {
return layoutId;
}

@Override
public boolean isForViewType(T item, int position) {
return true;
}

@Override
public void convert(ViewHolder holder, T t, int position) {
CommonAdapter.this.convert(holder, t, position);
}
});
}

protected abstract void convert(ViewHolder holder, T t, int position);
}
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


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
具体的新闻类型:

public class NewsSingleAdapter extends CommonAdapter<NewsItem> {
public NewsSingleAdapter(Context context) {
super(context, R.layout.prj_list_item_news);
}

@Override
protected void convert(ViewHolder holder, NewsItem item, int position) {
holder.setText(R.id.tv_news_title, item.getTitle());
holder.setText(R.id.tv_news_summary, item.getSummary());
holder.setText(R.id.tv_news_date, item.getTime());
holder.setImageUrl(R.id.img_news_image,item.getImgs().get(0));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14


1
2
3
4
5
6
7
8
9
10
11
12
13
14
这里也就实现了单一的列表形式,比如网易新闻的专题样式:



加载更多以及header 和footer的添加

这里直接参加wrapper包中几个类,



这里是对不同的item的type 类型进行控制,从而得到了不同的RecyclerView的样式。具体可以参考我的MVPCommon中的代码。

/**
* Created by zhy on 16/6/23.
*/
public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>
{
private static final int BASE_ITEM_TYPE_HEADER = 100000;
private static final int BASE_ITEM_TYPE_FOOTER = 200000;

private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();

private RecyclerView.Adapter mInnerAdapter;
private RecyclerView.Adapter mNotifyAdapter;

public HeaderAndFooterWrapper(RecyclerView.Adapter adapter)
{
mInnerAdapter = adapter;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
if (mHeaderViews.get(viewType) != null)
{
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
return holder;

} else if (mFootViews.get(viewType) != null)
{
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));
return holder;
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}

@Override
public int getItemViewType(int position)
{
if (isHeaderViewPos(position))
{
return mHeaderViews.keyAt(position);
} else if (isFooterViewPos(position))
{
return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
}
return mInnerAdapter.getItemViewType(position - getHeadersCount());
}

private int getRealItemCount()
{
return mInnerAdapter.getItemCount();
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
if (isHeaderViewPos(position))
{
return;
}
if (isFooterViewPos(position))
{
return;
}
mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
}

@Override
public int getItemCount()
{
return getHeadersCount() + getFootersCount() + getRealItemCount();
}

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
{
mNotifyAdapter = recyclerView.getAdapter();
WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback()
{
@Override
public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position)
{
int viewType = getItemViewType(position);
if (mHeaderViews.get(viewType) != null)
{
return layoutManager.getSpanCount();
} else if (mFootViews.get(viewType) != null)
{
return layoutManager.getSpanCount();
}
if (oldLookup != null)
return oldLookup.getSpanSize(position);
return 1;
}
});
}

@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
{
mInnerAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position))
{
WrapperUtils.setFullSpan(holder);
}
}

private boolean isHeaderViewPos(int position)
{
return position < getHeadersCount();
}

private boolean isFooterViewPos(int position)
{
return position >= getHeadersCount() + getRealItemCount();
}

public void addHeaderView(View view)
{
int key = findHeaderKeyByView(view);
if (key == -1) {
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
if (mNotifyAdapter != null)
mNotifyAdapter.notifyDataSetChanged();

if (mInnerAdapter instanceof MultiItemTypeAdapter) {
((MultiItemTypeAdapter) mInnerAdapter).offset += 1;
}
}
}

public void addFootView(View view)
{
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}

public int getHeadersCount()
{
return mHeaderViews.size();
}

public int getFootersCount()
{
return mFootViews.size();
}

public void deleteHeaderView(View view)
{
//        if (mHeaderViews.size() > position && position >=0 ) {
//            View v = mHeaderViews.get(position + BASE_ITEM_TYPE_HEADER, null);
//            if (v != null) {
//                mHeaderViews.remove(position + BASE_ITEM_TYPE_HEADER);
//                if (mInnerAdapter instanceof MultiItemTypeAdapter) {
//                    ((MultiItemTypeAdapter) mInnerAdapter).offset -= 1;
//                }
//                if (mNotifyAdapter != null)
//                    mNotifyAdapter.notifyDataSetChanged();
//            }
//        }

//        for(int i=0; i<mHeaderViews.size(); i++) {
//            int key = mHeaderViews.keyAt(i);
//            if(mHeaderViews.get(key) == view) {
//                mHeaderViews.remove(key);
//                if (mInnerAdapter instanceof MultiItemTypeAdapter) {
//                    ((MultiItemTypeAdapter) mInnerAdapter).offset -= 1;
//                }
//                if (mNotifyAdapter != null)
//                    mNotifyAdapter.notifyDataSetChanged();
//                break;
//            }
//        }

int key = findHeaderKeyByView(view);
if (key != -1) {
mHeaderViews.remove(key);
if (mInnerAdapter instanceof MultiItemTypeAdapter) {
((MultiItemTypeAdapter) mInnerAdapter).offset -= 1;
}
if (mNotifyAdapter != null)
mNotifyAdapter.notifyDataSetChanged();
}
}

public int findHeaderKeyByView(View view) {
for(int i=0; i<mHeaderViews.size(); i++) {
int key = mHeaderViews.keyAt(i);
if(mHeaderViews.get(key) == view) {
return key;
}
}

return -1;
}
}
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


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
这里的header和footer没有个数的限制。

添加section分区操作:

现在需求又来了 。需要对RecyclerView中的item进行分区操作,就比如说微信以B开头的姓名都放在B这个分区下,以C开头的名字,都在C这个分区下。比如说京东的这个界面

列表数据里面添加了分区。那么怎么操作呢?



这里对开源库SectionedRecyclerViewAdapter做了集成。并且添加的上面的ViewHolder,简化onCreateViewHolder中的数据绑定操作。

也就是代码中的recyclerview-section包中的部分。



Section的使用:

1) 创建自定义 Section 类集成自 StatelessSection

public class WeiboGridSection extends StatelessSection {
private final List<NewsItem>  list;

public WeiboGridSection(List<NewsItem> list) {
super(R.layout.grid_item);
this.list = list;
}

@Override
public int getContentItemsTotal() {
return list.getItems().size();
}

@Override
public ViewHolder getItemViewHolder(View view, int viewType) {
return new ViewHolder(mContext, view);
}

@Override
public void onBindItemViewHolder(ViewHolder holder, final int position) {

final NewsItem newsItem = list.get(position);
String itemImgUrl = newsItem.getImages().get(0);
final String name = newsItem.getTitle();
holder.setImageUrl(R.id.grid_item_iv, itemImgUrl);
holder.setText(R.id.grid_item_tv, name);
holder.getConvertView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), WebViewCommentActivity.class);
intent.putExtra(WebViewCommentActivity.WEB_VIEW_ITEM, newsItem);
startActivity(intent);
}
});
}
}
@Override
public ViewHolder getHeaderViewHolder(Context context, View view) {
return new ViewHolder(mContext, view);
}

@Override
public void onBindHeaderViewHolder(ViewHolder holder) {
holder.setText(R.id.section_header_tv, "微博关注");
holder.setImageResource(R.id.section_header_iv, R.mipmap.wb_focus);

}

}
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


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
2) 添加section到adapter,注意这里是SectionRVAdapter

// Create an instance of SectionedRecyclerViewAdapter
SectionRVAdapter sectionAdapter = new SectionRVAdapter();

// Add your Sections
sectionAdapter.addSection(new MySection());

// Set up your RecyclerView with the SectionRVAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
1
2
3
4
5
6
7
8
9
10


1
2
3
4
5
6
7
8
9
10
看看界面效果



Section的代码实现

整个代码由于是对
RecyclerView.Adapter
封装。所以需要关注的方法自然是
getItemViewType
,
onBindViewHolder
,
createViewHolder
getItemCount
四个方法,下面以这四个方法为切入点进行分析:

初始化需要关注的是这里使用hashMap对section进行存储。也就实现了后面的根据section的添加顺序依次展示Section到RecyclerView中。



onCreateViewHolder完成的是ViewHolder的创建,每一个section分为头部header,底部footer。以及中间部分,中间布局可以有Loading/Loaded/Failed三种状态分别对应加载,加载成功,失败界面。注意这里的状态都分别对应于每个section里面,而不是整个RecyclerView.

也就是说一个RecyclerView可以由多个Section组成,一个Section最多只能有一个Header和Footer,Section由多个RecyclerView的item条目组成。每个Section中间可以有三种状态。Loading/Loaded/Failed三种状态分别对应加载,加载成功,失败界面。

下面是来自SectionedRecyclerViewAdapter的界面效果



@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = null;
View view = null;

for (Map.Entry<String, Integer> entry : sectionViewTypeNumbers.entrySet()) {
if (viewType >= entry.getValue() && viewType < entry.getValue() + VIEW_TYPE_QTY) {

Section section = sections.get(entry.getKey());
int sectionViewType = viewType - entry.getValue();

switch (sectionViewType) {
case VIEW_TYPE_HEADER: {
Integer resId = section.getHeaderResourceId();

if (resId == null)
throw new NullPointerException("Missing 'header' resource id");

view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false);
// get the header viewholder from the section
viewHolder = section.getHeaderViewHolder(mContext,view);
break;
}
case VIEW_TYPE_FOOTER: {
Integer resId = section.getFooterResourceId();

if (resId == null)
throw new NullPointerException("Missing 'footer' resource id");

view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false);
// get the footer viewholder from the section
viewHolder = section.getFooterViewHolder(mContext,view);
break;
}
case VIEW_TYPE_ITEM_LOADED: {
view = LayoutInflater.from(parent.getContext()).inflate(section.getItemResourceId(), parent, false);
// get the item viewholder from the section
viewHolder = section.getItemViewHolder(view,viewType);
break;
}
case VIEW_TYPE_LOADING: {
Integer resId = section.getLoadingResourceId();

if (resId == null)
throw new NullPointerException("Missing 'loading state' resource id");

view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false);
// get the loading viewholder from the section
viewHolder = section.getLoadingViewHolder(mContext,view);
break;
}
case VIEW_TYPE_FAILED: {
Integer resId = section.getFailedResourceId();

if (resId == null)
throw new NullPointerException("Missing 'failed state' resource id");

view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false);
// get the failed load viewholder from the section
viewHolder = section.getFailedViewHolder(mContext,view);
break;
}
default:
throw new IllegalArgumentException("Invalid viewType");
}
}
}

return 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


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
和前面
MultiItemTypeAdapter<T>
的实现一样,我们需要根据不同viewType创建不同的viewHolder.但是需要注意的是一个Section是由一组item组成的,所以一个section需要多次的调用onCreateViewHolder进行创建不同的类型的样式。

接下来关注
onBindViewHolder
方法,通过
int sectionTotal = section.getSectionItemsTotal();
获取到了section的item的数量,并在下方针对每个section的头部header,底部footer,以及中间部分进行操作。并且调用
onBindHeaderViewHolder(holder)
,

onBindFooterViewHolder(holder)


以及
onBindContentViewHolder(holder, getSectionPosition(position))
方法,这就是当我们实现Section代码的时候需要实现的方法。
section.getSectionItemsTotal()
也是我们实现Section的时候提供的section的item的个数。

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

int currentPos = 0;

for (Map.Entry<String, Section> entry : sections.entrySet()) {
Section section = entry.getValue();

// ignore invisible sections
if (!section.isVisible()) continue;

int sectionTotal = section.getSectionItemsTotal();

// check if position is in this section
if (position >= currentPos && position <= (currentPos + sectionTotal - 1)) {

if (section.hasHeader()) {
if (position == currentPos) {
// delegate the binding to the section header
getSectionForPosition(position).onBindHeaderViewHolder(holder);
return;
}
}

if (section.hasFooter()) {
if (position == (currentPos + sectionTotal - 1)) {
// delegate the binding to the section header
getSectionForPosition(position).onBindFooterViewHolder(holder);
return;
}
}

// delegate the binding to the section content
getSectionForPosition(position).onBindContentViewHolder(holder, getSectionPosition(position));
return;
}

currentPos += sectionTotal;
}

throw new IndexOutOfBoundsException("Invalid position");
}
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


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
接下来关注getItemCount,代表整个RecyclerView的item的个数。当然是所有Section的item的总和,所以代码如下。



最后需要关注的是方法
getItemViewType
,这里也就完成了每个Section的五种类型的int返回操作。

@Override
public int getItemViewType(int position) {
/*
Each Section has 5 "viewtypes":
1) header
2) footer
3) items
4) loading
5) load failed
*/
int currentPos = 0;

for (Map.Entry<String, Section> entry : sections.entrySet()) {
Section section = entry.getValue();

// ignore invisible sections
if (!section.isVisible()) continue;

int sectionTotal = section.getSectionItemsTotal();

// check if position is in this section
if (position >= currentPos && position <= (currentPos + sectionTotal - 1)) {

int viewType = sectionViewTypeNumbers.get(entry.getKey());

if (section.hasHeader()) {
if (position == currentPos) {
return viewType;
}
}

if (section.hasFooter()) {
if (position == (currentPos + sectionTotal - 1)) {
return viewType + 1;
}
}

switch (section.getState()) {
case LOADED:
return viewType + 2;
case LOADING:
return viewType + 3;
case FAILED:
return viewType + 4;
default:
throw new IllegalStateException("Invalid state");
}

}

currentPos += sectionTotal;
}

throw new IndexOutOfBoundsException("Invalid position");
}
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


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
首先关注的是SectionRVAdapter,这里我并没有集成自上面的
MultiItemTypeAdapter<T>
,应为这里涉及到对RecyclerView.Adapter的封装操作。这里的弊端也就是每一个section甚至整个RecyclerView的itemView都是一个形式。但是多种形式的section我们可以转化为将section也视作一种
MultiItemTypeAdapter<T>
的item类型就能解决,所以这里也不算问题。这里是SectionRVAdapter的完整代码。至于Section类,主要是对几种方法和view状态,以及所有item的封装,这里不再赘述。直接看代码。

最后针对最近项目中遇到的这个问题,针对不同的布局,比如说下面的这个既有Grid,又有linear的形式。由于之前的问题全部是针对一个RecyclerView的,而一个RecyclerView在调用
recyclerView.setLayoutManager()
方法的时候,就只能有一个布局方式。好吧,当初我就是为了解决这个问题,后来才发现需要用三个RecyclerView来解决。



这篇博客就到这里,回过头去,去看看鸿洋写的为RecyclerView打造通用Adapter 让RecyclerView更加好用,以及开源库baseAdapter github相信你一目了然。

这里就是对baseAdapter github引用到实际项目中以及引入开源库SectionedRecyclerViewAdapter作为实际开发的例子。

注:这里不能提供实际项目代码,只能提供代码片段作为参考。目前暂未提供示例代码到github的项目中,只提供了recyclerView公用组件。

项目地址:MVPCommon

参考链接

baseAdapter github

为RecyclerView打造通用Adapter 让RecyclerView更加好用

RecyclerView源码分析

RecyclerView技术栈
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: