RecyclerView 介绍 02 – 重要概念

时间:2021-06-14 16:44:26

几个概念

  1. RecyclerView是一个ViewGroup;
  2. LayoutManager控制RecyclerView的ChildView的布局显示,childview由Recycler提供以及管理;
  3. Recycler具有两级缓存,Scrap和RecycledViewPool,通过Detach以及Remove,对Viewholder进行转移以及状态改变;
  4. RecycledViewPool可以由多个RecyclerView共享;
  5. ViewHolder具有多种状态标记;

关于Recycler

Scrap中的ViewHolder,不用通过Adapter重新处理,只需要attach后回到LayoutManager就可以重用。

RecycledViewPool中的ViewHolder,数据往往是错误的,则需要通过Adapter重新绑定正确的数据后在回到LayoutManager。

当LayoutManager需要一个新的View时,Recycler会行检查scrap中是否有合适的ViewHolder,如果有直接返回给LayoutManager使用;如果没有,就需要从Pool里面寻找,然后右Adapter重新绑定数据后,返回到LayoutManager;如果pool还是没有,就需要由Adapter创建一个新的Viewholder。见如下代码:

  1. View getViewForPosition(int position, boolean dryRun) {
  2.             if (position < 0 || position >= mState.getItemCount()) {
  3.                 throw new IndexOutOfBoundsException("Invalid item position " + position
  4.                         + "(" + position + "). Item count:" + mState.getItemCount());
  5.             }
  6.             boolean fromScrap = false;
  7.             ViewHolder holder = null;
  8.             // 0) If there is a changed scrap, try to find from there
  9.             if (mState.isPreLayout()) {
  10.                 holder = getChangedScrapViewForPosition(position);
  11.                 fromScrap = holder != null;
  12.             }
  13.             // 1) Find from scrap by position
  14.             if (holder == null) {
  15.                 holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
  16.                 if (holder != null) {
  17.                     if (!validateViewHolderForOffsetPosition(holder)) {
  18.                         // recycle this scrap
  19.                         if (!dryRun) {
  20.                             // we would like to recycle this but need to make sure it is not used by
  21.                             // animation logic etc.
  22.                             holder.addFlags(ViewHolder.FLAG_INVALID);
  23.                             if (holder.isScrap()) {
  24.                                 removeDetachedView(holder.itemView, false);
  25.                                 holder.unScrap();
  26.                             } else if (holder.wasReturnedFromScrap()) {
  27.                                 holder.clearReturnedFromScrapFlag();
  28.                             }
  29.                             recycleViewHolderInternal(holder);
  30.                         }
  31.                         holder = null;
  32.                     } else {
  33.                         fromScrap = true;
  34.                     }
  35.                 }
  36.             }
  37.             if (holder == null) {
  38.                 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
  39.                 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
  40.                     throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
  41.                             + "position " + position + "(offset:" + offsetPosition + ")."
  42.                             + "state:" + mState.getItemCount());
  43.                 }
  44.  
  45.                 final int type = mAdapter.getItemViewType(offsetPosition);
  46.                 // 2) Find from scrap via stable ids, if exists
  47.                 if (mAdapter.hasStableIds()) {
  48.                     holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
  49.                     if (holder != null) {
  50.                         // update position
  51.                         holder.mPosition = offsetPosition;
  52.                         fromScrap = true;
  53.                     }
  54.                 }
  55.                 if (holder == null && mViewCacheExtension != null) {
  56.                     // We are NOT sending the offsetPosition because LayoutManager does not
  57.                     // know it.
  58.                     final View view = mViewCacheExtension
  59.                             .getViewForPositionAndType(this, position, type);
  60.                     if (view != null) {
  61.                         holder = getChildViewHolder(view);
  62.                         if (holder == null) {
  63.                             throw new IllegalArgumentException("getViewForPositionAndType returned"
  64.                                     + " a view which does not have a ViewHolder");
  65.                         } else if (holder.shouldIgnore()) {
  66.                             throw new IllegalArgumentException("getViewForPositionAndType returned"
  67.                                     + " a view that is ignored. You must call stopIgnoring before"
  68.                                     + " returning this view.");
  69.                         }
  70.                     }
  71.                 }
  72.                 if (holder == null) { // fallback to recycler
  73.                     // try recycler.
  74.                     // Head to the shared pool.
  75.                     if (DEBUG) {
  76.                         Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
  77.                                 + "pool");
  78.                     }
  79.                     holder = getRecycledViewPool()
  80.                             .getRecycledView(mAdapter.getItemViewType(offsetPosition));
  81.                     if (holder != null) {
  82.                         holder.resetInternal();
  83.                         if (FORCE_INVALIDATE_DISPLAY_LIST) {
  84.                             invalidateDisplayListInt(holder);
  85.                         }
  86.                     }
  87.                 }
  88.                 if (holder == null) {
  89.                     holder = mAdapter.createViewHolder(RecyclerView.this,
  90.                             mAdapter.getItemViewType(offsetPosition));
  91.                     if (DEBUG) {
  92.                         Log.d(TAG, "getViewForPosition created new ViewHolder");
  93.                     }
  94.                 }
  95.             }
  96.             boolean bound = false;
  97.             if (mState.isPreLayout() && holder.isBound()) {
  98.                 // do not update unless we absolutely have to.
  99.                 holder.mPreLayoutPosition = position;
  100.             } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
  101.                 if (DEBUG && holder.isRemoved()) {
  102.                     throw new IllegalStateException("Removed holder should be bound and it should"
  103.                             + " come here only in pre-layout. Holder: " + holder);
  104.                 }
  105.                 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
  106.                 mAdapter.bindViewHolder(holder, offsetPosition);
  107.                 attachAccessibilityDelegate(holder.itemView);
  108.                 bound = true;
  109.                 if (mState.isPreLayout()) {
  110.                     holder.mPreLayoutPosition = position;
  111.                 }
  112.             }
  113.  
  114.             final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
  115.             final LayoutParams rvLayoutParams;
  116.             if (lp == null) {
  117.                 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
  118.                 holder.itemView.setLayoutParams(rvLayoutParams);
  119.             } else if (!checkLayoutParams(lp)) {
  120.                 rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
  121.                 holder.itemView.setLayoutParams(rvLayoutParams);
  122.             } else {
  123.                 rvLayoutParams = (LayoutParams) lp;
  124.             }
  125.             rvLayoutParams.mViewHolder = holder;
  126.             rvLayoutParams.mPendingInvalidate = fromScrap && bound;
  127.             return holder.itemView;
  128.         }

