RecyclerView 绘制、复用、对比ListView详解编程语言

之前学习ListView是为了更好地学习RecyclerView,没想到拖了这么久。

RecyclerView比ListView更加地复杂,主要从两个方面学习:

  1. RecyclerView 绘制流程
  2. RecyclerView 缓存复用
  3. ListView和RecyclerView缓存比较

RecyclerView 涉及的类

  1. LayoutManager : 负责子View的measure及layout
  2. Adapter: 适配器、负责创建子View和绑定数据到子View中
  3. Recycler:ViewHolder的回收和复用,有四种类型的缓存:crapped、cached、exCached、recycled

[1.0] RecyclerView 绘制流程

[1.1] onMeasure 测量

protected void onMeasure(int widthSpec, int heightSpec) {
    
    if (mLayout == null) {
    // LayoutManager不存在的情况 
        // 默认测量,见小节1.1.1 
        defaultOnMeasure(widthSpec, heightSpec); 
        return; 
    } 
    // 需要自动测量 
    if (mLayout.mAutoMeasure) {
    
        final int widthMode = MeasureSpec.getMode(widthSpec); 
        final int heightMode = MeasureSpec.getMode(heightSpec); 
        // 如果是宽高都是固定数值,skipMeasure为true,跳过测量步骤 
        final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY 
                && heightMode == MeasureSpec.EXACTLY; 
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 
        // 跳过测量步骤 
        if (skipMeasure || mAdapter == null) {
    
            return; 
        } 
        if (mState.mLayoutStep == State.STEP_START) {
     
            // 收集ItemView的ViewHolder的信息并保存,见小节1.1.2 
            dispatchLayoutStep1(); 
        } 
        mLayout.setMeasureSpecs(widthSpec, heightSpec); 
        // 实际测量 
        dispatchLayoutStep2(); 
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 
        // 如果recyclerview 有额外的宽高,需要二次测量   
        if (mLayout.shouldMeasureTwice()) {
    
            // 真正测量,见小节1.1.3 
            dispatchLayoutStep2(); 
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 
        } 
    } else {
    
       //若自定义LayoutManager,自行实现 
    } 
} 

[1.1.1] defaultOnMeasure

当不存在LayoutManager时候,会继续默认测量

void defaultOnMeasure(int widthSpec, int heightSpec) {
    
    // 根据measureSpec算出一个具体数值 
    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); 
} 
 
// LayoutManager 
public static int chooseSize(int spec, int desired, int min) {
    
    final int mode = View.MeasureSpec.getMode(spec); 
    final int size = View.MeasureSpec.getSize(spec); 
    switch (mode) {
    
        case View.MeasureSpec.EXACTLY: 
            return size; 
        case View.MeasureSpec.AT_MOST: 
            return Math.min(size, Math.max(desired, min)); 
        case View.MeasureSpec.UNSPECIFIED: 
        default: 
            return Math.max(desired, min); 
    } 
} 

默认测量,直接是利用View.MeasureSpec测算,所以可能会出现各种问题

[1.1.2] dispatchLayoutStep1

测量的状态有几个:

  1. STEP_START: 初始状态
  2. STEP_LAYOUT: 第一阶段preLayout完成,等待第二阶段真正的布局layout
  3. STEP_ANIMATIONS: 第二阶段layout完毕,等待第三阶段开始处理动画

dispatchLayoutStep1 主要工作:

  1. 处理adapter的数据更新
  2. 决定需要运行的动画
  3. 保存当前views的信息
  4. 若需要,提前layout和保存信息
private void dispatchLayoutStep1() {
    
    mState.assertLayoutStep(State.STEP_START); 
    processAdapterUpdatesAndSetAnimationFlags(); 
    if (mState.mRunSimpleAnimations) {
    
        // Step:保存未删除的ViewHolder的信息,进行pre_layout 
        int count = mChildHelper.getChildCount(); 
        for (int i = 0; i < count; ++i) {
    
            ... 
            // 保存所有的ViewHolder信息 
            mViewInfoStore.addToPreLayout(holder, animationInfo); 
            if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() 
                    && !holder.shouldIgnore() && !holder.isInvalid()) {
    
                long key = getChangedHolderKey(holder); 
                // 保存变化的ViewHolder 
                mViewInfoStore.addToOldChangeHolders(key, holder); 
            } 
        } 
    } 
    if (mState.mRunPredictiveAnimations) {
    
        // Step 1: 运行预布局 
        mLayout.onLayoutChildren(mRecycler, mState); 
    } else {
    
        clearOldPositions(); 
    } 
    mState.mLayoutStep = State.STEP_LAYOUT; 
} 

