RecyclerView

952 阅读14分钟

基本使用

  1. # RecyclerView GridLayoutManager 等分间距
  2. # 基于RecyclerView实现网格分页LayoutManager——PagerGridLayoutManager
  3. # GridLayoutManager这么用,你可能还真没尝试过

开源库

明天待看

文章

RecyclerView动画

源码解析

0. 待看
mp.weixin.qq.com/s/08LpubdLT…
www.jianshu.com/p/1ae2f2fcf…
www.jianshu.com/p/f2a5764a3…
www.jianshu.com/p/5735db176…

1. RecyclerView缓存机制

2. RecyclerView测量,布局,绘制流程,Recycler,Adapter

onMeasure
-->

onLayout
	--> dispatchLayout
		--> 
		case mState.mLayoutStep == State.STEP_START:
                  dispatchLayoutStep1();//D1
                  dispatchLayoutStep2();//D2
                  dispatchLayoutStep3();//D3
                case D1,D2在onMeasure中已经执行过,但是此时RecyclerView的宽高发生变化:
                  dispatchLayoutStep2();
                  dispatchLayoutStep3();
                default:
                  dispatchLayoutStep3();

-->
onDraw
  1. dispatchLayoutStep1
  • 记录ViewHolder状态及子View信息
  • 如有必要/mState.mRunPredictiveAnimations是true,则执行'预布局'
    mState.mStructureChanged = false;
    // temporarily disable flag because we are asking for previous layout
    mLayout.onLayoutChildren(mRecycler, mState);
    
  1. dispatchLayoutStep2
  • 使用LayoutManager对子View真正进行布局
  • 如有必要,dispatchLayoutStep2会执行多次
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
  1. dispatchLayoutStep3
  • 将之前保存的ViewHolder和其对应的Item动画信息进行绑定/存储
  • 执行Item动画.
  • 真正执行动画逻辑的是RecyclerView.ItemAnimator
  • 将布局,动画交给LayoutManager,ItemAnimator,可以由用户自定义,实现不同的布局方式及布局动画.
  1. onDraw:单纯绘制分隔线
@Override
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);
    }
}
  1. Recycler
public final class Recycler {
  static final int DEFAULT_CACHE_SIZE = 2;
  private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;//就是2
  int mViewCacheMax = DEFAULT_CACHE_SIZE;//就是2

  //一级缓存: mAttachedScrap + mChangedScrap
  final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
  ArrayList<ViewHolder> mChangedScrap = null;

  //二级缓存: mCachedViews
  final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

  //三级缓存
  private ViewCacheExtension mViewCacheExtension;

  //四级缓存
  RecycledViewPool mRecyclerPool;