关于ViewHolder

在RecyclerView里面,view是有多重状态的,各种状态在ViewHolder里面定义。看看下面的代码:

  1. public static abstract class ViewHolder {
  2.       public final View itemView;
  3.       int mPosition = NO_POSITION;
  4.       int mOldPosition = NO_POSITION;
  5.       long mItemId = NO_ID;
  6.       int mItemViewType = INVALID_TYPE;
  7.       int mPreLayoutPosition = NO_POSITION;
  8.  
  9.       // The item that this holder is shadowing during an item change event/animation
  10.       ViewHolder mShadowedHolder = null;
  11.       // The item that is shadowing this holder during an item change event/animation
  12.       ViewHolder mShadowingHolder = null;
  13.  
  14.       /**
  15.        * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
  16.        * are all valid.
  17.        */
  18.       static final int FLAG_BOUND = 1 << 0;
  19.  
  20.       /**
  21.        * The data this ViewHolder's view reflects is stale and needs to be rebound
  22.        * by the adapter. mPosition and mItemId are consistent.
  23.        */
  24.       static final int FLAG_UPDATE = 1 << 1;
  25.  
  26.       /**
  27.        * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
  28.        * are not to be trusted and may no longer match the item view type.
  29.        * This ViewHolder must be fully rebound to different data.
  30.        */
  31.       static final int FLAG_INVALID = 1 << 2;
  32.  
  33.       /**
  34.        * This ViewHolder points at data that represents an item previously removed from the
  35.        * data set. Its view may still be used for things like outgoing animations.
  36.        */
  37.       static final int FLAG_REMOVED = 1 << 3;
  38.  
  39.       /**
  40.        * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
  41.        * and is intended to keep views around during animations.
  42.        */
  43.       static final int FLAG_NOT_RECYCLABLE = 1 << 4;
  44.  
  45.       /**
  46.        * This ViewHolder is returned from scrap which means we are expecting an addView call
  47.        * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until
  48.        * the end of the layout pass and then recycled by RecyclerView if it is not added back to
  49.        * the RecyclerView.
  50.        */
  51.       static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
  52.  
  53.       /**
  54.        * This ViewHolder's contents have changed. This flag is used as an indication that
  55.        * change animations may be used, if supported by the ItemAnimator.
  56.        */
  57.       static final int FLAG_CHANGED = 1 << 6;
  58.  
  59.       /**
  60.        * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
  61.        * it unless LayoutManager is replaced.
  62.        * It is still fully visible to the LayoutManager.
  63.        */
  64.       static final int FLAG_IGNORE = 1 << 7;

------EOF----------