RecyclerView 全面的源码分析

RecyclerView 概要

RecyclerView是Android 5.0开始提供一个可回收容器,它比 ListView更先进灵活更具扩展性,可高效重用和滚动,能方便定制各种布局和交互效果。它继承自 ViewGroup,那么它展示和交互也离不开 measure , layout , draw , touch ,adapter 五步。后面将以这五步来抽丝剥茧分析其工作原理,其中 LayoutManager 以最简单的 LinearLayoutManager为例。更多经典自定义 ViewGroup集合

RecyclerView 的辅助类非常多,相联紧密的类都作为其内部类存在,如下图:

recycler_class

在这个类之外还有比较重要的三个类:AdapterHelper,ChildHelper,ViewInfoStore 。AdapterHelper负责处理 Adapter 里的数据集发生变化时的预处理;ChildHelper负责管理和访问 RecyclerView 的子视图;ViewInfoStore 记录 pre-layout 和 post-layout 阶段的 ViewHolder 状态信息,方便分析差异做相应的动画。

RecyclerView 的使用

首先需要引入 recyclerview-v7 的包,在 build.gradle的 dependencies 块中添加

compile 'com.android.support:recyclerview-v7:25.2.0@aar'
或
compile(name: 'recyclerview-v7-25.2.0', ext: 'aar') //直接引用 aar 文件,需要flatDir中添加 aar 目录dirs。

使用时自已的 YourAdapter 需要继承自 RecyclerView.Adapter,YourViewHolder 要继承RecyclerView.ViewHolder.
除此对 RecyclerView 还需要设置 LayoutManager,SDK提供了常用的三种布局管理器LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager。要设置divider或其它修饰可通过RecyclerView的addItemDecoration来定制。而事件可以在 Adapter 的onBindViewHolder里去绑定。总的来说 RecyclerView 的定制性是相当高的,用法就不多讲,下面重点讲解其工作原理。

RecyclerView(25.2.0) 原理分析(五个维度深入)。

<a name="part_measure" id="part_measure" /> 1.measure 过程,因为RecyclerView 的默认三大布局管理器的 mAutoMeasure 默认都是true,所以measure过程实际被LayoutManager接管。关键源码如下:

protected void onMeasure(int widthSpec, int heightSpec) {
    ......
    if (mLayout.mAutoMeasure) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        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) {
            dispatchLayoutStep1();
        }
        ......
        dispatchLayoutStep2();                      
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        ......
    }else{
        ......
    }

首先调用了LayoutManager的onMeasure,内部只是调用了RecyclerView的defaultOnMeasure,这一步实质是在忽略子视图情况下测绘容器大小。当RecyclerView本身的宽和高都是精确值即没有wrap_content时skipMeasure为true 此时子视图的测绘将会延迟到 onLayout 过程,后续会讲到。skipMeasure 为 true 或 adapter 为空时都是没有必要测绘子视图来决定自身大小的。不确定自身大小(存在wrap_content)才会走后续的 dispatchLayoutStep1()和 dispatchLayoutStep2()方法。而测绘和布局子视图就是在dispatchLayoutStep2这一步完成的。当调用mLayout.setMeasuredDimensionFromChildren,内部会调用 RecyclerView.setMeasuredDimension方法从而定格了 RecyclerView 自身容器的大小。

注:dispatchLayoutStep2在onLayout 中也有调用稍后会讲到其具体实现

<a name="part_layout" id="part_layout" /> 2.layout 过程调用了dispatchLayout,它会根据mState.mLayoutStep的值调用不同的dispatchLayout步骤,源码概要如下:

void dispatchLayout() {
    ......
    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();

可以发现 layout 过程也像 onMeasure 里有类似调用 即dispatchLayoutStep1和dispatchLayoutStep2. 这是因为RecyclerView对子视图的 measure 和 layout 都是通过dispatchLayoutStep2这个方法一起完成的。当然我们不必担心它会重复调用了,因为还有一个mState.mLayoutStep来控制测绘步骤呢。当值为STEP_START时会调用dispatchLayoutStep1之后赋值mLayoutStep = State.STEP_LAYOUT,当值为STEP_LAYOUT时才会调用dispatchLayoutStep2然后再赋值mLayoutStep = State.STEP_ANIMATIONS,而当值为STEP_ANIMATIONS时才会调用dispatchLayoutStep3然后重新赋值为STEP_START。确保 onMeasure 到 onLayout 中各步骤只会有一次。

<a name="part_measure_layout" id="part_measure_layout" /> 3.重点分析dispatchLayoutStep三个方法和 Recycler 类。

每一次数据集变化或是调用了 requestLayout 都会走dispatchLayoutStep这几个方法。这个方法也是 RecyclerView 对子视图填充布局和做动画的核心所在。上面在 onMeasure 和 onLayout 时也多次出现,以下就讲综合讲述它们各阶段的作用 。
��回顾 measure回顾 layout

其中最重要的是实现了子视图测量和填充的方法是dispatchLayoutStep2,它是,下面我们先讲
dispatchLayoutStep1和dispatchLayoutStep3,这两个方法都是与执行不同操作的动画紧密相关且相对简单些(动作如 Adapter 的 add、remove、insert 等)。

(1).dispatchLayoutStep1 布局预处理,step3 做动画的基础。

private void dispatchLayoutStep1() {
      ......
      mViewInfoStore.clear();
      if (mState.mRunSimpleAnimations) {
          // Step 0: Find out where all non-removed items are, pre-layout
          int count = mChildHelper.getChildCount();
          for (int i = 0; i < count; ++i) {
              final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
              if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                  continue;
              }
              ......
              mViewInfoStore.addToPreLayout(holder, animationInfo);
              if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                      && !holder.shouldIgnore() && !holder.isInvalid()) {
                  long key = getChangedHolderKey(holder);
                  ......
                  mViewInfoStore.addToOldChangeHolders(key, holder);
              }
          }
      }
      if (mState.mRunPredictiveAnimations) {
        ......
      } 
      ......
      mState.mLayoutStep = State.STEP_LAYOUT;
}

这部分主要是对已经存在容器里的 View 的 ViewHolder预处理信息分类暂时存放到mViewInfoStore中,为后面做动画有个差异分析提供基础。

(2). dispatchLayoutStep3 布局 post 处理,执行相应动作的动画。


private void dispatchLayoutStep3() {
    ......
    mState.mLayoutStep = State.STEP_START;
    if (mState.mRunSimpleAnimations) { 
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                ......
                long key = getChangedHolderKey(holder);
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPostLayoutInformation(mState, holder);
                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                    final boolean oldDisappearing = mViewInfoStore.isDisappearing(
                            oldChangeViewHolder);
                    final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                    if (oldDisappearing && oldChangeViewHolder == holder) {
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                    } else {
                        final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
                                oldChangeViewHolder);
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                        ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                        animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
                                                        oldDisappearing, newDisappearing);
                    }
                } else {
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                }
            }
            mViewInfoStore.process(mViewInfoProcessCallback);
    }
    .....
}

经过dispatchLayoutStep2重新填充后视图会更新或多或少,通过对比 dispatchLayoutStep1 阶段暂存在mViewInfoStore中的 pre-layout 信息,做一些处理后将当前ViewHolder和动画信息添加到mViewInfoStore的 post-layout 中。这两个阶段 addToPreLayout 和 addToPostLayout 都会添加相应的标记,最后调用mViewInfoStore.process(mViewInfoProcessCallback)在内部进行处理后并回调才执行相应操作的动画。见下面动画预处理后的回调处源码:

private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
        @Override
        public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                @Nullable ItemHolderInfo postInfo) {
            mRecycler.unscrapView(viewHolder);
            animateDisappearance(viewHolder, info, postInfo);
        }
        @Override
        public void processAppeared(ViewHolder viewHolder,
                ItemHolderInfo preInfo, ItemHolderInfo info) {
            animateAppearance(viewHolder, preInfo, info);
        }
        ......
}        

<a name="part_dispatchlayout" id="part_dispatchlayout" /> (3). dispatchLayoutStep2 正真的测量和填充子视图。

RecyclerView 的子视图测量和布局,最终还是委托给了 LayoutManager 来处理见源码:

private void dispatchLayoutStep2() {
    ......
    mLayout.onLayoutChildren(mRecycler, mState);
    ......
}

