RecylerView 源码分析

RecylerView 继承自ViewGroup, 是一个容器类, 自定义一个容器类, 需要实现的方法:

1. 构造方法:

2.onMeasure()

3.onLayout()

4.onIntercepterTouchEvent()

5.onTouchEvent()

源码版本: 27.1.1

===========================================================================

RecylerView.java:

 

        @Override
L:3194  protected void onMeasure(int widthSpec, int heightSpec) {
         if (mLayout == null) {
          defaultOnMeasure(widthSpec, heightSpec);
          return;
        ...   
        //调用该方法,执行pre_layout, 然后获取子view 的尺寸
        dispatchLayoutStep2();
         }

 

private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount(); //调用了Adapter 的getItemCount()
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);  //执行预先布局,该方法由LayoutManager实现

    mState.mStructureChanged = false;
    mPendingSavedState = null;

    // onLayoutChildren may have caused client code to disable item animations; re-check
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}
public void onLayoutChildren(Recycler recycler, State state) {  L:7909
//需要在LayoutManager中实现该方法, 本次分析以LinearLayoutManager分析
    Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}

LinearLayoutManager.java;

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm: 布局算法
// 1) by checking children and other variables, find an anchor coordinate and an anchor 寻找锚点
//  item position.
// 2) fill towards start, stacking from bottom 
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state

...

fill(recycler, mLayoutState, state, false); //关键代码,获取ViewHolder
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    ...

   //recyclerview 剩余空间
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
   
 //循环遍历,填满RecyclerView
 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (layoutChunkResult.mFinished) {
            break;
        }
       ...
}

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler); //从缓存中获取ViewHolder, 如果没有,就创建ViewHolder

   ...
   
}

LayoutState.java:

View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition); //从Recyler.java 中获取View
    mCurrentPosition += mItemDirection;
    return view;
}

Recyler.java:

public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}
//尝试获得指定位置的ViewHolder,要么从scrap,cache,RecycledViewPool中获取,要么直接重新创建
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {

 

ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there  从changed scrap集合中获取ViewHolderif (mState.isPreLayout()) {
    holder = getChangedScrapViewForPosition(position);
    fromScrap = holder != null;
}
// 1) Find by position from scrap/hidden list/cache 通过position从attach scrap或一级回收缓存中获取ViewHolderif (holder == null) {   holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
   ...
}

// 2) Find from scrap/cache via stable ids, if exists