  ****
}
  • 缓存存储的是ViewHolder实例
  • 一级缓存: mChangedScrap + mAttachedScrap
    • mChangedScrap
      • 仅参与预布局
    • mAttachedScrap
      • 存放还会被复用的ViewHolder
  • 二级缓存: mCachedViews
    • 最多存放2个ViewHolder
  • 三级缓存: mViewCacheExtension,需要开发者自行实现ViewCacheExtension
  • 四级缓存: mRecyclerPool
    • 可以将RecycledViewPool当做1个SparseArray. key是要存储的ViewHolder的viewType, value是ArrayList.
    • 每个ArrayList最多存放5个ViewHolder实例,已满则不再存放
    public static class RecycledViewPool {
        private static final int DEFAULT_MAX_SCRAP = 5;
        static class ScrapData {
          //真正存放ViewHolder实例的位置
          final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
          int mMaxScrap = DEFAULT_MAX_SCRAP;//5
          long mCreateRunningAverageNs = 0;
          long mBindRunningAverageNs = 0;
        }
        //每个存储的ViewHolder实例都被放在 ScrapData 中的 mScrapHeap 中.
        SparseArray<ScrapData> mScrap = new SparseArray<>();
    
        //向RecycledViewPool中存储ViewHolder实例
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            //最多存放5个ViewHolder实例,已满则不再存放
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            //之前已经存放了,报异常
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            //将ViewHolder实例添加到scrapHeap中
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }
        //通过ViewHolder的viewType,获取存储过的/新创建的ScrapData实例
        private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }
    }
    
  • 通过Recycler获取ViewHolder实例
    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }
    View getViewForPosition(int position, boolean dryRun) {
        return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }
    ViewHolder tryGetViewHolderForPositionByDeadline(int position,
            boolean dryRun, long deadlineNs) {
        if (position < 0 || position >= mState.getItemCount()) {
            //这儿就是RecyclerView内部的BUG抛出异常的位置
            throw new IndexOutOfBoundsException("Invalid item position " + position
                    + "(" + position + "). Item count:" + mState.getItemCount()
                    + exceptionLabel());
        }
        boolean fromScrapOrHiddenOrCache = false;
        ViewHolder holder = null;
        // 0) 如果处于'预布局',则从mChangedScrap中获取ViewHolder实例
        if (mState.isPreLayout()) {
            holder = getChangedScrapViewForPosition(position);
            fromScrapOrHiddenOrCache = holder != null;
        }
        // 1) 通过position,从mAttachedScrap,已移出屏幕的视图(隐藏列表项),及mCachedViews中获取ViewHolder实例
        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()) {
                //通过id,从mAttachedScrap及mCachedViews中获取ViewHolder实例
                holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                        type, dryRun);
                ***
            }
            if (holder == null && mViewCacheExtension != null) {
                //从自定义缓存中尝试获取ViewHolder实例
                // We are NOT sending the offsetPosition because LayoutManager does not
                // know it.
                ***
            }
            if (holder == null) { // fallback to pool
                //通过RecycledViewPool在缓存池中获取ViewHolder实例
                holder = getRecycledViewPool().getRecycledView(type);
                ***
            }
            if (holder == null) {
                ***
                //所有缓存都没有命中,只能通过Adapter创建ViewHolder实例
                holder = mAdapter.createViewHolder(RecyclerView.this, type);
                ***
            }
        }
        ***
        boolean bound = false;
        ***
        //只有invalid的viewHolder才能绑定视图数据
        //未绑定 || 需要更新 || 已失效
        if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            ***
            //对于需要绑定数据的ViewHolder执行tryBindViewHolderByDeadline.
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
        }
        ***
        return holder;
    }
    private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
            int position, long deadlineNs) {
        ***
        mAdapter.bindViewHolder(holder, offsetPosition);
        ***
        return true;
    }
    
    public abstract static class Adapter<VH extends ViewHolder> {
        public final void bindViewHolder(@NonNull VH holder, int position) {
            ***
            //熟悉的味道,会调用我们自定义Adapter的:
            //onBindViewHolder(@NonNull VH holder,int position,@NonNull List<Object> payloads)
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            holder.clearPayload();
            ***
        }
    }
    
    • RecyClerView通过Recycler获取下一个待绘制列表项.
    • Recycler有4层缓存用于缓存ViewHolder实例.
      • 优先级依次为: mChangedScrap --> mAttachedScrap --> mCachedViews --> mViewCacheExtension --> mRecyclerPool
      • mCachedViews用于缓存指定位置的ViewHolder,默认最多存储2个,已满情况,按照先进先出原则将第一个ViewHolder实例存入mRecyclerPool.
      • RecycledViewPool 对ViewHolder按viewType分类存储,同类ViewHolder被存放在最大长度为5的ArrayList中.
      • 若4层缓存都未命中,则通过Adapter实例创建ViewHolder实例,并执行数据绑定.
    • Recycler获取到ViewHolder实例,要不要执行数据绑定?
      • 通过mChangedScrap,mAttachedScrap,mCachedViews中获取的ViewHolder实例不需要
      • 通过mRecyclerPool 及 Adapter 获取的ViewHolder实例需要执行数据绑定
  1. ViewHolder的回收
  • ViewHolder回收的场景很多,以最常见的滑动为例.滑动涉及事件处理,看onTouchEvent的MOVE事件.
    @Override
    public boolean onTouchEvent(MotionEvent e) {
      ***
      switch (action) {
        ***
        case MotionEvent.ACTION_MOVE:
            scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,vtev)
      }
    }
    boolean scrollByInternal(int x, int y, MotionEvent ev) {
      ***
      if (mAdapter != null) {
        scrollStep(x, y, mScrollStepConsumed);
        ***
      }
    }
    void scrollStep(int dx, int dy, @Nullable int[] consumed) {
      if (dx != 0) {
          consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
      }
      if (dy != 0) {
          consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
      }
    }
    
    androidx/recyclerview/widget/LinearLayoutManager.java
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
      ***
      return scrollBy(dy, recycler, state);
    }
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        ***
        updateLayoutState(layoutDirection, absDy, true, state);
        final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);
        ***
    }
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
        ***
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            //通过Recycler获取ViewHolder实例
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            ***
            //通过Recycler回收ViewHolder
            recycleByLayoutState(recycler, layoutState);
        }
    }
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
        //通过Recycler获取ViewHolder,并进一步获取到View
        View view = layoutState.next(recycler);
        ***
        //将获取到的Item的View摆放到正确的位置
        layoutDecoratedWithMargins(view, left, top, right, bottom);
    }
    View next(RecyclerView.Recycler recycler) {
        ***
        //通过Recycler获取ViewHolder,并进一步获取到View
        final View view = recycler.getViewForPosition(mCurrentPosition);
        mCurrentPosition += mItemDirection;
        return view;
    }
    public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
            int bottom) {
        //将获取到的Item的View摆放到正确的位置
        child.layout(***);
    }
    //通过Recycler回收ViewHolde实例
    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        ***
        recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        ***
    }
    private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
        ***
        final int limit = dt;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
          View child = getChildAt(i);
          if (mOrientationHelper.getDecoratedEnd(child) > limit
            || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
            //回收指定index以上的所有ViewHolder
            recycleChildren(recycler, 0, i);
            return;
          }
        }
    }
    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        ***
        if (endIndex > startIndex) {
            for (int i = endIndex - 1; i >= startIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            ***
        }
    }
    public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
        final View view = getChildAt(index);
        //将对应的View从RecyclerView中移除
        removeViewAt(index);
        //Recycler进行回收
        recycler.recycleView(view);
    }
    public void recycleView(@NonNull View view) {
        //通过View获取到ViewHolder实例
        ViewHolder holder = getChildViewHolderInt(view);
        ***
        //Recycler真正回收ViewHolder实例的逻辑
        recycleViewHolderInternal(holder);
    }
    void recycleViewHolderInternal(ViewHolder holder) {
        if (forceRecycle || holder.isRecyclable()) {
            //这里的判断条件决定了复用mCachedViews中的ViewHolder时不需要重新绑定数据
            //重新执行数据绑定的条件: !holder.isBound() || holder.needsUpdate() || holder.isInvalid()
            //!holder.isBound()对于mCachedViews 中的ViewHolder来说必然为false,
            //只有当调用ViewHolder.resetInternal()重置ViewHolder后,才会将其设置为未绑定状态.
            //而只有存入回收池时才会重置ViewHolder
            if (mViewCacheMax > 0
                            && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_REMOVED
                            | ViewHolder.FLAG_UPDATE
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                // 如果mCachedViews已经存满了2个,则执行 recycleCachedViewAt(0) 
                int cachedViewSize = mCachedViews.size();
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                    //将cachedViewSize中最早加入的ViewHolder实例先存入mRecyclerPool中
                    //再将其从mCachedViews中移除
                    recycleCachedViewAt(0);
                    cachedViewSize--;
                }
                int targetCacheIndex = cachedViewSize;
                if (ALLOW_THREAD_GAP_WORK
                        && cachedViewSize > 0
                        && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                    // when adding the view, skip past most recently prefetched views
                    int cacheIndex = cachedViewSize - 1;
                    while (cacheIndex >= 0) {
                        int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                        if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                            break;
                        }
                        cacheIndex--;
                    }
                    targetCacheIndex = cacheIndex + 1;
                }
                mCachedViews.add(targetCacheIndex, holder);
                cached = true;
            }
            if (!cached) {
                addViewHolderToRecycledViewPool(holder, true);
                recycled = true;
            }
        } else {
            ***
        }
        // even if the holder is not removed, we still call this method so that it is removed
        // from view holder lists.
        mViewInfoStore.removeViewHolder(holder);
        if (!cached && !recycled && transientStatePreventsRecycling) {
            holder.mOwnerRecyclerView = null;
        }
    }
    void recycleCachedViewAt(int cachedViewIndex) {
        ***
        //获取mCachedViews中最先存放的ViewHolder实例
        ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
        ***
        //将该ViewHolder实例放入mRecyclerPool中
        addViewHolderToRecycledViewPool(viewHolder, true);
        //将该ViewHolder实例从mCachedViews中移除
        mCachedViews.remove(cachedViewIndex);
    }
    void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
        clearNestedRecyclerViewIfNotNested(holder);
        ***
        getRecycledViewPool().putRecycledView(holder);
    }
    public void putRecycledView(ViewHolder scrap) {
        final int viewType = scrap.getItemViewType();
        final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
        //最多存储5个同类型的ViewHolder实例
        if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
            return;
        }
        ***
        scrap.resetInternal();
        //将ViewHolder实例加入scrapHeap
        scrapHeap.add(scrap);
    }
    
  • 从上述代码可知,在使用LayoutManager执行视图填充/ViewHolder获取过程中,也伴随着ViewHolder的回收.回收指定position范围内的ViewHolder实例
    • 通过索引值找到Item对应的View,将其从RecyclerView中移除
    • 将对应的ViewHolder实例通过Recycler进行回收
      • 优先尝试将ViewHolder实例存入mCachedViews,可能伴随老的ViewHolder实例被移出并存入mRecyclerPool
      • 尝试存入 mCachedViews 失败,则直接将ViewHolder实例存入mRecyclerPool
  1. LayoutManager
  • 抽丝剥茧RecyclerView - LayoutManager
  • 这么用GridLayoutManager,你可能还真没尝试过
  • Orient-Ui 上面文章对应的代码
  • LayoutManager主要作用
    • 通过Recycler,回收和复用ViewHolder
    • 关于滑动的处理,涉及滑动过程中调用 ==> fill ==> ViewHolder的获取及回收
    • 测量及布局子View.
  • 重点解释:
    • 测量及布局子View
    • Recycler中 mAttachedScrap 的数据何时存储
    • fill中什么时候会涉及ViewHolder的回收
  • 测量
    androidx/recyclerview/widget/RecyclerView.java
    public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
      LayoutManager mLayout;
    
      public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
              int heightSpec) {
          mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
      }
      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));
        //单纯设置RecyclerView自身宽高
        setMeasuredDimension(width, height);
      }
      //计算宽高
      void setMeasureSpecs(int wSpec, int hSpec) {
        mWidth = MeasureSpec.getSize(wSpec);
        mWidthMode = MeasureSpec.getMode(wSpec);
        if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
            mWidth = 0;
        }
        mHeight = MeasureSpec.getSize(hSpec);
        mHeightMode = MeasureSpec.getMode(hSpec);
        if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
            mHeight = 0;
        }
      }
      @Override
      protected void onMeasure(int widthSpec, int heightSpec) {
          ***
          if (mLayout.isAutoMeasureEnabled()) {
              //LayoutManage的isAutoMeasureEnabled默认返回值是true
              final int widthMode = MeasureSpec.getMode(widthSpec);
              final int heightMode = MeasureSpec.getMode(heightSpec);
              ***
              //默认实现就是单纯设置RecyclerView自身宽高
              mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
              //当RecyclerView的宽高都是MATCH_PARENT或精确数值,return
              final boolean measureSpecModeIsExactly =
                      widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
              if (measureSpecModeIsExactly || mAdapter == null) {
                  return;
              }
              //mLayoutStep默认值就是State.STEP_START,会走dispatchLayoutStep1.
              //会走dispatchLayoutStep1 可能会执行'预布局'
              if (mState.mLayoutStep == State.STEP_START) {
                  dispatchLayoutStep1();
              }
              //1:计算宽高
              mLayout.setMeasureSpecs(widthSpec, heightSpec);
              mState.mIsMeasuring = true;
              //2:真正执行布局子View
              //会调用 mLayout.onLayoutChildren(mRecycler, mState);
              dispatchLayoutStep2();
              //3:获取尺寸最大的子View,将其范围/Rect作为参数得到RecyclerView的尺寸,设置为RecyclerView的宽高
              mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
              //若需要二次测量,则再执行一次 1->2->3
              //shouldMeasureTwice 默认返回false.
              //LinearLayoutManager重写了该方法,通常情况也会返回false
              /*
              LinearLayoutManager:
              @Override
              boolean shouldMeasureTwice() {
                  return getHeightMode() != View.MeasureSpec.EXACTLY
                          && getWidthMode() != View.MeasureSpec.EXACTLY
                          && hasFlexibleChildInBothOrientations();
              }
              //lp.width < 0 && lp.height < 0 ,不知道什么情况会小于0
              boolean hasFlexibleChildInBothOrientations() {
                  final int childCount = getChildCount();
                  for (int i = 0; i < childCount; i++) {
                      final View child = getChildAt(i);
                      final ViewGroup.LayoutParams lp = child.getLayoutParams();
                      if (lp.width < 0 && lp.height < 0) {
                          return true;
                      }
                  }
                  return false;
              }
              */
              if (mLayout.shouldMeasureTwice()) {
                  //4:需要二次测量,则再执行一次 1->2->3
                  mLayout.setMeasureSpecs(
                          MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                          MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                  mState.mIsMeasuring = true;
                  dispatchLayoutStep2();
                  mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
              }
          } else {
              ***
          }
      } 
    }
    
    • 测量总结:
      • isAutoMeasureEnabled方法,LayoutManager及3个实现类都返回true.
      • LayoutManager.onMeasure默认实现就是单纯设置RecyclerView自身尺寸. 且3个实现类未重写.
      • 上述代码中的1,2,3,4(再次1-2-3)暂且命名为'自动测量逻辑'.只有LayoutManager实例的isAutoMeasureEnabled返回true,且RecyclerView的宽高至少1个不是MeasureSpec.EXACTLY,'自动测量逻辑'才被执行.
      • 不要禁用自动测量机制,官方说如果不使用自动测量,需要重写LayoutManage,并在其onMeasure中包含自动测量逻辑.所以自定义LayoutManager不要重写isAutoMeasureEnabled.
      • dispatchLayoutStep2真正执行了子View的布局.
  • 布局
    private void dispatchLayoutStep2() {
      ***
      mState.mInPreLayout = false;
      mLayout.onLayoutChildren(mRecycler, mState);
      ***
    }
    
    androidx/recyclerview/widget/LinearLayoutManager.java
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
      //1:初始化LayoutState实例
      ensureLayoutState();
      mLayoutState.mRecycle = false;
      //2:获取持有焦点的子View
      final View focused = getFocusedChild();
      //3:将界面中已存在的子View从RecyclerView中移除,并保存对应的ViewHolder实例到Recycler中
      //Recycler中 mChangedScrap, mAttachedScrap 中存储的ViewHolder实例就是在这儿村上的
      detachAndScrapAttachedViews(recycler);
      if (mAnchorInfo.mLayoutFromEnd){
        ***
      }else{
        //4:根据锚点位置,分2次更新mLayoutState属性,分别从锚点处向结束方向 及 从锚点处向开始方向 填充子View.
        //根据锚点位置,更新mLayoutState的可用空间等属性,并设置:mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN
        //mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN非常重要,保证了在fill中不执行ViewHolder的回收
        updateLayoutStateToFillEnd(mAnchorInfo);
        ***
        //从锚点位置,向结束方向填充子View
        fill(recycler, mLayoutState, state, false);
        ***
        updateLayoutStateToFillStart(mAnchorInfo);
        ***
        //从锚点位置,向开始方向填充子View
        fill(recycler, mLayoutState, state, false);
        ***
        if (mLayoutState.mAvailable > 0) {
            //如果还有剩余空间,则再次填充子View
            updateLayoutStateToFillEnd(lastElement, endOffset);
            ***
            fill(recycler, mLayoutState, state, false);
            ***
        }
      }
      ***
      //5:设置参数值
      //不是预布局,则记录RecyclerView的宽/高: mLastTotalSpace = getTotalSpace();
      //是预布局,则重置锚点信息
      if (!state.isPreLayout()) {
        mOrientationHelper.onLayoutComplete();
      } else {
        mAnchorInfo.reset();
      }
      ***
    }
    
    之前的疑问点1:
    updateLayoutStateToFillEnd(mAnchorInfo) 及 updateLayoutStateToFillStart(mAnchorInfo) 中设置的
    mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN 决定了 fill 中不会执行ViewHolder的回收, 原因如下:
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
      while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        //这里会对mScrollingOffset进行校验, mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN 才会执行ViewHolder实例的回收
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
          ***
          //这里执行ViewHolder实例的回收
          recycleByLayoutState(recycler, layoutState);
        }
      }
    }
    而之前提到的因为滑动执行的fill,会触发ViewHolder实例的回收,原因如下:
    onTouchEvent -> scrollByInternal -> scrollStep -> scrollVerticallyBy -> scrollBy
    scrollBy:
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        ***
        //这儿会对mLayoutState计算并设置属性,其中 mScrollingOffset是计算得到的值,不是 LayoutState.SCROLLING_OFFSET_NaN
        updateLayoutState(layoutDirection, absDy, true, state);
        //fill方法中,对mScrollingOffset进行校验会通过,触发ViewHolder实例的回收
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        ***
        return scrolled;
    }
    private void updateLayoutState(int layoutDirection, int requiredSpace,
            boolean canUseExistingSpace, RecyclerView.State state) {
      //mScrollingOffset是计算得到的值,不是 LayoutState.SCROLLING_OFFSET_NaN
      mLayoutState.mScrollingOffset = scrollingOffset;
    }
    
    之前的疑问点2:
    Recycler中 ArrayList<ViewHolder> mAttachedScrap 和 ArrayList<ViewHolder> mChangedScrap 是何时存入的.
    源码可见,是scrapView方法存入的:
    void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
        if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
            //非预布局情况,存入mAttachedScrap
            mAttachedScrap.add(holder);
        } else {
            //预布局情况,存入mChangedScrap
            mChangedScrap.add(holder);
        }
    }
    scrapView调用栈:
    1.tryGetViewHolderForPositionByDeadline -> getScrapOrHiddenOrCachedHolderForPosition -> scrapView
      View view = mChildHelper.findHiddenNonRemovedView(position);
      scrapView(view);
    2.detachAndScrapAttachedViews -> scrapOrRecycleView -> scrapView
      recycler.scrapView(view);
    通过调用栈可知:
    - 在通过Recycler获取ViewHolder实例,若是从'已隐藏但未移除的Views'中获取到ViewHolder实例,则将此ViewHolder实例存入.
    - 在执行layout/布局过程中,会将当前页面中的子View对应的ViewHolder实例存入.
    - 处理预布局,都存入mAttachedScrap.
    
    • onLayoutChildren分5步:
      • 创建LayoutState实例
      • 获取焦点子View
      • 将界面中已存在的子View从RecyclerView中移除,并将其对应的ViewHolder实例存入Recycler中
        • 预布局则存入 mChangedScrap , 否则存入 mAttachedScrap
      • 根据锚点位置,分2次更新 LayoutState实例 属性,分别从锚点处向结束方向 及 从锚点处向开始方向 填充子View.
        • 更新 LayoutState实例 属性这一步非常重要,其中 mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN 保证了后面执行fill不会执行ViewHolder实例的回收.
      • 设置参数值
    • 之前疑问点解释
      • ViewHolder实例在滑动触发的fill中会回收,在layout触发的fill中不会回收
      • Recycler中的mChangedScrap和mAttachedScrap:
        • 在通过Recycler获取ViewHolder实例,若是从'已隐藏但未移除的Views'中获取到ViewHolder实例,则将此ViewHolder实例存入.
        • 在执行layout/布局过程中,会将当前页面中的子View对应的ViewHolder实例存入.
        • 处理预布局,都存入mAttachedScrap.
    • onLayoutChildren图示:
  • 自定义LayoutManager没有看懂,先过,收集LayoutManager待看文章
  1. LayoutManager