//LinearLayoutManager 的onLayoutChildren方法
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ......
    updateAnchorInfoForLayoutExpose(state, mAnchorInfo);
    ......
    detachAndScrapAttachedViews(recycler);
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start 和下面的逻辑分支是反过来的,仅看下面的逻辑为讲解。
        ......
    } else {
        // fill towards end
        updateLayoutStateToFillEndExpose(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        if (mLayoutState.mAvailable > 0) {
            extraForStart += mLayoutState.mAvailable;
        }
        // fill towards start
        updateLayoutStateToFillStartExpose(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
    }
    ......
}
  • 第一步找锚点和位置确定相对坐标和 item 位置;
  • 对容器上的 View 做detach 和回收处理见调用detachAndScrapAttachedViews,回收过程讲解
  • 填充 View 到可用空间,以锚点为界,在布局方向以相反的两个方向扩张来填充可用空间,单向填充算法.

以描点为界向两个方向填充的算法示意图如下,其中红色点为描点位置:

anchor_fill.jpg

<a name="part_fillchild" id="part_fillchild" /> 初始化时无描点的 itemView 此时按 (1)或(2)进行,当长视图滑动时填充就会出现(3)示意的填充。LinearLayoutManager单一方向的填充算法源码如下:

protected int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                       RecyclerView.State state, boolean stopOnFocusable) {
    ......
    if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
        ......
        recycleByLayoutStateExpose(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    while (remainingSpace > 0 && layoutState.hasMore(state)) {
        layoutChunkResultCache.resetInternal();
        layoutChunk(recycler, state, layoutState, layoutChunkResultCache);
        if (layoutChunkResultCache.mFinished) {
            break;
        }
        layoutState.mOffset += layoutChunkResultCache.mConsumed * layoutState.mLayoutDirection;
        if (!layoutChunkResultCache.mIgnoreConsumed || mLayoutState.mScrapList != null
           || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResultCache.mConsumed;
            // we keep a separate remaining space because mAvailable is important for recycling
            remainingSpace -= layoutChunkResultCache.mConsumed;
        }         ......
    }
    if (DEBUG) {
        validateChildOrderExpose();
    }
    return start - layoutState.mAvailable;
}

首先会通过recycleByLayoutStateExpose方法移除已经滑出屏幕以外的子视图,然后通过一个循环的朝着一个布局方向每次调用layoutChunk添加 View,并适时调节剩余空间和下次 layout 的 offset,直到剩下的空间不足循环结束。而子视图逐个的测量和布局就是在layoutChunk这个方法里进行的,看下面部分源码:

protected void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                               LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    ......
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        // can not find in scrapList
        if (mShouldReverseLayoutExpose == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    }
    ......
    measureChildWithMargins(view, 0, 0);
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    int left, top, right, bottom;
    if (getOrientation() == VERTICAL) {
        if (isLayoutRTL()) {
            right = getWidth() - getPaddingRight();
            left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
        } else {
            left = getPaddingLeft();
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            bottom = layoutState.mOffset;
            top = layoutState.mOffset - result.mConsumed;
        } else {
            top = layoutState.mOffset;
            bottom = layoutState.mOffset + result.mConsumed;
        }
    } else {
    ......
    }
    // We calculate everything with View's bounding box (which includes decor and margins)
    // To calculate correct layout position, we subtract margins.
    layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
            right - params.rightMargin, bottom - params.bottomMargin);
    ......
    result.mFocusable = view.isFocusable();
}

此方法通过layoutState.next(recycler)复用机制获取下一个需要布局的 View, 将它通过 ViewGroup 的 addView 方法添加到 RecyclerView 里面来,然后通过measureChildWithMargins(view, 0, 0)测量它的大小,最后通过layoutDecorated方法实际上调用了 child.layout(left,top,right,bottom)来把子视图布局到相应的位置。

<a name="part_recycler" id="part_recycler" /> (4).复用回收过程是Recycler负责的,涉及 ViewHolder 的创建和回收,它是 RecyclerView的内部类,具有三级的缓存结构外加一个扩展缓存接口:

public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;
    ......
}

先讲View和ViewHolder 的创建。接上文,layoutChunk 添加View时调用了layoutState.next(recycler)来获取下个 View,实际调用了 Recycler的getViewForPosition方法。

public View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextFromLimitedList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}
//以下 next 会调用下面方法。
View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

最终是调用Recycler的tryGetViewHolderForPositionByDeadline 这个方法分以下几步:

(1)检查mChangedScrap,若匹配到则返回相应holder
(2)检查mAttachedScrap,若匹配到且holder有效则返回相应holder
(3)检查mViewCacheExtension,若匹配到则返回相应holder
(4)检查mRecyclerPool,若匹配到则返回相应holder
(5)否则执行Adapter.createViewHolder(),新建holder实例
(6)返回holder再通过 holder.itemView 得到了要添加的 View.

