Android RecyclerView (一)

前言

Linus Benedict Torvalds : RTFSC – Read The Funning Source Code

概述

A flexible view for providing a limited window into a large data set. 用于向大型数据集提供有限窗口的灵活视图。

名词解析

Adapter:

A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set. 翻译:RecyclerView.Adapter的子类,负责提供表示数据集中的项目的视图。

Position:

The position of a data item within an Adapter. 翻译:数据项在适配器中的位置。

Index:

The index of an attached child view as used in a call to getChildAt(int). Contrast with Position. 翻译:在调用getChildAt(int)中使用的附加子视图的索引。与Position对比。

Binding:

The process of preparing a child view to display data corresponding to a position within the adapter. 翻译:准备子视图以显示对应于适配器内的位置的数据的过程。

Recycle (view):

A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction. 翻译:先前用于显示特定适配器位置的数据的视图可以被放置在高速缓存中,以便以后重新使用以稍后再次显示相同类型的数据。 这可以通过跳过初始布局膨胀或构造来显着提高性能。

Scrap (view):

A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty. 翻译:在布局期间已进入临时分离状态的子视图。 可以重复使用废弃视图,而不会与父RecyclerView完全分离,如果不需要重新绑定则无需修改,或者如果视图被认为是脏的,则适配器会修改。

Dirty (view):

A child view that must be rebound by the adapter before being displayed. 翻译:子视图必须在显示之前绑定在适配器上。

结构浅析

RecyclerView基础结构图

  • RecyclerView.Adapter - 处理数据集合并负责绑定视图。
  • ViewHolder - 持有所有的用于绑定数据或者需要操作的View。
  • LayoutManager - 负责摆放视图等相关操作。
  • ItemDecoration - 负责绘制Item附近的分割线。
  • ItemAnimator - 为Item的一般操作添加动画效果,如,增删条目等。

RecyclerView.Adapter

作为itemView和data之间的适配器,将data绑定到某一个itemView上。

1
2
3
4
5
6
7
8
9
10
// 创建Item视图,并返回相应的ViewHolder
// 在 RecyclerView 需要一个新的给定类型的 ViewHolder 来表示一栏时调用。
public VH onCreateViewHolder(ViewGroup parent, int viewType);
// 绑定数据到正确的Item视图上。
// 由RecyclerView调用以在指定位置显示数据。 此方法应更新 itemView 的内容以反映给定位置的项目。
public void onBindViewHolder(VH holder, int position);
// 返回适配器保存的数据集中的项目总数。
public abstract int getItemCount();

RecyclerView.ViewHolder

ViewHolder描述了项目视图及其在RecyclerView中的位置的元数据。
基本用法是用来存放View对象。Android团队很早之前就推荐使用“ViewHolder设计模式”。现在对于这种新型的 RecyclerView.Adapter,我们必须实现并使用它。

1
2
3
4
5
6
7
8
protected final static class SimpleItemViewHolder extends RecyclerView.ViewHolder {
protected TextView textView;
public SimpleItemViewHolder(View itemView) {
super(itemView);
this.textView = (TextView) itemView.findViewById(R.id.text);
}
}

主要元素组成有:

1
2
3
4
5
6
7
8
9
10
11
public static abstract class ViewHolder {
View itemView;//itemView
int mPosition;//位置
int mOldPosition;//上一次的位置
long mItemId;//itemId
int mItemViewType;//itemViewType
int mPreLayoutPosition;
int mFlags;//ViewHolder的状态标志
int mIsRecyclableCount;
Recycler mScrapContainer;//若非空,表明当前ViewHolder对应的itemView可以复用

关于ViewHolder,非常有用的是mFlags。
FLAG_BOUND: ViewHolder已经绑定到某个位置,mPosition、mItemId、mItemViewType都有效
FLAG_UPDATE: ViewHolder绑定的View对应的数据过时需要重新绑定,mPosition、mItemId还是一致的
FLAG_INVALID: ViewHolder绑定的View对应的数据无效,需要完全重新绑定不同的数据
FLAG_REMOVED: ViewHolder对应的数据已经从数据集移除
FLAG_NOT_RECYCLABLE: ViewHolder不能复用
FLAG_RETURNED_FROM_SCRAP: 这个状态的ViewHolder会加到scrap list被复用。
FLAG_CHANGED: ViewHolder内容发生变化,通常用于表明有ItemAnimator动画
FLAG_IGNORE: ViewHolder完全由LayoutManager管理,不能复用
FLAG_TMP_DETACHED: ViewHolder从父RecyclerView临时分离的标志,便于后续移除或添加回来
FLAG_ADAPTER_POSITION_UNKNOWN: ViewHolder不知道对应的Adapter的位置,直到绑定到一个新位置
FLAG_ADAPTER_FULLUPDATE: 方法addChangePayload(null)调用时设置

RecyclerView.LayoutManager

LayoutManager负责在RecyclerView中测量和定位项目视图,以及确定何时回收用户不再可见的项目视图的策略。 通过更改LayoutManager,RecyclerView可用于实现标准垂直滚动列表,统一网格,交错网格,水平滚动集合等。 提供了几个库存布局管理器供一般使用。

必须为RecyclerView指定LayoutManager,否则会出现异常。

内置的三种布局:

  1. LinearLayoutManager 水平或者垂直的Item视图。
  2. GridLayoutManager 网格Item视图。
  3. StaggeredGridLayoutManager 交错的网格Item视图。

唯一的抽象函数:

1
2
// Create a default LayoutParams object for a child of the RecyclerView.
public abstract LayoutParams generateDefaultLayoutParams();

值得注意的还有:

1
2
3
4
5
public void scrollToPosition(int position) {
if (DEBUG) {
Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
}
}

RecyclerView.ItemDecoration

ItemDecoration允许应用程序从适配器的数据集中为特定项目视图添加特殊的图形和布局偏移。 这可以用于在项目,亮点,视觉分组边界等之间绘制分隔线。

通过重写以下三个方法,来实现Item之间的偏移量或者装饰效果:

1
2
3
4
5
6
7
8
// 装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡
public void onDraw(Canvas c, RecyclerView parent);
// 装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
public void onDrawOver(Canvas c, RecyclerView parent);
// 与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent);

RecyclerView.ItemAnimator

此类定义在对适配器进行更改时在项目上发生的动画。

有以下三种事件用于触发动画:

  1. 某条数据被插入到数据集合中。
  2. 从数据集合中移除某条数据。
  3. 更改数据集合中的某条数据。

RecyclerView.OnItemTouchListener

一个 onitemtouchlistener 允许应用程序拦截进步触摸事件在视图层次水平的 recyclerview 之前那些触摸事件被认为是 recyclerview 的滚动行为。

通过继承去实现想要的单击双击或其他事件监听。

使用

Step 1: 添加控件库

1
compile 'com.android.support:recyclerview-v7:21.0.3'

Step 2: 添加到xml控件里

1
2
3
4
5
6
7
8
<android.support.v7.widget.RecyclerView
android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="350dp"
android:clipToPadding="false"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:scrollbars="none" />

Step 3: 初始化

在Activity onCreate中初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void initTagItemLayout() {
RecyclerView mRvTagBar = (RecyclerView) findViewById(R.id.recycler);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRvTagBar.setLayoutManager(layoutManager);
TagBarAdapter mTagBarAdapter = new TagBarAdapter();
mRvTagBar.setAdapter(mTagBarAdapter);
mTagBarAdapter.loadData(getTagInfosList());
mTagBarAdapter.setOnItemClickListener(new ITagItemClickListener() {
@Override
public void onItemClick(int position) {
refreshItemDataSelectInTagBar(position);
}
@Override
public void onItemMove(int fromPosition, int toPosition) {
}
});
}

Step 4: 添加适配器

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 TagBarAdapter extends RecyclerView.Adapter<TagBarAdapter.ViewHolder> {
private List<TagInfo> mTagInfos = new ArrayList<>();
public void loadData(List<TagInfo> TagInfos) {
if (TagInfos == null) {
return;
}
this.mTagInfos.clear();
this.mTagInfos.addAll(TagInfos);
this.notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(getContext()).
inflate(R.layout.act_notice_tag_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
if (mTagInfos.size() <= position) {
return;
}
TagInfo TagInfo = mTagInfos.get(position);
if (TagInfo == null) {
return;
}
holder.tvTagName.setText(TagInfo.getTagName());
if (TagInfo.getSelect()) {
holder.ivTagSelect.setVisibility(View.GONE);
holder.tvTagName.setTextColor(holder.tvTagName.getResources().getColor(R.color.guide_fc5));
holder.tvTagName.setTextSize(TypedValue.COMPLEX_UNIT_PX, holder.tvTagName.getResources().getDimension(R.dimen.common_font_fs1));
} else {
holder.ivTagSelect.setVisibility(View.GONE);
holder.tvTagName.setTextColor(holder.tvTagName.getResources().getColor(R.color.secondary_fc2));
holder.tvTagName.setTextSize(TypedValue.COMPLEX_UNIT_PX, holder.tvTagName.getResources().getDimension(R.dimen.common_font_fs4));
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null){
mOnItemClickListener.onItemClick(position);
}
}
});
}
@Override
public int getItemCount() {
return mTagInfos.size();
}
private ITagItemClickListener mOnItemClickListener = null;
public void setOnItemClickListener(ITagItemClickListener listener) {
this.mOnItemClickListener = listener;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvTagName = null;
public ImageView ivTagSelect = null;
public ViewHolder(View itemView) {
super(itemView);
tvTagName = (TextView) itemView.findViewById(R.id.tv_tag_name);
ivTagSelect = (ImageView) itemView.findViewById(R.id.notice_tag_down_undo);
}
}
}

多Item布局

通过 getItemViewType(int position) 函数我们可以区分不同的item样式。
再在 onCreateViewHolder(ViewGroup parent, int viewType) 生成item时判断不同的viewType来生成不同样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static enum ITEM_TYPE {
ITEM_TYPE_IMAGE,
ITEM_TYPE_TEXT
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) {
return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_image, parent, false));
} else {
return new TextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
}
}
@Override
public int getItemViewType(int position) {
return position % 2 == 0 ? ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal() : ITEM_TYPE.ITEM_TYPE_TEXT.ordinal();
}

总结

我们在本章简单的认识了RecyclerView,在接下来的章节我们会深入的去分析RecyclerView和认识怎么去自定义各种效果。敬请关注。