if (holder == null) {
   ...

    final int type = mAdapter.getItemViewType(offsetPosition);  //调用Adapter.getItemViewType()

    // 2) Find from scrap via stable ids, if exists
    if (mAdapter.hasStableIds()) {
        holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        if (holder != null) {
            // update position
            holder.mPosition = offsetPosition;
            fromScrap = true;
        }
    }
   ...
    if (holder == null) { // fallback to recycler
        // try recycler.
        // Head to the shared pool.
        if (DEBUG) {
            Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                    + "pool");
        }
        holder = getRecycledViewPool().getRecycledView(type); //根据type 从RecycledViewPool中获取对应类型的viewHolder

        ...
if (holder == null) { //如果缓存池中没有对应类型的ViewHolder,就调用Adapter.createViewHolder()创建
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
    if (DEBUG) {
        Log.d(TAG, "getViewForPosition created new ViewHolder");
    }
}
...
//获得ViewHolder后,调用bindViewHolder绑定视图数据bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);

}private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
        int position, long deadlineNs) {

....
mAdapter.bindViewHolder(holder, offsetPosition); //调用了Adapter.bindViewHolder()
public final void bindViewHolder(@NonNull VH holder, int position) {
    holder.mPosition = position;
    if (hasStableIds()) {
        holder.mItemId = getItemId(position);
    }
    holder.setFlags(ViewHolder.FLAG_BOUND,
            ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                    | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
    TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
    onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); 调用了Adapter.onBindViewHolder()
    holder.clearPayload();
    final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
    if (layoutParams instanceof RecyclerView.LayoutParams) {
        ((LayoutParams) layoutParams).mInsetsDirty = true;
    }
    TraceCompat.endSection();
}

至此,Adapter 的各个方法调用流程已经完成. 

 

接下来执行onLayout()

void dispatchLayout() {
    if (mAdapter == null) {
        Log.e(TAG, "No adapter attached; skipping layout");
        // leave the state in START
        return;
    }
    if (mLayout == null) {
        Log.e(TAG, "No layout manager attached; skipping layout");
        // leave the state in START
        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()) {
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
}

 

 

这里的代码就比较好理解了,并且上面提到的问题也就迎刃而解了,当我们给RecyclerView设置固定的宽高的时候,onMeasure是直接跳过了执行,那么为什么子View仍然能绘制出来。 
这里可以看到,如果onMeasure没有执行,mState.mLayoutStep == State.STEP_START就成立,所以仍然会执行 dispatchLayoutStep1(), dispatchLayoutStep2();也就对应的会绘制子View。 
而后面的注释也比较清楚,由于我们在Layout的时候改变了宽高,也会导致dispatchLayoutStep2();,也就是子View的重新绘制。 
如果上面情况都没有,那么onLayout的作用就仅仅是dispatchLayoutStep3(),而 dispatchLayoutStep3()方法的作用除了重置一些参数,外还和执行动画有关
 

 

 

 

1.RecyclerView是将绘制流程交给LayoutManager处理,如果没有设置不会测量子View。 
2.绘制流程是区分正向绘制和倒置绘制。 
3.绘制是先确定锚点,然后向上绘制,向下绘制,fill()至少会执行两次,如果绘制完还有剩余空间,则会再执行一次fill()方法。 
4.LayoutManager获得View是从RecyclerView中的Recycler.next()方法获得,涉及到RecyclerView的缓存策略,如果缓存没有拿到,则走我们自己重写的onCreateView方法。 
5.如果RecyclerView宽高没有写死,onMeasure就会执行完子View的measure和Layout方法,onLayout仅仅是重置一些参数,如果写死,子View的measure和layout会延后到onLayout中执行。
 

 

 

 

 

 

RecycledViewPool.java:

 

 

 

public ViewHolder getRecycledView(int viewType) {
    final ScrapData scrapData = mScrap.get(viewType); //根据type 从SparseArray<ScrapData> mScrap 中获取
    if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
        final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        return scrapHeap.remove(scrapHeap.size() - 1);
    }
    return null;
}

 

//===========================以下来源于网络=======================

  • RecyclerView中,并不是每次绘制表项,都会重新创建ViewHolder对象,也不是每次都会重新绑定ViewHolder数据。
  • RecyclerView通过Recycler获得下一个待绘制表项。
  • Recycler有4个层次用于缓存ViewHolder对象,优先级从高到底依次为ArrayList<ViewHolder> mAttachedScrapArrayList<ViewHolder> mCachedViewsViewCacheExtension mViewCacheExtensionRecycledViewPool mRecyclerPool。如果四层缓存都未命中,则重新创建并绑定ViewHolder对象
  • RecycledViewPoolViewHolderviewType分类存储(通过SparseArray),同类ViewHolder存储在默认大小为5的ArrayList
  • mRecyclerPool中复用的ViewHolder需要重新绑定数据,从mAttachedScrap中复用的ViewHolder不需要重新创建也不需要重新绑定数据
  • mRecyclerPool中复用的ViewHolder,只能复用于viewType相同的表项,从mCachedViews中复用的ViewHolder,只能复用于指定位置的表项。


 

 

=============================================华丽的分割线=========================================

RecyclerView 相关文章记录:

RecyclerView缓存机制 :https://juejin.im/post/5c696ba9e51d457f136d24ff   作者: Taylor

RecyclerView库中的遗珠 :https://blog.csdn.net/qq_17766199/article/details/83147483   作者: 唯鹿

记一次失败的RecycleView滑动定位 :https://www.jianshu.com/p/7734ae5f149c   作者: 点先生在这

自定义LayoutManager实现商品属性界面https://github.com/xiangcman/LayoutManager-FlowLayout

自定义LayoutManager实现卡片效果: https://github.com/DingMouRen/LayoutManagerGroup

 

RecylerView优化:

降低item的布局层次(使用ConstraintLayout)

去除冗余的setitemclick事件:

 

将setitemclick事件的绑定和viewholder对应的rootview进行绑定,viewholer由于缓存机制的存在它创建的个数是一定的,所以和它绑定的setitemclick对象也是一定的。

 

还有另一种做法可以通过rv自带的addOnItemTouchListener来实现点击事件,原理就是rv在触摸事件中会使用到addOnItemTouchListener中设置的对象,然后配合GestureDetectorCompat实现点击item

复用pool缓存:

这个复用pool是针对item中包含rv的情况才适用,如果rv中的item都是普通的布局就不需要复用pool

保存嵌套rv的滑动状态:

linearlayoutmanager中有对应的onSaveInstanceState和onRestoreInstanceState方法来分别处理保存状态和恢复状态,它的机制其实和activity的状态恢复非常类似,我们需要做的就是当rv被移除屏幕调用onSaveInstanceState,移回来时调用onRestoreInstanceState即可。

视情况设置itemanimator动画:

关闭的方法直接调用setItemAnimator(null)即可

diffutil一个神奇的工具类:

弊端一:


看示例代码应该也能察觉到,要想使用diffutil必须准备两个数据集,这就是一个比较蛋疼的事情.

原先我们只需要维护一个数据集就可以,现在就需要我们同时维护两个数据集,两个数据集都需要有一份自己的数据.

如果只是简单将数据从一个集合copy到另一个集合是可能会导致问题的,会涉及到对象的深拷贝和浅拷贝问题,你必须保证两份数据集都有各自独立的内存,否则当你修改其中一个数据集可能会造成另一个数据集同时被修改掉的情况。

弊端二:

为了实现callback接口必须实现四个方法,其中areContentsTheSame是最难实现的一个方法,因为这里涉及到对比同type的item内容是否一致,这就需要将该item对应的数据bean进行比较,怎么比较效率会高点,目前能想到的方法就是将bean转换成string通过调用equals方法进行比较,如果item的数据bean对应的成员变量很少如示例所示那倒还好,这也是网上很多推荐diffutil文章避开的问题。

但是如果bean对应的成员很多,或者成员变量含有list,里面又包含各种对象元素,想想就知道areContentsTheSame很难去实现,为了引入一个diffutil额外增加这么多的逻辑判断有点得不偿失。

弊端三:

diffutil看起来让人捉摸不透的item动画行为,以上面代码为例

setHasFixedSize:

swapadapter:

getAdapterPosition和getLayoutPosition:

大部分情况下调用这两个方法得到的结果是一致的,都是为了获得holder对应的position位置,但getAdapterPosition获取位置更为及时,而getLayoutPosition会滞后到下一帧才能得到正确的position,如果你想及时得到holder对应的position信息建议使用前者。

removeview和detachview:

这两个方法在rv进行排布item的时候会遇到,removeview就是大家很常见的操作,但是detachview就不太常见了,其实removeview是一个更为彻底的移除view操作,内部是会调用到detachview的,并且会调用到我们很熟悉的ondetachfromwindow方法,而detachview是一个轻量级的操作,内部操作就是简单的将该view从父view中移除掉,rv内部调用detachview的场景就是对应被移除的view可能在近期还会被使用到所以采用轻量级的移除操作,removeview一般都预示着这个holder已经彻底从屏幕消失不可见了

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值