[1.1.3] dispatchLayoutStep2

真正执行LayoutManager绘制的地方

private void dispatchLayoutStep2() {
    
        // Step 2: Run layout 
        mState.mInPreLayout = false; 
        mLayout.onLayoutChildren(mRecycler, mState); 
} 

最后真正的测量动作是mLayout,mLayout是LayoutManager,我们看LinearLayoutManager

// LinearLayoutManager 
@Override 
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    
        // detach所有view到recycler 
        detachAndScrapAttachedViews(recycler); 
        // 倒着绘制 
        if (mAnchorInfo.mLayoutFromEnd) {
    
            // 先从锚点往上 
            updateLayoutStateToFillStart(mAnchorInfo); 
            // 填充,见小节[2.1] 
            fill(recycler, mLayoutState, state, false); 
            //  再从锚点往下 
            updateLayoutStateToFillEnd(mAnchorInfo); 
            fill(recycler, mLayoutState, state, false); 
            endOffset = mLayoutState.mOffset; 
        } else {
    
            // 正向绘制 
            // 先从锚点往下 
            updateLayoutStateToFillEnd(mAnchorInfo); 
            // 填充 
            fill(recycler, mLayoutState, state, false); 
            // fill towards start 
            updateLayoutStateToFillStart(mAnchorInfo); 
            fill(recycler, mLayoutState, state, false); 
            startOffset = mLayoutState.mOffset; 
        } 
    } 

参考: https://www.jianshu.com/p/e9752f8890c8
锚点

[1.2] onLayout 布局

RecyclerView继承自ViewGroup

// RecyclerView 
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
    dispatchLayout(); 
    mFirstLayoutComplete = true; 
} 
 
void dispatchLayout() {
    
    mState.mIsMeasuring = false; 
    // 如果未测量过,则调用dispatchLayoutStep1、dispatchLayoutStep2 
    if (mState.mLayoutStep == State.STEP_START) {
    
        dispatchLayoutStep1(); 
        mLayout.setExactMeasureSpecsFrom(this); 
        dispatchLayoutStep2(); 
    // 如果adapter发生更新或者宽高变了,则调用dispatchLayoutStep2 
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() 
            || mLayout.getHeight() != getHeight()) {
    
        mLayout.setExactMeasureSpecsFrom(this); 
        dispatchLayoutStep2(); 
    } else {
    
        mLayout.setExactMeasureSpecsFrom(this); 
    } 
    dispatchLayoutStep3(); 
} 

dispatchLayout做了一次保证,保证执行dispatchLayoutStep1()、dispatchLayoutStep2(),保证测量动作已经完成,最后执行dispatchLayoutStep3(),它主要是负责执行动画

[1.2.1] dispatchLayoutStep3

dispatchLayoutStep3

// 保存信息,执行动画、相应的清理工作 
private void dispatchLayoutStep3() {
    
    this.mState.assertLayoutStep(State.STEP_ANIMATIONS); 
    this.mState.mLayoutStep = State.STEP_START; 
    if (this.mState.mRunSimpleAnimations) {
    
        for (int i = this.mChildHelper.getChildCount() - 1; i >= 0; --i) {
    
            // 保存viewholder布局 
              this.mViewInfoStore.addToPostLayout(holder, animationInfo); 
        } 
          // 执行各个执行动画 
        this.mViewInfoStore.process(this.mViewInfoProcessCallback); 
    } 
   // 清空view信息 
    this.mViewInfoStore.clear(); 
} 

measure、layout流程

  1. dispatchLayoutStep1: Adapter的更新; 决定该启动哪种动画; 保存当前View的信息,如果有必要,先跑一次布局并将信息保存下来。
  2. dispatchLayoutStep2: 实际测量流程
  3. dispatchLayoutStep3: 保存View的相关信息; 触发动画; 相应的清理工作。
  4. LayoutManager负责真正测量子View的布局,ItemAnimator负责执行子View的动画

[1.3] onDraw 绘制

绘制基本交给了ViewGroup,RecyclerView只是绘制了ItemDecoration

