摘要:其實通過父類的這個方法之后會調用它的方法,這個名字熟悉自定義的童鞋都知道了。
我一直在說RecyclerView是一個值得深入學習,甚至可以說是一門具有藝術性的控件。那到底哪里值得我們花時間去深入學習呢。沒錯了,就是源碼的設計。但是看源碼其實是一件不簡單的事情,就拿RecyclerView的源碼來說,打開源碼一看,往下拉啊拉啊,我擦,怎么還沒到頭,汗....居然有12k+行。看到這里恐怕會嚇一跳,就這么一個看似簡單的控件就這么多行源碼,這讓我從何看起,一股畏懼感油然而生。
其實不需要害怕,我們不需要一開始就想完全弄懂它每一步怎么實現的,這樣反而會造成只見森林不見樹木的感覺。我們就把源碼就當成一片森林來說吧。首先我們只需要先抓住一條路徑去看,也就是帶著一個問題去看,這樣就能夠把這條路徑上的樹都看明白了。就不會有只見森林不見樹,一臉茫然了。當然我們大多數情況肯定是不滿足于此一條路徑,想完全看明白它是怎么實現的,那就繼續另開路徑(再帶著另外一個問題),繼續看這條路上的樹。當你把每條路都走差不多了,再回頭來看,就會發現你既見到了森林又見到了一顆顆清晰樹木,猶如醍醐灌頂、豁然開朗。
說著很簡單,但是不得不說看源碼的過程還是有點小痛苦的。不過,不用慌,看完之后你所獲得那種充實感和滿足感會遠遠大于過程中的痛苦感。畢竟這是一個充滿藝術感的控件嘛,值得我們去欣賞和學習。
從使用RecyclerView的時候,它的一個功能就讓我感覺很這個控件不簡單,不知道你和我想的是不是一樣。那是什么功能呢?我們只需改變一行代碼就可以直接設置它的ItemView為水平布局、垂直布局、表格布局以及瀑布流布局。這是ListView所不能做到的。用起來簡單,其背后肯定有故事啊。那我們就以這條路為核心來看這片森林了。
二、開始尋路從哪里開始看呢?
1.我們先從setAdapter()看起,這個方法我們比較熟悉,在Activity中這是我們直接接觸的方法。
/** *Replaces the current adapter with the new one and triggers listeners. */ public void setAdapter(Adapter adapter){ ..... //用一個新的設配器和觸發器來替代目前正在使的 setAdapterInternal(adapter,false,true); //請求布局,直接調用View類的請求布局方法 requestLayout(); }
setAdapter里面主要做了兩件事:
首先調用setAdapterInternal方法,目的是用一個新的設配器和觸發器來替代目前正在使用的。
我們深入進去看看它做了什么?
對于熟悉了觀察者設計模式的,可以從下面的代碼看出來,其實里面有個操作是:
注銷觀察者(之前的設配器)和注冊觀察者(新的設配器)操作。簡單的理解一下就是設配器觀察者會監測一些對象的狀態,當這些對象狀態改變,它可以通過這種設計模式低耦合的做出相應的改變。最后調用markKnownViewsInvalid方法刷新一下視圖。
如果你想深入了解觀察者設計模式的可以看一下這篇文章
傳送門:觀察者設計模式
{ Adapter mAdapter; ...... private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); //注銷觀察者 mAdapter.onDetachedFromRecyclerView(this); //Called by RecyclerView when it stops observing this Adapter. } ...... mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null) { adapter.registerAdapterDataObserver(mObserver); //注冊觀察者 adapter.onAttachedToRecyclerView(this); } ...... //刷新視圖 markKnownViewsInvalid(); }
之后調用了 requestLayout方法請求重新布局。這個方法很關鍵,和我們的這次選的路是相通的。
@Override public void requestLayout() { if (mEatRequestLayout == 0 && !mLayoutFrozen) { super.requestLayout(); } else { mLayoutRequestEaten = true; } }
這么關鍵的方法代碼卻這么少?而且好像只做了一個操作?沒錯,表面上只調用了父類View的requestLayout方法。其實通過父類的這個方法之后會調用它的onLayout方法,這個名字熟悉自定義View的童鞋都知道了。但我們看父類View的onLayout方法其實是個空方法。也就是說最終需要由它的子類來重寫,也即RecyclerVie調用自身的onLayout方法。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; }
onLayout又調用了dispatchLayout方法,來分發layout
void dispatchLayout() { ...... if (mState.mLayoutStep == State.STEP_START) { //分發第一步 dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); //分發第二步 dispatchLayoutStep2(); } ...... //分發第三步 dispatchLayoutStep3(); ...... }
它把這個分發的過程分為了三步走
step1:做一下準備工作:決定哪一個動畫被執行,保存一些目前view的相關信息
private void dispatchLayoutStep1() { ...... 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)); final ItemHolderInfo animationInfo = mItemAnimator .recordPreLayoutInformation(mState, holder, ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), holder.getUnmodifiedPayloads()); mViewInfoStore.addToPreLayout(holder, animationInfo); } ...... }
step2:找到實際的view和最終的狀態后運行layout。
private void dispatchLayoutStep2() { eatRequestLayout(); onEnterLayoutOrScroll(); ...... mState.mInPreLayout = false; // Step 2: 運行layout mLayout.onLayoutChildren(mRecycler, mState); mState.mStructureChanged = false; .... resumeRequestLayout(false); }
這里面有個方法很關鍵了,就是下面這個onLayoutChildren,這個為什么關鍵呢,先提一下這個,待會要詳細說的。
mLayout.onLayoutChildren(mRecycler, mState);
step3:做一些分發的收尾工作了,保存動畫和一些其他的信息。和我們不同路,就不看它了。
看了這么多先喝一杯92年的肥宅快樂水壓壓驚吧~~,順便看張圖小結一下上面的過程
三、尋得果樹之前說過RecyclerView和ListView最大的不同就是在它們的布局實現上。在ListView中布局是通過自身的layoutChildren方法實現的,但對于RecyclerView來說就不是了,那是誰來實現了呢?
這就要從剛才結束的onLayoutChildren方法說起了,它不是RecyclerView的類直接方法,它是RecyclerView的內部類LayoutManager的方法,顧名思義,就是布局管理者了。我們的RecyclerView布局就通過這個布局管理者來做了,把這樣一個很重要的職責就交給它了。從而實現某種程度上的低耦合。
那我們繼續走,它是怎么執行這一職責的。
但是點進去看onLayoutChildren方法,發現只有一行代碼,而且還是打印的日志:必須重寫這個方法。
public void onLayoutChildren(Recycler recycler, State state) { Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); }
那么既然要重寫必須要尋找一個子類,所以這里我就找了一個子類LinearLayoutManager類,也是我們最常用的一種線性布局來看。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ...... int startOffset; int endOffset; final int firstLayoutDirection; onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); ...... if (mAnchorInfo.mLayoutFromEnd) { // 底部向頂部的填充 updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; //填充 fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; final int firstElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForEnd += mLayoutState.mAvailable; } // 頂部向底部的填充 updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; //填充 fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; ...... } } else { ...... } ...... }
這個方法主要就是通過一個布局算法,實現itemView從頂部到底部或者底部到頂部的填充,并創建一個布局的狀態。接下來看一下fill方法是怎么進行填充的。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { ...... //1.計算RecyclerView可用的布局寬或高 int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; //2.迭代布局item View while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); //3.布局item view layoutChunk(recycler, state, layoutState, layoutChunkResult); //4.計算布局偏移量 layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // we keep a separate remaining space because mAvailable is important for recycling //5.計算剩余的可用空間 remainingSpace -= layoutChunkResult.mConsumed; } ...... } return start - layoutState.mAvailable; }
fill方法總的來說用了5步實現了itemVIew的填充:
(1)計算RecyclerView可用的布局寬或高
(2)迭代布局item View
(3)布局itemview
(4)計算布局偏移量
(5)計算剩余的可用空間
fill方法又會循環的調用layoutChunk來進行itemView的布局,下面先看看layoutChunk的實現
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { //1.獲取itemview View view = layoutState.next(recycler); ...... //2.獲取itemview的布局參數 LayoutParams params = (LayoutParams) view.getLayoutParams(); //3.測量Item View measureChildWithMargins(view, 0, 0); //4.計算該itemview消耗的寬和高 result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); int left, top, right, bottom; //5.按照水平或豎直方向布局來計算itemview的上下左右坐標 if (mOrientation == 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 { ...... } 6.計算itemview的邊界比如下劃線和margin,從而確定itemview準確的位實現最終的布局 layoutDecoratedWithMargins(view, left, top, right, bottom); } result.mFocusable = view.hasFocusable(); }
在layoutChunk中首先從layoutState獲取此時的itemview,然后根據獲得的這個itemview獲取它的布局參數和尺寸信息,并且判斷布局方式(橫向或者縱向),以此計算出itemview的上下左右坐標。最后調用layoutDecoratedWithMargins方法完成布局。
這樣一看就對整個過程有了個清晰的認識了吧,有沒有感覺設計的很優雅。
四、貫穿布局的一條線到這里已經算走完我們之前準備走的一條路了。但從開始到這里始終忽略了一個東西沒有說,那就在布局過程的大多方法中的參數都有一個Recycler對象。這個Recycler是什么呢?
在使用RecyclerView的過程中,我們都知道Adapter被緩存的單位不再是普通的itemview了,而是一個ViewHolder。這是和listview的一個很大的不同。
public final class Recycler { final ArrayListmAttachedScrap = new ArrayList<>(); ArrayList mChangedScrap = null; final ArrayList mCachedViews = new ArrayList (); private final List mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); 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) { ... } ......
在Recycler類的開始就看到mAttachedScrap、mChangedScrap、mCachedViews、 mUnmodifiableAttachedScrap這幾個ViewHolder的列表對象,它們就是用來緩存ViewHolder的。
具體是怎么實現的這里就不做詳細的解釋了。因為這里一說又會牽涉到其他的點,子子孫孫無窮盡也,畢竟這是一個有藝術感的控件,不能指望一篇文章把它說透哈。
到這里我們就結束了我們對RecyclerView的的源碼分析了。相信你看完會有所收獲。
作者:錦小白
https://www.jianshu.com/p/102...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76915.html
閱讀 3847·2021-09-06 15:00
閱讀 2180·2019-08-30 15:53
閱讀 3288·2019-08-23 16:44
閱讀 951·2019-08-23 15:19
閱讀 1399·2019-08-23 12:27
閱讀 4198·2019-08-23 11:30
閱讀 591·2019-08-23 10:33
閱讀 376·2019-08-22 16:05