此方法比较长,看下面关键源码:

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
    ......
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) Find by position from scrap/hidden list/cache
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        ......
    }
    if (holder == null) {
        ......
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exists
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            ......
        }
        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);
            if (view != null) {
                holder = getChildViewHolder(view);
                ......
            }
        }
        if (holder == null) { // fallback to pool
            holder = getRecycledViewPool().getRecycledView(type);
            ......
        }
        if (holder == null) {
            ......
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            ......
        }
    }
    ......
    boolean bound = false;
    if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        if (DEBUG && holder.isRemoved()) {
            throw new IllegalStateException("Removed holder should be bound and it should"
                    + " come here only in pre-layout. Holder: " + holder);
        }
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    ......
    return holder;
}

基本上就是通过各级缓存获取 ViewHolder,获取不到就调用mAdapter.createViewHolder 创建 。最终的 ViewHolder 如果需要绑定就会调用tryBindViewHolderByDeadline来绑定数据,其内部调用了mAdapter.bindViewHolder。

下面我们再看看 Recycler 的回收过程,

  • 在重新设置 Adapter 或通知某个item 变法时,相应的 ViewHolder 会加上标志位如UPDATE,REMOVE 等,待onLayoutChildren时就会先调用detachAndScrapAttachedViews其最终也是调用了Recycler 的的recycleViewHolderInternal方法。

  • 滑动过程中不断回收不可见的 ViewHolder,见fill 方法中的recycleByLayoutStateExpose,最终是调用了LayoutManager 的 removeAndRecycleViewAt。

    //先移除再回收,
    public void removeAndRecycleViewAt(int index, Recycler recycler) {
        final View view = getChildAt(index);
        removeViewAt(index);
        recycler.recycleView(view);
    }

而 removeViewAt 是通过帮助类ChildHelper来移除的,最终回收仍是调用了Recycler 的recycleViewHolderInternal

回收关键方法recycleViewHolderInternal很长,关键源码如下:

void recycleViewHolderInternal(ViewHolder holder) {
    ......
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            ......
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    }
    ......
    mViewInfoStore.removeViewHolder(holder);
 }

对于移除的 View 如果是有效的无update 或remove 动作就添加到mCachedViews中,否则失效的 ViewHolder 会添加到RecycledViewPool中。

在 Recycler的各级缓存中,只有RecycledViewPool是按照 viewType 去存储和获取缓存的 ViewHolder 来达到复用,其它缓存需要匹配 layoutPosition 或 getItemId再加上 viewType 。缓存级别最高的是mChangedScrap它是预处理layout时添加的,对应方法 dispatchLayoutStep1;mAttachedScrap是其次,它是attach到容器未移除的缓存复用很适用频繁抖动滑动;mCachedViews再次,它是 detach 的 ViewHolder 缓存,mViewCacheExtension是用户扩展的缓存池一般用户是没有实现的,最后才是 RecyclerViewPool它只需要按viewType 返回。

4.draw 过程比较简单,只是简单循环调用了ItemDecoration去绘制背景和前景视图,

(1).以下是ItemDecoration接口的设置和内部使用的代码。

public void addItemDecoration(ItemDecoration decor) {
    addItemDecoration(decor, -1);
}

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);
    }
}
    @Override
public void draw(Canvas c) {
    super.draw(c);
    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDrawOver(c, this, mState);
    }
    ......
 }

(2). ItemDecoration 的灵活性比较高,主要需要用户实现 onDraw,onDrawOver 和 getItemOffset三个方法,且都不是必须的。

 public static abstract class ItemDecoration {
    public void onDraw(Canvas c, RecyclerView parent, State state){ }
    public void onDrawOver(Canvas c, RecyclerView parent, State state){}
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {}
 }

在对 itemView 画装饰时候,为了实现 Item 之间的间隔或是避免画的内容覆盖到 itemView 上,就需要通过getItemOffsets返回,itemView 的需要预留出的四边内间距,如下视图。

item_decoration

整个 item 所占的空间也就是最外面虚线的边界,在 measure 过程中这部分其实也是考虑进去了。

5.手势滑动,RecyclerView 的手势滑动和大多数滑动控件类似处理了onInterceptTouchEvent 和 onTouchEvent事件。

fling平滑滑动过程是借助ViewFlinger不断postOnAnimation 内部调用ScrollerCompat工具进行计算,每次滑动 dx,dy,这就和ACTION_MOVE 时的滑动一致了。
最终手势滑动或 fling 滑动都会回调给 LayoutManager的scrollHorizontallyBy(dx...)或scrollVerticallyBy(dy...)内部又会调用 scrollBy 如下

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    ......
    final int consumed = mLayoutState.mScrollingOffset
            + fill(recycler, mLayoutState, state, false);
    ......
    mLayoutState.mLastScrollDelta = scrolled;
    return scrolled;
}