2. DiffUtil

零散的点

1. java.lang.IndexOutOfBoundsException

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{f82aa71 position=11 id=-1, oldPos=6, pLpos:6 scrap [attachedScrap] tmpDetached not recyclable(1) no parent}
     at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5297)
     at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5479)
     at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5440)
     at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5436)
     at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2224)
     at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1551)
     at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1511)
     at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:595)
     at android.support.v7.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3534)
     at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3310)
  1. 原因
  2. 综合解决方案
    • 重写LinearLayoutManager
      @Override
      public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
          try {
              super.onLayoutChildren(recycler, state);
          } catch (IndexOutOfBoundsException e) {
              e.printStackTrace();
          }
      }
      mRecyclerView.setLayoutManager(new CustomLayoutManager(this, LinearLayoutManager.VERTICAL, false));
      
    • 不要直接设置Adapter实例中的'data list',要使用新的List
      private List<Item> mItems;
      public CustomAdapter(List<Item> items){
          this.mItems = new ArrayList<>();
          try{
              this.mItems.addAll(items);
          }catch(Exception e){
              ***
          }
      }
      
    • 对'data list'进行更新,尽量避免调用notifyDataSetChanged,应该使用notifyItem~进行替代. notifyDataSetChanged没有动画效果,且更新数据的性能低
      /**
       * 'data lsit'
       */
      private List<Item> mItems;
      
      /**
       * refresh 'data lsit'
       *
       * @param items
       */
      public void refreshList(List<Item> items) {
          int preSize = this.mItems.size();
          this.mItems.clear();
          notifyItemRangeRemoved(0, preSize);
          this.mItems.addAll(items);
          notifyItemRangeInserted(0, this.mItems.size());
      }
      
      /**
       * load more
       *
       * @param moreItems
       */
      public void loadMore(List<Item> moreItems) {
          int curIndex = this.mItems.size();
          this.mItems.addAll(moreItems);
          notifyItemRangeInserted(curIndex, this.mItems.size());
      }
      
      /**
       * refresh an Item
       *
       * @param item
       */
      public void refreshItem(Item item) {
          if (this.mItems.contains(item)) {
              int index = this.mItems.indexOf(item);
              notifyItemChanged(index);
          }
      }
      
      /**
       * add an Item
       *
       * @param item
       */
      public void add(Item item) {
          this.mItems.add(item);
          notifyItemInserted(this.mItems.size() - 1);
      }
      
      /**
       * delete an Item
       *
       * @param item
       */
      public void delete(Item item) {
          if (this.mItems.contains(item)) {
              int index = this.mItems.indexOf(item);
              this.mItems.remove(item);
              notifyItemRemoved(index);
          }
      }
      
    • 在RecyclerView滑动的时候不要更新'data-list',或更新'data-list'时候不要滑动,这种方案没有试 2. RecyclerView刷新指定Item中的指定View,真正的局部刷新


