最近一直在捣鼓RecyclerView,今天闲来无事就把之前收集到的资料和使用过程中的体会整理一下,写了如下这篇博客。博客的结构跟之前的博客结构类似,首先简单交代背景,随后给出一个简单使用的实例,最终根据前面遇到的一些问题,走进底层看看源码如何实现。不过我们的重点是分析RecyclerView的ViewHolder资源回收策略。顺便吐槽一句一九八网络科技V客学院(http://blog.inet198.cn/)真是傻逼,如果网友是从这里进的还请移步CSDN,那边显示效果好点!
基础介绍
动态布局
RecyclerView的官方资料介绍是:A flexible view for providing a limited window into a large data set,大体意思就是RecyclerView是一个用于显示大量数据的弹性视图控件。在RecyclerView出现之前,我们往往使用ListView显示大量的数据,对于ListView,其官方介绍是:A view that shows items in a vertically scrolling list,即垂直显示一组数据,注意这里加入了垂直两个字,这也正是RecyclerView和ListView的一个非常直观的区别。使用RecylclerView能够很容易的实现水平、垂直、瀑布流等显示样式,而ListView只能进行垂直显示。究其原因在于,ListView把布局方式硬编码进ListView类中,而RecyclerView则把布局方式和自身解耦,交给第三方实现,因此RecyclerView可以动态设置内容的显示方式。在这里也建议广大童鞋,最好把自己写的代码解耦出来,这样的好处真是简直了。
视图资源回收
丰富内部类
和ListView的RecycleBin类似,RecyclerView通过
RecyclerView.Recycler内部类(非抽象)
管理已经废弃或与RecyclerView分离的(scrapped or detached)item view,内部使用多级回收策略。LayoutManager向Recycler要ViewHolder,Recycler先从各级缓存中寻找合适的ViewHolder,最后如果在RecyclerPool中依然没有得到ViewHolder那么就会通过Adapter的onCreateViewHolder方法创建一个ViewHolder。该部分具体细节在本篇博客的最后会做详细的介绍。
RecyclerView类中定义了很多的抽象公用类,把抽象类的定义写在RecyclerView中的好处在于,当客户使用该抽象公共类时,可以很明显的知道当前抽象类应该是为RecyclerView服务的。比如ListView使用需要设置一个Adapter,如果对ListView不熟悉的话,你肯定不知道具体应该使用按个Adapter,而Android的Adapter又比较多。RecyclerView就好多了,它对应的是RecyclerView.Adapter。把抽象类定义在RecyclerView中还有诸如访问RecyclerView的私有方法和私有域等好处。RecyclerView中经常使用的几个定义在RecyclerView类中的
抽象类有:
- RecyclerView.Adapter
- RecyclerView.Adapter的使用方式和ListView的ListAdapter 类似,向RecyclerView提供显示的数据。但是!!RecyclerView.Adapter做了一件了不起的优化,那就是RecyclerView.Adapter的public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 方法能够保证当前RecyclerView是确实需要你创建一个新的ViewHolder对象。而ListView的ListAdapter 对应的方法getView(int position, View convertView, ViewGroup parent)需要在方法内部判断是重新创建一个View还是刷新一个View的数据,而不明所以的客户可能每次都会返回一个新创建的View造成ListView的卡顿和资源的浪费,;创建和刷新作为两个不同的功能本来就应该在两个方法中实现!庆幸的是RecyclerView.Adapter解决了这个问题。
- RecyclerView.LayoutManager
- 对于这一点我们在本节一开始的动态布局中就已经说过了,在此就不再说了。这里给出经常使用的三个已经实现好了的LayoutManager类,LinearLayoutManager、 StaggeredGridLayoutManager、GridLayoutManager,他们分别提供线性、瀑布流、网络视图的布局。
- RecyclerView.ViewHolder
- RecyclerView中强制客户使用ViewHolder,之前在谈及ListView的时候就经常说到使用ViewHolder来进行优化。使用ViewHolder其中一点好处是能够避免重复调用方法findViewById(),对当前item的View中的子View进行管理。
- ViewHolder 描述RecylerView中某个位置的itemView和元数据信息,属于Adapter的一部分。其实现类通常用于保存 findViewById 的结果。
- 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.ItemAnimator
- 用于在item项数据变化时的动画效果;当调用Adapter的notifyItemChanged、notifyItemInserted、notifyItemMoved等方法,会触发该对象显示相应的动画。
- RecyclerView 的 ItemAnimator 使得item的动画实现变得简单而样式丰富,我们可以自定义item项不同操作(如添加,删除)的动画效果;
- ItemAnimator作触发于以下三种事件:
- 某条数据被插入到数据集合中 ,对应public final void notifyItemInserted(int position)方法
- 从数据集合中移除某条数据 ,对应public final void notifyItemRemoved(int position) 方法
- 更改数据集合中的某条数据,对应public final void notifyItemChanged(int position) 方法
- 注意:notifyDataSetChanged(),会触发列表的重绘,并不会出现任何动画效果
- 使用:Animator使用到的逻辑比较多,因此最方便的就是使用第三方库:https://github.com/wasabeef/recyclerview-animators
- RecyclerView.ItemDecoration
- 用于绘制itemView之间的一些特殊UI,比如在itemView之前设置空白区域、画线等。
- RecyclerView 将itemView和装饰UI分隔开来,装饰UI即 ItemDecoration ,主要用于绘制item间的分割线、高亮或者margin等
- 通过recyclerView.addItemDecoration(new DividerDecoration(this))对item添加装饰;对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。
- public void onDraw(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡
- public void onDrawOver(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
- public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量
简单使用
一、引入依赖 compile 'com.android.support:recyclerview-v7:23.2.1'
compile 'com.android.support:appcompat-v7:23.3.0'
二、定义数据源对象
public class Item {
private String title;
private int idSource;
public String getTitle().....
}
三、为Item创建一个布局文件
item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/colorBackground" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/item_imageView"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
<TextView
android:hint="hello"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/item_textView"
android:gravity="center" />
</LinearLayout>
</LinearLayout>
四、创建一个继承Recycler.Adapter的类和一个继承Recycler.ViewHolder的类
一般情况自定义ViewHolder被客户定义在自定义的Adapter类中
public class MAdapter extends RecyclerView.Adapter{
private ArrayList<Item> items;
private Context context;
public MTest(ArrayList<Item> items, Context context) {
this.items = items;
this.context = context;
}
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
return new ItemViewHolder(view);
}
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(holder instanceof ItemViewHolder){
ItemViewHolder itemViewHolder= (ItemViewHolder)holder;
itemViewHolder.imageView.setImageBitmap(...);
itemViewHolder.textView.setText("test");
}
}
@Override public int getItemCount() {
return items.size();
}
class ItemViewHolder extends RecyclerView.ViewHolder{
private ImageView imageView;
private TextView textView;
public ItemViewHolder(View itemView) {
super(itemView);
initView(itemView);
}
private void initView(View view){
imageView = (ImageView)view.findViewById(R.id.item_imageView);
textView = (TextView)view.findViewById(R.id.item_textView);
}
}
}
五、在Activity的布局文件中引入RecyclerView布局
activity_main.xml
<android.support.v7.widget.RecyclerView
android:id="@+id/recycleView"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</android.support.v7.widget.RecyclerView>
六、在Activity中为RecyclerView进行初始化设置
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recycleView = (RecycleView)findViewById(R.id.recycleView);
recycleView.addItemDecoration(...);
recycleView.setItemAnimator(...);
recycleView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
recycleView.setAdapter(....); //保证一定在setLayoutManager方法之后调用该方法!!尤其是你使用RecyclerView显示Header视图的时候。
}
补充:
对于如何在RecyclerView中添加Header、Footer视图;给RecyclerView设置下拉上拉刷新视图等高级使用限于篇幅,而且相关的资料网上也很容易找到,
这里不再详细介绍。下面给出实现的大体思想。
Header和Footer视图的添加两者原理一致,在listView中我们有addHeaderView和addFooterView两个方法向ListView中添加Header和Footer视图。如果我们的RecyclerView仅仅局限在垂直布局中显示,即不使用瀑布流、网格等复杂布局,通过重写Adapter的getItemViewType(int position)方法我们可以分分钟就实现添加Header和Footer。但是既然使用了RecyclerView就不可能仅仅局限在使用垂直布局,因此下面给出一种通用的解决方案。重写Adapter的如下两个方法,具体内容如下:
/** * 对GridLayoutManager的处理;该方法会在RecyclerView.setAdapter()方法中被调用,因此前面建议保证一定在setLayoutManager方法之后调用该方法 * @param recyclerView */ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if(layoutManager instanceof GridLayoutManager){ final GridLayoutManager gridLayoutManager = (GridLayoutManager)layoutManager; gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if: positon是Header视图或Footer视图显示的位置 return gridLayoutManager.getSpanCount(); return 1; } }); } } /** * 对StaggeredGridLayoutManager的处理 * @param holder */ @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); if(layoutParams!=null&& layoutParams instanceof StaggeredGridLayoutManager.LayoutParams ){ StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) layoutParams; int position = holder.getLayoutPosition(); if: positon是Header视图或Footer视图显示的位置 lp.setFullSpan(true); } }
对于上下拉加载的实现方法,网上有各种各样的实现方法;但是最核心的东西都没有变,下拉刷新都是通过重写RecyclerView的onTouchEvent(MotionEvent e)方法,方法内部判断当前RecyclerView的position为0的View是否处于显示状态,即视图是否是最顶层,如果是则利用用户在y轴的滑动距离改变下拉刷新视图的显示高度,最终显示高度超过设定阈值则进入刷新状态,直到调用相关方法才停止刷新操作。上拉刷新相对于下拉刷新简单很多,上拉刷新视图没有下拉刷新视图的状态处理,可以通过重写RecyclerView的onScrollStateChanged(int state)方法判断当前RecyclerView是否已经显示最后一个数据,如果是就调用加载方法,方法加载完毕通过Adapter添加Item数据,并notifyItem...通知刷新RecyclerView视图,并隐藏FooterView。
当然还有一种就是使用RecyclerView + SwipeRefreshLayout的模式实现下拉刷新,不过个人觉得它太不美观因此也就不推荐。感兴趣可以看看这个文章
深入分析
首先将RecyclerView常用的几个域,摘录如下:
RecyclerView中的域
private Adapter mAdapter;//与RecyclerView绑定的Adapter
@VisibleForTesting LayoutManager mLayout; //与RecyclerView绑定的LayoutManager
private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>(); //存储所有装饰对象
ItemAnimator mItemAnimator = new DefaultItemAnimator(); //删除、插入、添加等操作对应显示的动画
//OnScrollListener定义了onScrolled和onScrollStateChanged方法
private OnScrollListener mScrollListener; //监听器 对应setOnScrollListener方法,该方法已经标注为已过时
private List<OnScrollListener> mScrollListeners;//监听器 对应addOnScrollListener方法
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
//调用Adapter的notifyDataXXX()方法,实际上是在调用 mObserver的对应方法,作用就是从Adapter取出更新的数据
final Recycler mRecycler = new Recycler();//管理RecyclerView暂时不使用的ViewHolder对象
private final ViewFlinger mViewFlinger = new ViewFlinger();//实现根据用户滑动的动作惯性的继续执行一段滑动,
往下就按照RecyclerView使用的流程为主线进行分析。即
- 首先对addItemDecoration、setItemAnimator、setLayoutManager和setAdapter几个方法进行分析,了解其底层对RecyclerView对象的影响;
- 随后对View的事件分发机制onTouchEvent方法进行分析,了解滑动过程中LayoutManager如何刷新视图内容;
- 最后对ViewGroup的绘制流程中onMeasure、onLayout、draw和onDraw几个方法进行分析。
Part1——常用方法
addItemDecoration()@RecyclerView.class
public void addItemDecoration(ItemDecoration decor) {
addItemDecoration(decor, -1);
}
addItemDecoration()@RecyclerView.class
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" + " layout"); } //如果当前Layout处于绘制状态会报错
...
if (index < 0) { mItemDecorations.add(decor); } //末尾添加
else {mItemDecorations.add(index, decor); }//指定位置添加
.....
requestLayout();
}
requestLayout()@RecyclerView.class
public void requestLayout() {
if (mEatRequestLayout == 0 && !mLayoutFrozen) { super.requestLayout(); }
else { mLayoutRequestEaten = true; }
}
该方法大体思路很简单就是将参数ItemDecoration decor存入集合ArrayList<ItemDecoration> mItemDecorations中。
setItemAnimator()@RecyclerView.class
public void setItemAnimator(ItemAnimator animator) {
if (mItemAnimator != null) { //
mItemAnimator.endAnimations();
mItemAnimator.setListener(null);
}
mItemAnimator = animator;
if (mItemAnimator != null) {
mItemAnimator.setListener(mItemAnimatorListener); //note1
}
}
跟addItemDecoration()方法类似,只不过这里是更新ItemAnimator mItemAnimator域
setLayoutManager()@Recy
clerView.class
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) { return; }
stopScroll();
if (mLayout != null) { //第一次调用该语句为假
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
}
mRecycler.clear(); //clear操作
.....
mLayout = layout; //更新mLayout域
if (layout != null) {
if (layout.mRecyclerView != null) { throw ....}
mLayout.setRecyclerView(this); //LayoutManager和当前RecyclerView对象
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
requestLayout(); //重绘
}
进行的主要操作有:清空Recycler mRecycler中的数据、更新LayoutManager mLayout域、将当前RecyclerView和LayoutManager绑定;
setAdapter()@RecyclerView.class
public void setAdapter(Adapter adapter) {
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout(); //重绘
}
setAdapterInternal()@RecyclerView.class
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {//对之前的View、动画、装饰等视图进行回收
//一般情况会进到这里
if (mItemAnimator != null) { mItemAnimator.endAnimations(); }
if (mLayout != null) {
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
}
mRecycler.clear();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter; //赋予新值
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this); //在方法内部针对GridLayoutManager布局进行的设置,使得可以显示Header和Footer视图
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
.....
}
该方法的主要动作是,移除LayoutManager中的视图、清空Recycler mRecycler中的数据、更新Adapter mAdapter域、注册数据观察者、Adapter和RecyclerView绑定;
小结:
addItemDecoration、setItemAnimator、setLayoutManager和setAdapter几个方法相对简单,大体都是更新RecyclerView中的相关域,方法的最后会申请requestLayout进行绘制。
Partt2——事件分发
分析完上面几个常用的方法我们分析一下滑动过程中RecyclerView是如何动态刷新视图的。
onTouchEvent()@RecyclerView.class
public boolean onTouchEvent(MotionEvent e) {
......
if (dispatchOnItemTouch(e)) { //note1
cancelTouch();
return true;
}
if (mLayout == null) { return false; }
......
switch (action) {
case MotionEvent.ACTION_DOWN: { //记录当前按下的坐标值
......
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
......
} break;
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
if (index < 0) { return false; }
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) { //不是在拖动页面
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) { dx -= mTouchSlop; } else { dx += mTouchSlop;}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) { dy -= mTouchSlop; } else { dy += mTouchSlop; }
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);//note2
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal( canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, vtev)) //note3
{ getParent().requestDisallowInterceptTouchEvent(true); }
}
} break;
.....
}
.....
vtev.recycle();
return true;
}
1、分派当前事件给子View处理,如果没有任何子View处理则进行后续操作
2、该方法内部如下
private void setScrollState(int state) { if (state == mScrollState) { return; } mScrollState = state; if (state != SCROLL_STATE_SETTLING) { stopScrollersInternal(); } dispatchOnScrollStateChanged(state); //note1 }
setScrollState处理的参数主要有SCROLL_STATE_IDLE表示当前并不处于滑动状态、SCROLL_STATE_DRAGGING 表示当前RecyclerView处于滑动状态(手指在屏幕上)
SCROLL_STATE_SETTLING 表示当前RecyclerView处于滑动状态,(手已经离开屏幕)
1、 该方法底层先后调用LayoutManager的onScrollStateChanged(state)方法、RecyclerView的onScrollStateChanged(state)方法,最后调用RecyclerView下所有的mScrollListener域的onScrollStateChanged(state) 方法。
3、该方法具体内容如下
scrollByInternal()@RecyclerView.class boolean scrollByInternal(int x, int y, MotionEvent ev) { int unconsumedX = 0, unconsumedY = 0; int consumedX = 0, consumedY = 0; ..... if (mAdapter != null) { ...... if (x != 0) { consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); //note1 unconsumedX = x - consumedX; } if (y != 0) { consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); //note2 unconsumedY = y - consumedY; } ......... if (consumedX != 0 || consumedY != 0) { dispatchOnScrolled(consumedX, consumedY); //note3 } if (!awakenScrollBars()) { invalidate(); } return consumedX != 0 || consumedY != 0; }
1-2、这里调用LayoutManager的scrollXXBy方法,具体LayoutManager会根据参数进行具体的绘制,该部分我们暂时不细谈,后面会讲。3、该方法底层先后调用RecyclerView的onScrolled(hresult, vresult);方法,然后调用RecyclerView下所有的mScrollListener域的onScrolled(this, hresult, vresult);方法
小结:
在事件分发中,首先将事件分派给子View去处理,如果子View没有消耗当前事件,则事件才会交给RecyclerView执行。RecyclerView可能对事件再包装交给自己的监听器去处理也可能触发刷新视图的操作,具体通过调用LayoutManager的scrollXXBy方法。
Part3——绘制流程
对于RecyclerView的绘制流程,因为它继承自ViewGroup,因此我们首先回顾一下ViewGroup的绘制流程:
- ViewGroup没有重写View的onMeasure方法,但提供了一个measureChildren方法用于调用children的measure方法,但是很多子类都不用该方法,而是自己重写View的onMeasure方法。
- ViewGroup重写了View的onLayout方法,但是声明为抽象方法交给子类实现。
- ViewGroup没有重写View的draw和onDraw方法,但是View的draw方法会调用dispatchDraw(canvas)方法,ViewGroup实现了dispatchDraw(canvas)方法,对于dispatchDraw(canvas)方法ViewGroup子类一般都不会去重写。
所以ViewGroup要求子类必须实现onLayout方法,对ViewGroup下面的View显示位置方式进行控制。下面按照onMeasure、onLayout、draw和onDraw的顺序来分析RecyclerView的绘制流程。
onMeasure()@RecyclerView.class
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);//note0
return;
}
if (mLayout.mAutoMeasure) {
......
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);//note1
......
mLayout.setMeasureSpecs(widthSpec, heightSpec);
.....
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);//note2
......
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
......
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
....
}
}
0、方法实现如下
void defaultOnMeasure(int widthSpec, int heightSpec) { final int width = LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this)); final int height = LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this)); setMeasuredDimension(width, height); //这个是View的方法,我们就不介绍了 }
1、mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);一般情况下就等于defaultOnMeasure(widthSpec, heightSpec);;
2、mLayout.setMeasuredDimensionFromChildren顾名思义就是调用子View的onMeasure方法。
onMeasure暂时没有我们太关心的东西,接着往下看。
onLayout()@RecyclerView.class
protected void onLayout(boolean changed, int l, int t, int r, int b) {
.....
dispatchLayout();
....
}
void dispatchLayout() {
if (mAdapter == null) { return; }
if (mLayout == null) { return; }
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
该方法主要是对LayoutManager的相关方法的调用,后面LayoutManager.class部分会再讲。
draw()@RecyclerView.class
public void draw(Canvas c) {
super.draw(c); //内部会调用执行完onDraw方法之后才返回
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState); //会在temDecoration的onDraw之后调用
}
....
}
给itemView绘制完后在其基础上绘制一些装饰图案
onDraw()@RecyclerView.class
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState); //会在temDecoration的onDrawOver之前调用
}
}
在itemView绘制之前绘制一些装饰如背景、画布形状。
小结:
本节分析的onMeasure和onLayout方法暂时没有找到令我们兴奋 的东西,但是在Draw和onDraw方法中我们找到了ItemDecoration的使用!在每次RecyclerView正式绘制之前都会先调用ItemDecorations集合中所有的ItemDecoration对象的onDraw方法,等ItemView绘制完毕,再调用ItemDecorations集合中所有的ItemDecoration对象的onDrawOver方法。
到此为止我们分析完了Part1、Part2、Part3三部分,中间遇到了如下的一些方法还没有解释清楚。
- Adapter
- onDetachedFromRecyclerView、onAttachedToRecyclerView、registerAdapterDataObserver、unregisterAdapterDataObserver
- LayoutManager
- setRecyclerView、onAdapterChanged、dispatchDetachedFromWindow、dispatchAttachedToWindow、removeAndRecycleAllViews、removeAndRecycleScrapInt、scrollHorizontallyBy、scrollVerticallyBy
- Recycler
- clear
下面我们就按照这里的顺序依次查看这些方法底层的实现。
Adapter.calss
Fields
onAttachedToRecyclerView()@Adapter.class
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
}
当RecyclerView开始使用当前Adapter对象时会调用该方法
onDetachedFromRecyclerView()@Adapter.class
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
}
当RecyclerView停止使用当前Adapter对象时会调用该方法
registerAdapterDataObserver()@Adapter.class
public void registerAdapterDataObserver(AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
注册一个新的观察者
unregisterAdapterDataObserver()@Adapt
er.class
public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
mObservable.unregisterObserver(observer);
}
注销一个观察者
notifyItemInserted()@Adapter.cla
ss
public final void notifyItemInserted(int position) {
mObservable.notifyItemRangeInserted(position, 1);
}
通过mObservable去通知所有的观察者处理该事件
小结:Adapter是RecyclerView的数据供应方,当Adapter和RecyclerView绑定的时候其实就是一个相互引用的过程。绑定过程中RecyclerView注册成为Adapter的观察者,一旦Adapter中的数据发生改变就通过调用Adapter的notifyXX方法告知RecyclerView去刷新视图。绑定过程中Adapter可以通过onAttachedToRecyclerView()方法对RecyclerView进行相应的处理。
注意:
当有数据更新的时候,需要通知对应的Adapter;而对于RecyclerView而言,不能够仅仅通过调用notifyDataSetChanged()就完事儿了的,因为RecyclerView还有更多精细的动作,需要处理,而不像listView那么简单;因此推荐一旦有事件需要处理则调用方法notifyItemXX();
- 如果我们在RecyclerView第一栏中添加了一行数据,而且希望RecyclerView显示这一行数据:adapter.notifyItemInserted(0); recyclerView.scrollToPosition(0);
- 如果我们在RecyclerView最后栏中添加了一行数据,而且希望RecyclerView显示这一行数据:adapter.notifyItemInserted(contacts.size() - 1); recyclerView.scrollToPosition(mAdapter.getItemCount() - 1); contacts是我们的data数据集;
下面我们分析LayoutManager对象,LayoutManager的子类比较多,我们不妨以简单的LinearLayoutManager方法为例进行说明。
LinearLayoutManager.class
LayoutManager 主要作用是,测量和摆放RecyclerView中itemView,以及当itemView对用户不可见时循环复用处理。 通过RecycleView.set.LayoutManager()方法将自定义LayoutManager和RecycleView进行绑定。通过设置Layout Manager的属性,可以实现水平滚动、垂直滚动、方块表格等列表形式。
Fields
ChildHelper mChildHelper; //负责管理LayoutManager使用的ItemView
RecyclerView mRecyclerView; //与之绑定的RecyclerView
setRecyclerView()@LayoutManager.class
void setRecyclerView(RecyclerView recyclerView) {
if (recyclerView == null) {
mRecyclerView = null;
mChildHelper = null;
mWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
mHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
} else {
mRecyclerView = recyclerView;
mChildHelper = recyclerView.mChildHelper;
mWidthSpec = MeasureSpec
.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY);
mHeightSpec = MeasureSpec
.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY);
}
}
对LayoutManager中的域完成初始化值的设置,如mChildHelper和mRecyclerView
onAdapterChanged()@LayoutManager.class
public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
}
空方法没有子类实现它
dispatchDetachedFromWindow()@LayoutManager.class
void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) {
mIsAttachedToWindow = false;
onDetachedFromWindow(view, recycler);
}
onDetachedFromWindow()@LinearLayoutManager.class
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
if (mRecycleChildrenOnDetach) {
removeAndRecycleAllViews(recycler);
recycler.clear();
}
}
removeAndRecycleAllViews()@LayoutManager.class
public void removeAndRecycleAllViews(Recycler recycler) {
for (int i = getChildCount() - 1; i >= 0; i--) { //内部调用mChildHelper.getChildCount()
final View view = getChildAt(i); //内部调用mChildHelper.getChildAt(index)
if (!getChildViewHolderInt(view).shouldIgnore()) { //note1
removeAndRecycleViewAt(i, recycler);
}
}
}
1、getChildViewHolderInt(view):通过当前view的LayoutParams值获取ViewHolder、这里的LayoutParams是RecyclerView的内部类,由当前View对应的ViewHolder
removeAndRecycleViewAt()@LayoutManager.class
public void removeAndRecycleViewAt(int index, Recycler recycler) {
final View view = getChildAt(index); //内部调用mChildHelper.getChildAt(index)
removeViewAt(index); //内部调用mChildHelper.removeViewAt(index);
recycler.recycleView(view);
}
小结:
dispatchDetachedFromWindow()方法中主要是调用了removeViewAt(index)、recycler.recycleView(view)和recycler.clear()方法。removeViewAt(index)将RecyclerView不再使用的View移除;recycler.recycleView(view)用于回收该ViewHolder。LayoutManager使用中的View都是交给ChildHelper完成的,Recycler负责回收ViewHolder。
dispatchAttachedToWindow()@LayoutManager.class
void dispatchAttachedToWindow(RecyclerView view) {
mIsAttachedToWindow = true;
onAttachedToWindow(view);//note1
}
1、该方法留给子类实现,但是子类基本都没有重写该方法
removeAndRecycleAllViews()@LayoutManager.class
该部分已经在dispatchDetachedFromWindow()@LayoutManager.class中介绍过
removeAndRecycleScrapInt()@LayoutManager.class
void removeAndRecycleScrapInt(Recycler recycler) {
final int scrapCount = recycler.getScrapCount();
for (int i = scrapCount - 1; i >= 0; i--) {
final View scrap = recycler.getScrapViewAt(i);
final ViewHolder vh = getChildViewHolderInt(scrap); //note1
if (vh.shouldIgnore()) { continue; }
vh.setIsRecyclable(false); //避免因关闭动画而导致的重复回收
if (vh.isTmpDetached()) { mRecyclerView.removeDetachedView(scrap, false); }
if (mRecyclerView.mItemAnimator != null) { mRecyclerView.mItemAnimator.endAnimation(vh); } //结束动画
vh.setIsRecyclable(true);
recycler.quickRecycleScrapView(scrap);
}
recycler.clearScrap();
if (scrapCount > 0) { mRecyclerView.invalidate(); }
}
1、getChildViewHolderInt(view):通过当前view的LayoutParams值获取ViewHolder、这里的LayoutParams是RecyclerView的内部类,由当前View对应的ViewHolder
scrollHorizontallyBy()@LayoutManager.calss
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { return 0; }
scrollHorizontallyBy()@LinearLayoutManager.calss
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mOrientation == VERTICAL) { return 0; }
return scrollBy(dx, recycler, state);
}
scrollBy()@LinearLayoutManager.calss
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) { //内部调用mChildHelper.getChildCount()方法
return 0;
}
mLayoutState.mRecycle = true;
ensureLayoutState(); //mLayoutState和mOrientationHelper初始化设置
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state); //.....
final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false); //绘制肯定在fill方法内部
if (consumed < 0) {
return 0;
}
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
mOrientationHelper.offsetChildren(-scrolled);
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
fill()@LinearLayoutManager.calss
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
......
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal(); //layoutChunkResult域初始化
layoutChunk(recycler, state, layoutState, layoutChunkResult); //note1 具体内容见后面
if (layoutChunkResult.mFinished) { break; }
........
} //end of While
return start - layoutState.mAvailable;
}
layoutChunk()@LinearLayoutManager.calss
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler); //note1
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
addView(view); //note2
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0); //会调用childView.measure方法
......
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
right - params.rightMargin, bottom - params.bottomMargin); //会调用childView.layout方法
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.isFocusable();
}
next()@ LayoutState.class@LinearLayoutManager.class
List<RecyclerView.ViewHolder> mScrapList = null;
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList(); //note1
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
首先从自身的mScrapList集合中获取一个View,命中则直接返回
利用recycler的getViewForPosition获取一个View,该部分我们放到后面讲
addView()@LayoutManager.calss
将View添加到RecyclerView中
scrollHorizontallyBy()@LayoutManager.calss
public int scrollVerticallyBy(int dy, Recycler recycler, State state) { return 0; }
scrollVerticallyBy()@LinearLayoutManager.calss
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state); //该方法在前面scrollHorizontallyBy()@LayoutManager.calss已经介绍过了
}
LayoutManager负责测量和摆放RecyclerView中itemView,利用ChildHelper mChildHelper管理LayoutManager使用的ItemView,利用Recycler获取回收View。简单讲就是从Recycler中取数据然后进行显示。
上面分析完了Adapter.class和LayoutManager.class,接着我们分析RecyclerView中非常重要的资源回收部分,这部分的操作实体是Recycler.class,下面我们需要重点分析的方法:
- recycler.clearScrap()
- recycler.getScrapViewAt(i)
- recycler.clear()
- recycler.recycleView(view)
- recycler.getViewForPosition(mCurrentPosition);
- recycler.quickRecycleScrapView(scrap)
Recycler.class——视图资源回收
Fields:
屏幕内缓存
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
屏幕外缓存
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();//存储最近刚被RecyclerView抛弃的ViewHolder,最多存2个
private static final int DEFAULT_CACHE_SIZE = 2;
private int mViewCacheMax = DEFAULT_CACHE_SIZE;
private RecycledViewPool mRecyclerPool = new RecycledViewPool() ; //里面会给每个类型的ViewHolder建立一个集合,每个这样的集合存储的元素最多5个。
private ViewCacheExtension mViewCacheExtension;
clearScrap()@Recycler.class
void clearScrap() {
mAttachedScrap.clear();
if (mChangedScrap != null) {
mChangedScrap.clear();
}
}
getScrapViewAt()@Recycler.class
View getScrapViewAt(int index) {
return mAttachedScrap.get(index).itemView;
}
clear()@Recycler.class
public void clear() {
mAttachedScrap.clear();
recycleAndClearCachedViews();
}
recycleAndClearCachedViews()@Recycler.class
void recycleAndClearCachedViews() {
final int count = mCachedViews.size();
for (int i = count - 1; i >= 0; i--) {
recycleCachedViewAt(i);
}
mCachedViews.clear();
}
recycleCachedViewAt()@Recycler.class
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder);
mCachedViews.remove(cachedViewIndex);
}
addViewHolderToRecycledViewPool()@Recycler.class
void addViewHolderToRecycledViewPool(ViewHolder holder) {
......
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder); //等于mRecyclerPool.putRecycledView(holder)
}
putRecycledView()@RecycledViewPool.class
private SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>();
private SparseIntArray mMaxScrap = new SparseIntArray(); //存储每个类型对应的能够缓存的最大ViewHolder数量
private int mAttachCount = 0;
private static final int DEFAULT_MAX_SCRAP = 5; //默认每个类型能够5个ViewHolder
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType(); //获取ViewHolder的类型
final ArrayList scrapHeap = getScrapHeapForType(viewType); //从mScrap集合中获取viewType类型的集合
if (mMaxScrap.get(viewType) <= scrapHeap.size()) { //缓存队列已满,直接返回
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
ArrayList<ViewHolder> scrap = mScrap.get(viewType);
if (scrap == null) { //mScrap没有当前类型的集合,创建一个集合添加进队列
scrap = new ArrayList<>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) { //如果mMaxScrap中没有当前类型,存入
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
}
}
return scrap;
}
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);
}
}
}
小结:将ArrayList<ViewHolder> mAttachedScrap集合中的数据全部清除、对于ArrayList<ViewHolder> mCachedViews集合中的数据先存入RecycledViewPool中,然后从mCachedViews集合中移除出去。RecyclerViewPool针对每种类型的ViewHolder都有一个对应的ArrayList的集合,该集合存储的最大ViewHolder数默认为5。不过通过setMaxRecycledViews方法可以修改指定类型存储的ViewHolder最大值。
recycleView()@Recycler.class
public void recycleView(View view) {
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()){
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
recycleViewHolderInternal()@Recycler.class
void recycleViewHolderInternal(ViewHolder holder) {
if (holder.isScrap() || holder.itemView.getParent() != null) { throw new IllegalArgumentException(...); }
if (holder.isTmpDetached()) { throw new IllegalArgumentException("Tmp detached view should be removed " ...); }
if (holder.shouldIgnore()) { throw new IllegalArgumentException("Trying to recycle an ignored view ...."); }
......
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
}
.....
}
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder); //clear()@Recycler.class中已经介绍过,就是把数据存入到池中
mCachedViews.remove(cachedViewIndex); //当前位置的ViewHolder从Cache中移除出去
}
小结:recycleView()方法会将参数存入到Recycler的 mCachedViews集合中。该集合默认只能存2个ViewHolder,如果当前集合中已经存有两个ViewHolder,则在将参数ViewHolder存入到集合前,会将0位置ViewHolder放入到回收池中。
getViewForPosition()@Recycler.class
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
getViewForPosition()@Recycler.class
View getViewForPosition(int position, boolean dryRun) {
......
boolean fromScrap = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position); //note1
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); //note2
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) { //判断得到的ViewHolder位置信息是否正确,不正确回收它
if (!dryRun) {
holder.addFlags(ViewHolder.FLAG_INVALID);
.... //使得当前View及其子View和Recycler脱离
recycleViewHolderInternal(holder);//该方法在 recycleView()@Recycler.class中已经介绍过
} //end of if (!dryRun)
holder = null;
}// end of if (!validateViewHolderForOffsetPosition(holder))
else { fromScrap = true; }
} // end of if (holder != null)
} // end of if (holder == null)
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
......
final int type = mAdapter.getItemViewType(offsetPosition); //获取该位置的ViewHolder所属类型
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); //note3
if (holder != null) {
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); //note4
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) { throw ....}
.....
}
}
if (holder == null) { // fallback to recycler
holder = getRecycledViewPool().getRecycledView(type);//note5
if (holder != null) {
holder.resetInternal();
.......
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type); //note6
}
}
//动画相关的初始化设置
......
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
holder.mOwnerRecyclerView = RecyclerView.this;
mAdapter.bindViewHolder(holder, offsetPosition);//note7
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
//ViewHolder的一些初始化设置
.......
return holder.itemView;
}
1、
遍历集合mChangedScrap中的所有ViewHolder,并根据holder.getLayoutPosition() == position是否为真判断是否是预期的ViewHolder
2、遍历集合mAttachedScrap中所有的ViewHolder,并根据holder.getLayoutPosition() == position是否为真判断是否是预期的ViewHolder
3、根据id和type在mAttachedScrap和mCachedViews中尝试获取ViewHolder
4、根据id和type在mViewCacheExtension 中尝试获取view
5、根据type在RecycledViewPool中尝试获取ViewHolder
6、利用Adapter.createViewHolder(Recyclerview, Type)方法获取一个ViewHolder
7、调用Adapter的bindViewholder进行一次绑定
quickRecycleScrapView()@Recycler.class
void quickRecycleScrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
holder.mScrapContainer = null;
holder.mInChangeScrap = false;
holder.clearReturnedFromScrapFlag();
recycleViewHolderInternal(holder); //该方法在 recycleView()@Recycler.class中已经介绍过
}
总结
回收流程
到此为止我们把RecyclerView的整个基本流程分析完毕了,重点了解了RecyclerView的资源回收部分如何实现。再次回顾一下RecyclerView的回收策略:
RecyclerView中用于回收视图的对象有:
- private ArrayList<ViewHolder> mChangedScrap
- private ArrayList<ViewHolder> mAttachedScrap
- private ArrayList<ViewHolder> mCachedViews //ViewHolder缓存列表,一级缓存
- private ViewCacheExtension mViewCacheExtension //由开发者控制的可以作为View缓存的帮助类,默认是获取null
- private RecycledViewPool mRecyclerPool 提供复用ViewHolder池。
- SparseArray<ArrayList<ViewHolder>> mScrap ;//每一个Type对应一个List,Type为int型,也就是说我们映射的键是int型而非一个对象,这点跟SparseArray这个集合有关!
- SparseIntArray mMaxScrap = new SparseIntArray();//每个Type对应的最大ViewHolder数量!
- RecyclerViewPool用于多个RecyclerView之间共享View;
- RecyclerView默认会创建一个RecyclerViewPool实例。也可通过setRecycledViewPool(RecycledViewPool) 方法,让RecycleView使用自定义RecyclerViewPool;
RecyclerView的视图回收流程是:
- 调用Recycler.getViewForPosition(int)方法获取View时,Recycler先检查mChangedScrap和mAttachedScrap ,没命中进行如下操作
- 调用ViewCacheExtension.getViewForPositionAndType(Recycler, int, int)获取View,没命中进行如下操作
- 检查RecyclerViewPool,如果还是没有命中则内部会调用Adapter的onCreateViewHolder等方法创建一个新的ViewHolder。
RecyclerView && ListView
最后我们简单对比一下ListView和RecyclerView的资源回收策略。
ListView的视图资源回收使用的是内部类RecyclBin、而RecyclerView使用的是多级缓存机制。即ListView中只有一个对象负责收集废弃的View,而RecyclerView有多个集合负责收集废弃的ViewHolder。针对不同的视图类型ListView的RecycleBin和RecyclerView的RecycledViewPool有如下两种不同的存储方式。
ListView的RecycleBin采用数组+ArrayList的形式:
- ListView的setAdapter方法内部会调用mRecycler.setViewTypeCount(mAdapter.getViewTypeCount())方法,方法会给RecycleBin创建mAdapter.getViewTypeCount()个ArrayList集合,每个集合对应一个View类型。android.widget.BaseAdapter的getViewTypeCount()方法默认返回1。表明在将Adapter和ListView绑定的那一刻起,RecycleBin所能缓存的View类型数是一定的。
- setViewTypeCount()内容如下:
RecyclerView的RecycledViewPool采用SparseArray+ArrayList的形式:private ArrayList<View>[] mScrapViews;public void setViewTypeCount(int viewTypeCount) {if (viewTypeCount < 1) { thow....}ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];for (int i = 0; i < viewTypeCount; i++) {scrapViews[i] = new ArrayList<View>();}mViewTypeCount = viewTypeCount;mCurrentScrap = scrapViews[0];mScrapViews = scrapViews;}
- private SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>();
- final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
- 注意:SparseArray是一个很神奇的数组,可以不按照位置存储数据,比如当前集合大小为10我们可以在15的位置插上一个数据,通过get(15)获取到对应的内容!以后有时间我们好好探究其如何实现!!