【转载】RecyclerView源码解析
原文地址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0307/4032.html
概述
之前面试的时候经常有人问是否用过RecyclerView
,最近项目中也大量用到RecyclerView
。对于有点追求的码工来时,当然不会满足于仅仅会使用这一层次,学姐就是一个有追求的妹纸。我先从普通的AdapterView
和RecyclerView
的比较说起,后面再详细介绍几个关键类。
AdapterView
vs. RecyclerView
Item复用方面:
RecyclerView
内置了RecyclerViewPool
、多级缓存、ViewHolder
,而AdapterView
需要 手动添加ViewHolder
且复用功能也没RecyclerView
更加完善样式丰富方面:
RecyclerView
通过支持水平、垂直和表格列表及其他更复杂形式,而AdapterView
只支持具体某一种效果增强方面:
RecyclerView
内置了ItemDecoration
和ItemAnimator
,可以自定义绘制itemView之间的一些特殊UI或item项数据变化时的动画效果,而用AdapterView
实现时采取的做法是将这些特殊UI作为itemView的一部分,设置可见不可见决定是否展现,且数据变化时的动画效果没有提供,实现起来比较麻烦代码内聚方面:
RecyclerView
将功能密切相关的类写成内部类,如ViewHolder
,Adapter
,而AdapterView
没有
1. Recycler
(1)Recycler简介
Recycler用于管理已经废弃或与RecyclerView分离的(scrapped or detached)item view,便于重用。
Scrapped view指依附于RecyclerView,但被标记为可移除或可复用的view。
LayoutManager获取Adapter某一项的View时会使用Recycler。当复用的View有效(clean)时,View能直接被复用,反之若View失效(dirty)时,需要重新绑定View。对于有效的View,如果不主动调用request layout,则不需要重新测量大小就能复用
(2)原理解析
在分析Recycler的复用原理之前,我们先了解下如下两个类:
RecycledViewPool
RecyclerViewPool用于多个RecyclerView之间共享View。只需要创建一个RecyclerViewPool实例,然后调用RecyclerView的setRecycledViewPool(RecycledViewPool)
方法即可。RecyclerView默认会创建一个RecyclerViewPool实例。
public static class RecycledViewPool {
private SparseArray<ArrayList<ViewHolder>> mScrap =
new SparseArray<ArrayList<ViewHolder>>();
private SparseIntArray mMaxScrap = new SparseIntArray();
private int mAttachCount = 0; private static final int DEFAULT_MAX_SCRAP = 5; public void clear() {
mScrap.clear();
} public void setMaxRecycledViews(int viewType, int max) {
mMaxScrap.put(viewType, max);
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null) {
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
} public ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null && !scrapHeap.isEmpty()) {
final int index = scrapHeap.size() - 1;
final ViewHolder scrap = scrapHeap.get(index);
scrapHeap.remove(index);
return scrap;
}
return null;
} int size() {
int count = 0;
for (int i = 0; i < mScrap.size(); i ++) {
ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
if (viewHolders != null) {
count += viewHolders.size();
}
}
return count;
} public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapHeapForType(viewType);
if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
scrapHeap.add(scrap);
} void attach(Adapter adapter) {
mAttachCount++;
} void detach() {
mAttachCount--;
} /**
* Detaches the old adapter and attaches the new one.
* <p>
* RecycledViewPool will clear its cache if it has only one adapter attached and the new
* adapter uses a different ViewHolder than the oldAdapter.
*
* @param oldAdapter The previous adapter instance. Will be detached.
* @param newAdapter The new adapter instance. Will be attached.
* @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
* ViewHolder and view types.
*/
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
if (oldAdapter != null) {
detach();
}
if (!compatibleWithPrevious && mAttachCount == 0) {
clear();
}
if (newAdapter != null) {
attach(newAdapter);
}
} private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
ArrayList<ViewHolder> scrap = mScrap.get(viewType);
if (scrap == null) {
scrap = new ArrayList<ViewHolder>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) {
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
}
}
return scrap;
}
}
通过源码我们可以看出mScrap
是一个<viewType, List>的映射,`mMaxScrap`是一个<viewType, maxNum>的映射,这两个成员变量代表可复用View池的基本信息。调用`setMaxRecycledViews(int viewType, int max)`时,当用于复用的`mScrap`中viewType对应的ViewHolder个数超过maxNum时,会从列表末尾开始丢弃超过的部分。调用`getRecycledView(int viewType)`方法时从`mScrap`中移除并返回viewType对应的List的末尾项。
ViewCacheExtension
ViewCacheExtension
是一个由开发者控制的可以作为View缓存的帮助类。调用Recycler.getViewForPosition(int)方法获取View时,Recycler先检查attached scrap和一级缓存,如果没有则检查ViewCacheExtension.getViewForPositionAndType(Recycler, int, int)
,如果没有则检查RecyclerViewPool。注意:Recycler不会在这个类中做缓存View的操作,是否缓存View完全由开发者控制。
public abstract static class ViewCacheExtension {
abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
}
现在大家熟悉了RecyclerViewPool
和ViewCacheExtension
的作用后,下面开始介绍Recycler
。 如下是Recycler
的几个关键成员变量和方法:
private ArrayList<ViewHolder> mAttachedScrap
private ArrayList<ViewHolder> mChangedScrap与RecyclerView分离的ViewHolder列表。 private ArrayList<ViewHolder> mCachedViewsViewHolder缓存列表。 private ViewCacheExtension mViewCacheExtension开发者控制的ViewHolder缓存 private RecycledViewPool mRecyclerPool提供复用ViewHolder池。 public void bindViewToPosition(View view, int position)
将某个View绑定到Adapter的某个位置。
public View getViewForPosition(int position)
获取某个位置需要展示的View,先检查是否有可复用的View,没有则创建新View并返回。具体过程为:
(1)检查mChangedScrap
,若匹配到则返回相应holder
(2)检查mAttachedScrap
,若匹配到且holder有效则返回相应holder
(3)检查mViewCacheExtension
,若匹配到则返回相应holder
(4)检查mRecyclerPool
,若匹配到则返回相应holder
(5)否则执行Adapter.createViewHolder()
,新建holder实例
(6)返回holder.itemView
(7)注:以上每步匹配过程都可以匹配position或itemId(如果有stableId)
2. LayoutManager
LayoutManager
主要作用是,测量和摆放RecyclerView中itemView,以及当itemView对用户不可见时循环复用处理。 通过设置Layout Manager的属性,可以实现水平滚动、垂直滚动、方块表格等列表形式。其内部类Properties
包含了所需要的大部分属性
3. ViewHolder
对于传统的AdapterView
,需要在实现的Adapter类中手动加ViewHolder
,RecyclerView
直接将ViewHolder
内置,并在原来基础上功能上更强大。ViewHolder
描述RecylerView中某个位置的itemView和元数据信息,属于Adapter的一部分。其实现类通常用于保存findViewById
的结果。 主要元素组成有:
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)
调用时设置
4. Adapter
和AdapterView
中用到的BaseAdapter
、ListAdapter
等作用类似,都是作为itemView和data之间的适配器,将data绑定到某一个itemView上。差别在于,RecyclerView
将Adapter
内置作为其内部类,我认为将功能密切相关的类以内部类的形式定义使得代码内聚更好,更便于理解与阅读。
5. ItemDecoration
当我们想在某些item上加一些特殊的UI时,往往都是在itemView中先布局好,然后通过设置可见性来决定哪些位置显示不显示。RecyclerView
将itemView和装饰UI分隔开来,装饰UI即ItemDecoration
,主要用于绘制item间的分割线、高亮或者margin等。其源码如下:
public static abstract class ItemDecoration {
//itemView绘制之前绘制,通常这部分UI会被itemView盖住
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
} //itemView绘制之后绘制,这部分UI盖在itemView上面
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
} //设置itemView上下左右的间距
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
6. ItemAnimator
过去AdapterView
的item项操作往往是没有动画的。现在RecyclerView
的ItemAnimator
使得item的动画实现变得简单而样式丰富,我们可以自定义item项不同操作(如添加,删除)的动画效果。
7. 触摸事件监听
关于Item项的手势监听事件,如单击和双击没有像其他AdapterView
一样分别提供具体接口,但是RecyclerView
提供OnItemTouchListener
接口和SimpleOnItemTouchListener
实现类,大家可以通过继承去实现自己想要的单击双击或其他事件监听。
ps:关于RecyclerView
的具体使用,我提供一个链接供大家参考RecyclerView技术栈。今天就先讲这么多,以后有新的体会会继续补充的,各位期待吧~
【转载】RecyclerView源码解析的更多相关文章
- RecyclerView源码解析 - 分割线
猜想: 既然考虑了分割线,那么子View在测量时候肯定要去考虑分割线留出的位置 直接measureChild()方法 猜想: 分割线会调用绘制的方法 onDraw()
- RecyclerView 源码分析(一) —— 绘制流程解析
概述 对于 RecyclerView 是那么熟悉又那么陌生.熟悉是因为作为一名 Android 开发者,RecyclerView 是经常会在项目里面用到的,陌生是因为只是知道怎么用,但是却不知道 Re ...
- jQuery整体架构源码解析(转载)
jQuery整体架构源码解析 最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性, ...
- 转载:Bootstrap 源码解析
Bootstrap 源码解析 前言 Bootstrap 是个CSS库,简单,高效.很多都可以忘记了再去网站查.但是有一些核心的东西需要弄懂.个人认为弄懂了这些应该就算是会了.源码看一波. 栅格系统 所 ...
- 【转载】Xutils3源码解析
Github源码地址:https://github.com/wyouflf/xUtils3 原文地址 :http://www.codekk.com/blogs/detail/54cfab086c476 ...
- 【转载】okhttp源码解析
转自:http://www.open-open.com/lib/view/open1472216742720.html https://blog.piasy.com/2016/07/11/Unders ...
- 【转载】FloatingActionButton源码解析
原文地址:https://github.com/Rowandjj/my_awesome_blog/blob/master/fab_anlysis/README.md loatingActionButt ...
- 【转载】Scroller源码解析
原文地址:https://github.com/Skykai521/AndroidSdkSourceAnalysis/blob/master/article/Scroller%E6%BA%90%E7% ...
- Android LayoutInflater源码解析:你真的能正确使用吗?
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 好久没写博客了,最近忙着换工作,没时间写,工作刚定下来.稍后有时间会写一下换工作经历.接下来进入本篇主题,本来没想写LayoutInflater的 ...
随机推荐
- UVA 10886 Standard Deviation
https://vjudge.net/problem/UVA-10886 计算标准差 碰到这种题将式子展开 #include<cmath> #include<cstdio> / ...
- vijos 1069 新年趣事之红包 Prim水题
描述 xiaomengxian一进门,发现外公.外婆.叔叔.阿姨……都坐在客厅里等着他呢.经过仔细观察,xiaomengxian发现他们所有人正好组成了一个凸多边形.最重要的是,他们每个人手里都拿着一 ...
- 2015/8/28 Python基础(2):对象
Python用对象模型来存储数据.构造任何类型的值都是一个对象.Python对象都有是三个特性:身份,类型和值 身份是每个对象的唯一身份标识.任何对象都可以用内建函数id()来得到身份.如: > ...
- C11简洁之道:tupe元祖
tuple元组是一个固定大小不同类型的值的集合,是泛化的std::pair.我们也可以把它当作一个通用的结构体来使用,不需要创建结构体有获取结构体特征,在某些情况可以取代结构体,使程序更简洁.直观. ...
- Independence.
It's not giving up, it's letting go, and moving to a better place. I will survive and be the one who ...
- PHP日期时间操作
一.设置时区 date_default_timezone_set('PRC'); 二.获取当前时间的 Unix 时间戳(格林威治时间 1970 年 1 月 1 日 00:00:00到当前时间的秒数)和 ...
- 【20151105noip膜你赛】bzoj3652 bzoj3653
题目仿佛在讽刺我... 第一题: 题解: 考虑枚举区间右端点,维护所以左到当前的 and 和 or .注意 and 每次变化至少有一个二进制位从1变 0,or 每次至少有一个位从0变 1,所以最多有l ...
- Response.Redirect在新窗口打开(转载)
Response.Rederect在默认情况下是在本页跳转,所以除了在js中用window.open或是给A标签添加target属性之外,在后台似乎不能来打开新的页面,其实不然,通过设置form的ta ...
- 安装magento配置完数据库后出现: PHP Extensions "0" must be loaded. 解决方案
打开magento目录下的该文件: app/code/core/Mage/Install/etc/config.xml 将下面该段修改: <mysql4> <type>pdo_ ...
- new操作符的内部运行解析
在加上new操作符,我们就能完成传统面向对象的class + new的方式创建对象,在Javascript中,我们将这类方式成为Pseudoclassical. 基于上面的例子,我们执行如下代码 ...