public void onDraw(Canvas c) {
    
    super.onDraw(c); 
    int count = this.mItemDecorations.size(); 
    for (int i = 0; i < count; ++i) {
    
        ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState); 
    } 
} 

[2.0] RecyclerView 缓存复用

RecyclerView 有四级缓存类型,比ListView多了两种;

缓存级别 参与对象 作用
一级缓存 mChangedScrap、mAttachedScrap mChangedScrap 仅参与预布局 preLayout ;mAttachedScrap存放还会被复用的ViewHolder,但最多包含屏幕可见表项,不需要重新绑定数据
二级缓存 mCachedViews 最多存放2个缓存ViewHolder
三级缓存 mViewCacheExtension 自定义实现
四级缓存 mRecyclerPool SparseArray类型,每个viewType最多可以存放5个ViewHolder

其中,mChangedScrap实际并未参加真实的缓存过程,它的添加和移除ViewHolder都出现在dispatchLayoutStep1方法中的PreLayout(预布局)过程

[2.1] 获取缓存

fill 布局填充

// LinearLayoutManager 
int fill(Recycler recycler, LinearLayoutManager.LayoutState layoutState, State state, boolean stopOnFocusable) {
    
    int start = layoutState.mAvailable; 
    // 剩余空间 
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra; 
    LinearLayoutManager.LayoutChunkResult layoutChunkResult = this.mLayoutChunkResult; 
 
    while((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
    
        layoutChunkResult.resetInternal(); 
        // 根据剩余位置,不断填充空白块 
        this.layoutChunk(recycler, state, layoutState, layoutChunkResult); 
        if (layoutChunkResult.mFinished) {
    
            break; 
        } 
 
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; 
        if (!layoutChunkResult.mIgnoreConsumed || this.mLayoutState.mScrapList != null || !state.isPreLayout()) {
    
            layoutState.mAvailable -= layoutChunkResult.mConsumed; 
            remainingSpace -= layoutChunkResult.mConsumed; 
        } 
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
    
            break; 
        } 
    } 
 
    return start - layoutState.mAvailable; 
} 

[2.1.0] layoutChunk

实际填充mCurrentPostion的位置、并添加布局子View

void layoutChunk(Recycler recycler, State state, LinearLayoutManager.LayoutState layoutState, LinearLayoutManager.LayoutChunkResult result) {
    
   // 获取或创建view 
   View view = layoutState.next(recycler); 
   // 添加view  
    if (layoutState.mScrapList == null) {
    
            if (mShouldReverseLayout == (layoutState.mLayoutDirection= LayoutState.LAYOUT_START)) {
    
                addView(view); 
            } else {
    
                addView(view, 0); 
            } 
        }  
    // 测量view 
    ... 
    this.measureChildWithMargins(view, 0, 0); 
    ... 
} 

获取子View,会优先取出在屏幕中活跃的子View,再根据位置从废弃的子view中选出

View next(Recycler recycler) {
    
    if (this.mScrapList != null) {
    
        // 从Scrap优先取出活跃的view,见小节[2.1.1] 
        return this.nextViewFromScrapList(); 
    } else {
    
        // 从Recycler获取,见小节[2.1.2] 
        View view = recycler.getViewForPosition(this.mCurrentPosition); 
        this.mCurrentPosition += this.mItemDirection; 
        return view; 
    } 
} 

[2.1.1] nextViewFromScrapList

// 从Scrap取出缓存 
private View nextViewFromScrapList() {
    
    int size = this.mScrapList.size(); 
    for(int i = 0; i < size; ++i) {
    
        View view = ((ViewHolder)this.mScrapList.get(i)).itemView; 
        LayoutParams lp = (LayoutParams)view.getLayoutParams(); 
        if (!lp.isItemRemoved() && this.mCurrentPosition == lp.getViewLayoutPosition()) {
    
            this.assignPositionFromScrapList(view); 
            return view; 
        } 
    } 
    return null; 
} 

[2.1.2] getViewForPosition

getViewForPosition: 获取指定位置的子View,是最核心的取ViewHolder缓存或创建ViewHolder的部分

 
View getViewForPosition(int position, boolean dryRun) {
 
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; 
} 
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
 
