RecyclerView
根據平常對recyclerview的調用過程進行代碼跟蹤,以此來了解RecyclerView的原理
RecyclerView recycler = findViewById(R.id.xxx);
recycler.setLayoutManager(new LinearLayoutManager(this,Orientation,false));
//recyclerView.setItemAnimator(new DefaultItemAnimator()); //item的動畫,這裏不涉及,不設置也是這個默認動畫
//recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL)); //item之間的分割線,這裏不涉及
recycler.setAdapter(new RecyclerView.Adapyer<VH extends ViewHolder>);
recycler.set
瞭解調用過程後,看看各個方法的具體實現。
public void setLayoutManager(@Nullable LayoutManager layout) {
if (layout == mLayout) {//如果本身持有一個layoutManager比對是否一致
return;
}
stopScroll();//停止滑動
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {//一開始就有一個layout,通常切換視圖佈局的時候不爲null,Grid變爲Linear或者瀑布流
// end all running animations,停止所有正在運行的動畫
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
//移除所有的視圖
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();//清除複用數據
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();//清除複用數據
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout
+ " is already attached to a RecyclerView:"
+ layout.mRecyclerView.exceptionLabel());
}
mLayout.setRecyclerView(this);//將RecyclerView綁定至LayoutManager
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();//更新緩存View
requestLayout();//重繪
}
LayoutManager綁定了RecyclerView後:
void setRecyclerView(RecyclerView recyclerView) {
if (recyclerView == null) {
mRecyclerView = null;
mChildHelper = null;
mWidth = 0;
mHeight = 0;
} else {
//更新數據
mRecyclerView = recyclerView;
mChildHelper = recyclerView.mChildHelper;
mWidth = recyclerView.getWidth();
mHeight = recyclerView.getHeight();
}
mWidthMode = MeasureSpec.EXACTLY;//給定了高寬
mHeightMode = MeasureSpec.EXACTLY;
}
回收,這個方法真的隨時都在調用,從而使得將緩存裏的視圖的給remove掉,放進pool裏
void updateViewCacheSize() {
//layoutManager爲空否?展示在屏幕上的View個數
int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
mViewCacheMax = mRequestedCacheMax + extraCache;//緩存視圖,mRequestedCacheMax 默認爲2
// first, try the views that can be recycled 試圖回收可回收視圖
for (int i = mCachedViews.size() - 1;//倒序查詢
i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
recycleCachedViewAt(i);
}
}
回收緩存視圖
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
//RecyclerView裏緩存的實際對象就是ViewHolder,mCachedViews實際是一個ArrayList
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
//將ViewHolder添加進RecyclerViewPool,然後進行remove
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);//清除嵌套,
View itemView = holder.itemView;
//androidx 源碼
if (mAccessibilityDelegate != null) {//對ViewHolder添加標誌位
AccessibilityDelegateCompat originalDelegate = mAccessibilityDelegate
.mItemDelegate.getAndRemoveOriginalDelegateForItem(itemView);
// Set the a11y delegate back to whatever the original delegate was.
ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
}
//API28源代碼,添加flag
//if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
// holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
// ViewCompat.setAccessibilityDelegate(holder.itemView, null);
//}
if (dispatchRecycled) {
dispatchViewRecycled(holder);//被回收了,進行回調通知,有listener、adapter...
}
holder.mOwnerRecyclerView = null;//重置所屬RecyclerView
getRecycledViewPool().putRecycledView(holder);//放進RecyclerViewPool維護的數據緩存裏
}
至此setLayoutManager()
方法到這裏結束,接着是setAdapter()
方法:
//設置適配器以提供相應的視圖
//回收所有的視圖到pool中,如果pool只有一個適配器,那將被清除...
public void setAdapter(@Nullable Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);//追蹤下去,配置修改,並調用requestLayout().......
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);//將當前的視圖和緩存的視圖進行標記位重置,看下面的mark...方法
requestLayout();//重繪
}
/**
* Removes and recycles all views - both those currently attached, and those in the Recycler.
* 移除並回收所有的View,包括正在使用和在Recycler裏緩存好的
*/
void removeAndRecycleViews() {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
// Since animations are ended, mLayout.children should be equal to
// recyclerView.children. This may not be true if item animator's end does not work as
// expected. (e.g. not release children instantly). It is safer to use mLayout's child
// count.
if (mLayout != null) {
//layout清空數據
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
}
// we should clear it here before adapters are swapped to ensure correct callbacks.
mRecycler.clear();//清空數據
}
/**
* Replaces the current adapter with the new one and triggers listeners.
* @param adapter The new adapter
* @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
* item types with the current adapter (helps us avoid cache
* invalidation).爲true就使用一樣的holder和type,就避免了緩存失效
* @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
* compatibleWithPrevious is false, this parameter is ignored.
*/
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);//註銷觀察者
mAdapter.onDetachedFromRecyclerView(this);//解綁RecyclerView
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();//清除所有的視圖
}
mAdapterHelper.reset();//重置
final Adapter oldAdapter = mAdapter;//將當前的adapter留一下
mAdapter = adapter;//設置新的
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);//註冊
adapter.onAttachedToRecyclerView(this);//綁定RecyclervIew
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);//回調adapter改變了
}
//回調adapter改變了,將當前的Pool的數據clear掉。進而重新設置。
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;//結構已經改變
}
對視圖進行標記位重置:
void markKnownViewsInvalid() {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {//不爲空以及不應該被忽略
holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);//將標記變成更新|無效
}
}
markItemDecorInsetsDirty();//對分割線的重新設置
mRecycler.markKnownViewsInvalid();//對緩存的視圖也進行標記位修改
}
至此,瞭解了LayoutManager和Adapter的相關邏輯。
那麼重要的是,RecyclerView是如何複用的呢?上面的代碼看的到mRecycler
的出現,名字上就知道是回收的意思。那麼,是這個類對RecyclerView的數據進行回收複用麼?Look ↓↓↓↓↓↓↓
源碼
父類ViewGroup,實現了ScrollView、NestedScrollingChild2方法,AndroidX還實現了NestedScrollingChild3。
int computeHorizontalScrollRange();//水平滾動範圍
int computeHorizontalScrollOffset();//水平滾動偏移量
int computeHorizontalScrollExtent();//水平滾動thmub的寬度
int computeVerticalScrollRange();//同理
int computeVerticalScrollOffset();//同理
int computeVerticalScrollExtent();//同理
//另外的不貼了,子子孫孫無窮盡也。。
//各種初始化
...
//列一些我覺得主要的東西,但下文不一定會涉及
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();//觀察者,adapter更新數據就能刷新視圖
final Recycler mRecycler = new Recycler();//複用
AdapterHelper mAdapterHelper;//處理adapter的更新
ChildHelper mChildHelper;//處理RecyclerView和LayoutManager的子View的關係
final ViewInfoStore mViewInfoStore = new ViewInfoStore();//動畫時候的信息store
final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();//下劃線數組集合
private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = new ArrayList<>();//touchListener集合
瞭解上面內容,對RecyclerView持有的各個對象基本瞭解後,看看重要的回收與複用。
回收與複用
RecyclerView的內部類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;
static final int DEFAULT_CACHE_SIZE = 2;
上面的一堆集合就是對ViewHolder進行緩存的地方,那麼,如何對ViewHolder進行管理的呢??
在Adapter裏,需要重寫onCreateViewHolder()
方法,那麼這個方法在哪裏調用??
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
if (holder.itemView.getParent() != null) {
throw new IllegalStateException("ViewHolder views must not be attached when"
+ " created. Ensure that you are not passing 'true' to the attachToRoot"
+ " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
}
holder.mItemViewType = viewType;
return holder;
} finally {
TraceCompat.endSection();
}
}
是傳遞的數據進來的,哪裏調用的?再找找。
tryGetViewHolderForPositionByDeadline
這個方法調用了創建視圖。
//在這一部分裏創建ViewHolder
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);//找到嵌套View
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);//弱引用,回收
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
這是創建,對於複用呢?在LayoutManager中對調用Recycler的各個方法,在LayoutManager繪製視圖的時候,通過position得到對應的ViewHolder
onLayoutChildren
->fill
->layoutChunk
->next
->recycler.getViewForPosition(mCurrentPosition)
->tryGetViewHolderForPositionByDeadline
到了最後的方法就是獲取各個position上的ViewHolder
作爲緩存,那麼,通過Recycler持有的各個數據集,從而循環查詢判斷得到對應的ViewHolder。當數據集中不能拿到對應的ViewHolder的時候,纔會用上面的方法在Adapter中創建新的視圖。
第一步:
// 0) If there is a changed scrap, try to find from there
//通過mChangeScrap集合查找
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
//通過方法查找從mAttachScrap、mHiddenView、mCacheView得到對應的ViewHolder
//mHiddenView是mChildHelper進行管理的,也通過這個查找。。
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();//不捨棄
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);//預備回收
}
holder = null;//回收置空
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
/*******************在這裏,上面的驗證回收置空,下面id查找***********************/
if (holder == null) {
//爲空從上述數據集裏通過id查找
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists id查詢
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position,不爲空更新位置
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//如果id沒有找到,從ViewCacheExtension中查詢,RecyclerView本身不會實現這個實例,需要自己通過
//setViewCacheExtension()方法傳入實例
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) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
//依然爲空,從RecyclerViewPool中拿到ViewHolder
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
//RecyclerViewPool內部維護了一個數據集,通過type對ViewHolder進行緩存,默認大小爲5
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);//強制刷新一遍visible
}
}
}
//以上緩存都沒有,只有新建了,以下代碼省略,在上面↑
.........
至此,對ViewHolder的複用瞭解清楚了,那麼如何緩存的呢?就是LayoutManager創建的時候回調用recyclerView()
方法,這時候就會對所擁有的ViewHolder進行cache緩存,當cache清理的時候,就會加入RecyclerViewPool中進行緩存。
OK,it’s over!