可见滑动过程也会调用 LinearLayoutManager 的 fill 方法,上面讲过,内部会回收移除屏幕的 View和 ViewHolder,并填充新的View 和 ViewHolder.

6.进阶 Adapter和数据集的更新过程.

每次重新设置不同的 Adapter 或是更改 Adapter 里的数据集合并调用 notifyItemXXX 时,最终都会触发当前存在的 ViewHolder里的标记位发生变化。加上FLAG_UPDATE, FLAG_REMOVE等不同操作的标记位。然后再 requestLayout 后会重新走 上面讲到的 dispatchLayoutStep 的三个流程。界面的变化更新也主要是在第dispatchLayoutStep2 后完成。

(1).Adapter 发生变化时

Adapter 改变后比较简单,直接重置所有无效。清除所有的缓存 ViewHolder 调用了removeAndRecycleViews();并且调用markKnownViewsInvalid标记了容器中的所有 View 无效(FLAG_UPDATE)。

(2).Adapter 数据合集操作并调用相应的notifyItemXXX 方法。

在设置 Adapter 给 RecyclerView 时就会绑定一个数据变化时的观察者DataObserver。当更改数据集后用户需要调用
notifyItemChanged,notifyItemRemoved,notifyItemInsert 等方法,然后DataObserver就会接收到相应的 onItemChaged,onItemRemoved,onItemInsert 等回调并由AdapterHelper对应的onItemXXX来接管。在AdapterHelper内每个操作都对应一个UpdateOp

    static class UpdateOp {
        static final int ADD = 1;
        static final int REMOVE = 1 << 1;
        static final int UPDATE = 1 << 2;
        static final int MOVE = 1 << 3;
        static final int POOL_SIZE = 30;

        int cmd;
        int positionStart;
        ......
    }

AdapterHelper的onItemXXX 方法是有有bool型的返回值,为true表示需要处理触发triggerUpdateProcessor调用,实际post 执行以下 Runnable:

    final Runnable mUpdateChildViewsRunnable = new Runnable() {
        @Override
        public void run() {
            if (!mFirstLayoutComplete || isLayoutRequested()) {
                // a layout request will happen, we should not do layout here.
                return;
            }
            if (!mIsAttached) {
                requestLayout();
                // if we are not attached yet, mark us as requiring layout and skip
                return;
            }
            if (mLayoutFrozen) {
                mLayoutRequestEaten = true;
                return; //we'll process updates when ice age ends.
            }
            consumePendingUpdateOperations();
        }
    };

没有首次layout 过,或是layout 后续即将触发时直接return ,这里就防止了频繁操作会多次执行操作。consumePendingUpdateOperations方法,调用AdapterHelper的preProcess进行预处理内部预处理暂存的一系列 UpdateOp 对象后再通过callback 回调给RecyclerView,而RecyclerView 再传给了 Recycler 处理,即对 ViewHolder 添加不同的标记。
最后调用了dispatchLayout 进行重新执行 dispatchLayoutStep 三步来更新视图。在dispatchLayoutStep1 中也会调用processAdapterUpdatesAndSetAnimationFlags来处理mAdapterHelper 中的UpdateOp.

只要 ViewHolder 有了标记后在下一个requestLayout 期间会调用到onMeasure,onLayout最终会触发dispatchLayoutStep三步,在dispatchLayoutStep2 里会更新视图,而在dispatchLayoutStep 3 里会播放相应操作的动画 。

总结

至此 RecyclerView 工作的源码分析基本完成,其相关辅助类比较多,源码很长,不对或不详尽之处请大家多提意见,后续再分享 VLayout,欢迎拍砖.更多经典自定义 ViewGroup集合

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,468评论 25 707
  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,374评论 0 27
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 5,138评论 0 16
  • 【经文】我的心哪,你為何忧闷?為何在我裡面烦躁?应当仰望神,因他笑脸帮助我,我还要称讚他。(诗篇 42:5) 祂用...
    斐斐feifei阅读 1,578评论 1 1
  • 绿荫苒苒 繁花成簇春暮 更兼黄沙漫漫夕阳暮因困在那华屋艳巢 朱门绮户 不得出
    爱码爱自由阅读 305评论 0 7