Android中的RecyclerView源码分析

RecyclerView组件

  1. Adapter
    提供数据以及数据变更通知
  2. LayoutManager
    布局管理,负责测绘、指定item位置、item回收、
  3. ItemAnimator
    Item变更动画

关键实现

1、ViewHolder复用

三层缓存
第一层:Recycler中的mCachedViews

第二层:ViewCacheExtension 由开发者实现的缓存策略,可通过setViewCacheExtension设置到RecyclerView中

第三层:RecycledViewPool中的mScrap 可在多个RecyclerView共享的View缓存池

Scrapped View(废弃的View):一个View虽然仍附属在它的父视图RecyclerView上,但是已经被标记为已使用或已迁移。这样的View就叫做Scrapped View。

获取一个View的过程

//RecyclerView.Recycler
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        private ArrayList<ViewHolder> mChangedScrap = null;

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

        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
//所有获取View调用的入口
public View getViewForPosition(int position) {
            //调用内部实现
            //参数 position 获取一个View的位置
            //     false   不进行预检操作  dryrun
            return getViewForPosition(position, false);
        }

View getViewForPosition(int position, boolean dryRun) {
    //参数检查
    ...
    //如果处于提前布局状态,尝试从mChangedScrap列表寻找
    if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
     //如果没找到,则从mAttachedScrap列表中寻找
     holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
     //如果没找到,或者找到的ViewHolder和OffsetPosition不匹
     //配,则重新计算OffsetPosition。并且根据stable ids在
     //mAttachedScrap中寻找(如果mAdapter存在stableIds)。
      if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
    //如果还找不到,则从mViewCacheExtension中寻找,
    //前提是开发者为RecyclerView设置了ViewCacheExtension
    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);
    ....
    ....
    //如果没有设置ViewCacheExtension,肯定还是找不到的,
    //那么就会从RecycledViewPool中寻找。
    //因为RecycledViewPool是多个RecyclerView共享的,如果找到合适的ViewHolder,要先更新需要显示的信息。
    holder = getRecycledViewPool().getRecycledView(type);
    ...
    ...
    //最后一层缓存也找不到的话,只能新建了
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
     ...
//填充数据
 mAdapter.bindViewHolder(holder, offsetPosition);

     ...
     return holder.itemView; 
}

2、数据变更通知(观察者模式)

观察者AdapterDataObserver,具体实现为RecyclerViewDataObserver,当数据源发生变更时,及时响应界面变化

public static abstract class AdapterDataObserver {
        public void onChanged() {
            // Do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            // fallback to onItemRangeChanged(positionStart, itemCount) if app
            // does not override this method.
            onItemRangeChanged(positionStart, itemCount);
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            // do nothing
        }
    }

被观察者AdapterDataObservable,内部持有观察者AdapterDataObserver集合

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

        public void notifyItemRangeChanged(int positionStart, int itemCount) {
            notifyItemRangeChanged(positionStart, itemCount, null);
        }

        public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
            // since onItemRangeChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
            }
        }

        public void notifyItemRangeInserted(int positionStart, int itemCount) {
            // since onItemRangeInserted() is implemented by the app, it could do anything,
            // including removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
            }
        }

        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
            }
        }

        public void notifyItemMoved(int fromPosition, int toPosition) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
            }
        }
    }

Adapter内部持有AdapterDataObservable对象。
当我们为RecyclerView设置Adapter时,会向Adapter注册一个观察者RecyclerViewDataObserver。

adapter.registerAdapterDataObserver(mObserver);

当数据变更时,调用notify**方法时,Adapter内部的被观察者会遍历通知已经注册的观察者的对应方法,这时界面就会响应变更。

3、多种布局类型展示
基类LayoutManager
子类有LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager

测量入口:RecyclerView的onMeasure方法

//mAutoMeasure代表是否是由已经存在的LayoutManager去执行测绘工作。
//当使用自定义的LayoutManager时,需要设置此值为false
if (mLayout.mAutoMeasure) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            //如果RecyclerView的测量模式为EXACTLY(准确模式)
            //则不必依赖子视图的尺寸来确定RecyclerView本身的高度
            //子视图的测量工作会延迟到onLayout方法中
            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();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            //此方法会真正执行LayoutManager的测量布局操作
            dispatchLayoutStep2();
            .....
            .....
 /**
     * The second layout step where we do the actual layout of the views for the final state.
     * This step might be run multiple times if necessary (e.g. measure).
     */
    private void dispatchLayoutStep2() {
    ...
    ...
    //mLayout即RecyclerView设置的LayoutManager
    mLayout.onLayoutChildren(mRecycler, mState);
    ...
    ...
   }
//LayoutManager的这个方法是一个空实现,具体布局操作由子类决定
//当自定义LayoutManager时,这是必须要实现的一个方法
public void onLayoutChildren(Recycler recycler, State state) {
            Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
        }
//这里以LinearLayoutManager为例,说明布局过程
//通过注释可以很好的理解测量和布局算法流程
@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.
        ...
        ...
        //第一步,通过检查子视图和其他变量,找出锚点坐标和item位置锚点
        //第二步,从底部向起始位置填充
        //第三步,从顶部向末尾位置填充
        //第四步,滚动要满足要求的位置,例如当从底部填充时
        ...
        ...
        //当锚点计算完成后,就开始填充view
        //LayoutManager的三个子类都有这个填充方法,但是不同的
        //的布局类型,此方法会有不同的实现
        fill(recycler, mLayoutState, state, false);

4、Item变更动画

5、其他

notifyDataSetChanged发生了什么

1、为页面中存在的ViewHolder和Recycler中mCachesView中缓存的ViewHolder添加一个标志:ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN

2、检查有没有等待执行的更新操作(add/remove/update/move)。如果没有的话,并且当前没有处于布局状态,并且没有设置禁止LayoutFrozen,调用requestLayout()方法重新布局。

3、如果有等待执行的更新操作怎么办?
不允许刷新

notifyItemInserted发生了什么

1、RecyclerView把Item的插入、删除、移动、变更抽象成了一个个命令UpdateOp,并在AdapterHelper中维护了一个命令池。当插入一条Item时,向更新的命令队列中添加一个“ADD”命令。

mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));

2、然后会触发更新处理器,来处理新的命令。

3、RecyclerView会启动一个Runnable来处理等待执行的命令。

    /** * Note: this Runnable is only ever posted if: * 1) We've been through first layout * 2) We know we have a fixed size (mHasFixedSize) * 3) We're attached */
    private final Runnable mUpdateChildViewsRunnable = new Runnable() {
        public void run() {
            if (!mFirstLayoutComplete || isLayoutRequested()) {
                // a layout request will happen, we should not do layout here.
                return;
            }
            if (mLayoutFrozen) {
                mLayoutRequestEaten = true;
                return; //we'll process updates when ice age ends.
            }
            consumePendingUpdateOperations();
        }
    };

但是我们在单步调试的时候,发现条件isLayoutRequested()为true,所以这个Runnable什么都没干,直接返回了。
????

既然isLayoutRequested()为true,说明接下来会重新布局,可能执行更新的地方放在了layout中。
我们在dispatchLayout()方法中的第二步dispatchLayoutStep1();发现了processAdapterUpdatesAndSetAnimationFlags()方法;
该方法主要做了两件事:第一,根据操作类型和数量更新Adapter中Item的位置(例,插入一个新的Item,原列表中Item的Position要依次加一)。第二,设置RecyclerView的状态mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations为true。

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/joye123/article/details/52219857
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