boolean fromScrapOrHiddenOrCache = false; 
ViewHolder holder = null; 
// 0. 通过position从 mChangedScrap 中获取 
if (mState.isPreLayout()) {
 
holder = getChangedScrapViewForPosition(position); 
fromScrapOrHiddenOrCache = holder != null; 
} 
//1. 通过position 从 mAttachedScrap 获取 
if (holder == null) {
 
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); 
} 
if (holder == null) {
 
final int type = mAdapter.getItemViewType(offsetPosition); 
//2. 通过id在mAttachedScrap集合中查找 
if (mAdapter.hasStableIds()) {
 
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); 
} 
//3. 从自定义缓存中获取 
if (holder == null && mViewCacheExtension != null) {
 
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); 
} 
//4.从缓存池获取 
if (holder == null) {
 // fallback to pool 
holder = getRecycledViewPool().getRecycledView(type); 
} 
//没有命中所有缓存,创建ViewHolder 
if (holder == null) {
 
holder = mAdapter.createViewHolder(RecyclerView.this, type); 
} 
} 
if (mState.isPreLayout() && holder.isBound()) {
 
... 
else {
 //没有绑定就重新绑定 
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); 
} 
return holder; 
} 

实际上,mChangedScrap没有参加ViewHolder的缓存过程,只是在dispatchLayoutStep1方法中的PreLayout中缓存了一下ViewHolder;mAttachedScrap表示未发生改变的ViewHolder,不需要重新绑定,在重绘过程中,会将屏幕内的子View添加到mAttachedScrap,fill过程再从mAttachedScrap取出,不需要重绑。

[2.2] 缓存回收

当view被滑出屏幕的时候,会触发回收流程

[2.2.1] recycleView

public void recycleView(View view) {
 
ViewHolder holder = getChildViewHolderInt(view); 
recycleViewHolderInternal(holder); 
} 
void recycleViewHolderInternal(ViewHolder holder) {
 
boolean cached = false; 
boolean recycled = false; 
if (forceRecycle || holder.isRecyclable()) {
 
if (mViewCacheMax > 0 
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 
| ViewHolder.FLAG_REMOVED 
| ViewHolder.FLAG_UPDATE 
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
 
// Retire oldest cached view 
int cachedViewSize = mCachedViews.size(); 
// 最大为两个 
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
 
// 缓存数量大于2的时候将最先进来的ViewHolder移除 
recycleCachedViewAt(0); 
cachedViewSize--; 
} 
mCachedViews.add(targetCacheIndex, holder); 
cached = true; 
} 
// 添加到recyclePool 
if (!cached) {
 
addViewHolderToRecycledViewPool(holder, true); 
recycled = true; 
} 
} 
mViewInfoStore.removeViewHolder(holder); 
} 

mCachedViews最多为两个,超过两个时候,会将最先的移除到RecyclerPool,再保存到mCachedViews。

// 回收指定位置的mCachedViews 
void recycleCachedViewAt(int cachedViewIndex) {
 
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); 
addViewHolderToRecycledViewPool(viewHolder, true); 
mCachedViews.remove(cachedViewIndex); 
} 

[2.2.2] addViewHolderToRecycledViewPool

addViewHolderToRecycledViewPool 添加ViewHolder到mRecyclerPool

void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
 
holder.mOwnerRecyclerView = null; 
getRecycledViewPool().putRecycledView(holder); 
} 

RecycledViewPool的SparseArray以ViewType为区分,每种类型保存至多5个ViewHolder

//RecycledViewPool 
public static class RecycledViewPool {
 
private static final int DEFAULT_MAX_SCRAP = 5; 
static class ScrapData {
 
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); 
int mMaxScrap = DEFAULT_MAX_SCRAP; // 5 
} 
SparseArray<ScrapData> mScrap = new SparseArray<>(); 
} 

[3.0] ListView 和 RecyclerView 缓存比较

  • ListView 缓存的是子View、RecyclerView 缓存的是ViewHolder

  • ListView 的RecyclerBin 分成ActiveView和ScrapView两种;ActiveView负责回收当前界面的活跃的View,可以直接复用不需要重新绑定,用于屏幕内快速复用;ScrapView负责根据ViewType回收划出屏幕非活跃的子View,需要重新绑定

  • RecyclerView的Recycler 分成四级缓存crapped、cached、exCached、recycled,除了crapped,用于屏幕内快速复用,其他都需要重新绑定,比ListView多了cached、exCached两级

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/19389.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论