源码中指出实现Item局部刷新的方法:

androidx/recyclerview/widget/RecyclerView.java
public abstract static class Adapter<VH extends ViewHolder> {
  /**
   * 1:调用notifyItemChanged时候,传入payload,不为null即可
   * Client can optionally pass a payload for partial change. 
   * 2:最终会调用到我们重写的 Adapter中的 onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads)
   * These payloads will be merged and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
   * item is already represented by a ViewHolder and it will be rebound to the same
   * ViewHolder.
   */
  public final void notifyItemChanged(int position, @Nullable Object payload) {
      mObservable.notifyItemRangeChanged(position, 1, payload);
  }

  /**
   * 局部刷新对比整体刷新:
   * Partial bind vs full bind:
   * <p>
   * payloads是从 notifyItemChanged 或 notifyItemRangeChanged 传入的.
   * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
   * {@link #notifyItemRangeChanged(int, int, Object)}.  
   * payloads不为空,Adapter应该通过解析payloads数据执行局部刷新,如果payloads是空,则执行整体刷新.
   * If the payloads list is not empty,
   * the ViewHolder is currently bound to old data and Adapter may run an efficient partial
   * update using the payload info.  If the payload is empty,  Adapter must run a full bind.
   * Adapter should not assume that the payload passed in notify methods will be received by
   * onBindViewHolder().  For example when the view is not attached to the screen, the
   * payload in notifyItemChange() will be simply dropped.
   *
   * @param payloads A non-null list of merged payloads. Can be empty list if requires full
   *                 update.
   */
  public void onBindViewHolder(@NonNull VH holder, int position,
          @NonNull List<Object> payloads) {
        //源码默认情况下直接执行了onBindViewHolder. 所以我们在自定义Adapter中要重写onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads)
        //根据payloads是否为空,payloads具体数据执行不同的逻辑
      onBindViewHolder(holder, position);
  }
}

实际代码实现Item局部刷新的方法:

1:自定义Adapter重写onBindViewHolder(@NonNull VH holder, int position,@NonNull List<Object> payloads)
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
    //如果payloads是空,则执行Item整体刷新
    if(payloads == null || payloads.isEmpty()){
        //Item整体刷新
        onBindViewHolder(holder,position);
        return;
    }
    //如果payloads非空,则根据payloads具体的值,执行不同的Item局部刷新逻辑
    final int pos = getRealPosition(holder);
    if (viewHolder instanceof **ViewHolder) {
        **ViewHolder holder = (**ViewHolder) holder;
        String refreshType = (String)payloads.get(0);
        switch(refreshType){
        case "refresh1":
          holder.refresh1(pos);
          break;
        case "refresh2":
          holder.refresh2(pos);
          break;
        default:
          holder.refreshX(pos);
          break;
        }
    }
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
    final int pos = getRealPosition(holder);
    if (viewHolder instanceof (**ViewHolder) {
        **ViewHolder holder = (**ViewHolder) holder;
        //Item整体刷新
        holder.setData(pos);
    }
}
public int getRealPosition(RecyclerView.ViewHolder holder) {
    return holder.getLayoutPosition();
}
2:调用 notifyItemChanged 和 notifyItemRangeChanged 时候传入对应参数
rvAdapter.notifyItemChanged(10,"refresh1");
rvAdapter.notifyItemRangeChanged(10,5,"refresh2");

3. 如何为Item项设置点击监听器,不产生N个冗余的OnClickListener实例

4. 类型