阿里巴巴的Vlayout框架原始碼原理詳解(第一篇流程分析)
先看一下阿里對這個框架留下的Demo的效果:
看效果大體的可以猜測這個框架給我們提供了很多佈局規則,據說淘寶首頁就是用這個框架做的。原始碼地址
好接下來我們就沿著這個Demo這條線開始分析實現原理,從而學習人家的架構搭建方式
先看佈局程式碼
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe_container" android:layout_width="match_parent" android:layout_height="match_parent" > <android.support.v7.widget.RecyclerView android:id="@+id/main_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aaaaaa" android:clipToPadding="true" android:paddingLeft="0dp" android:paddingRight="0dp" android:requiresFadingEdge="none" android:scrollbars="vertical" /> </android.support.v4.widget.SwipeRefreshLayout> ..省略不重要程式碼塊 </FrameLayout>
佈局程式碼很簡單就是一個下拉重新整理的控價包裹了RecyclerView,那麼裡面的所有一切佈局規則都是根據RecyclerView的使用規則命名的白,如有對RecyclerView不熟悉的童鞋就看洋神的這篇部落格
RecycleView使用詳解
既然是用RecycleView,那麼看一下RecycleView的Adapter和ViewHoder的實現方式
這裡Vlayout寫了一個DelegateAdapter 類,資料介面卡全是基於此類,那麼看一看此類是怎麼實現的吧final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view); // layoutManager.setRecycleOffset(300); /** * 設定佈局規則 */ recyclerView.setLayoutManager(layoutManager); // layoutManager.setReverseLayout(true); /** * 只設置間距,而不畫分割線 */ RecyclerView.ItemDecoration itemDecoration = new RecyclerView.ItemDecoration() { public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = ((LayoutParams) view.getLayoutParams()) .getViewPosition(); outRect.set(4, 4, 4, 4); } }; /** * 自定義View的快取策略 */ final RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); // recyclerView.addItemDecoration(itemDecoration); viewPool.setMaxRecycledViews(0, 20); /** * 設定資料介面卡 */ final DelegateAdapter delegateAdapter = new DelegateAdapter( layoutManager, true); recyclerView.setAdapter(delegateAdapter);
public abstract class VirtualLayoutAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> { //儲存了一個佈局管理器 @NonNull protected VirtualLayoutManager mLayoutManager; public VirtualLayoutAdapter(@NonNull VirtualLayoutManager layoutManager) { this.mLayoutManager = layoutManager; } public void setLayoutHelpers(List<LayoutHelper> helpers) { this.mLayoutManager.setLayoutHelpers(helpers); } /** * 獲取所有佈局規則類 * @return */ @NonNull public List<LayoutHelper> getLayoutHelpers() { return this.mLayoutManager.getLayoutHelpers(); } }
它的父類持有佈局管理類,以及可以獲取加入的佈局管理規則類集合。那麼接著看它自己怎麼實現的,既然是實現了多種規則按照常規處理方式的話肯定會複寫getItemViewType方法,找一下有沒有這個方法
@Override
public int getItemViewType(int position) {
// 折半演算法查出對應的子Adapter
Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
if (p == null) {
return RecyclerView.INVALID_TYPE;
}
/**
* 通過子Adapter得到Type
*/
int subItemType = p.second.getItemViewType(position
- p.first.mStartPosition);
if (subItemType < 0) {
// negative integer, invalid, just return
return subItemType;
}
// Activity填入的為true所以將type作為key和Adapter作為value存入集合mItemTypeAry
if (mHasConsistItemType) {
mItemTypeAry.put(subItemType, p.second);
return subItemType;
}
// 第幾個子adapter
int index = p.first.mIndex;
// 根據型別和第幾個子adapter重新計算型別,就運算元adapter裡的型別有重複的type也會定義為不同的type
return (int) getCantor(subItemType, index);
}
果然不出所料,確實有這個方法,這個方法的主要意思就是從已經儲存了子Adpter的集合中用折半演算法取出子adapter和它註冊的資料觀察者AdapterDataObserver,然後通過子Adapter的getItemViewType獲得子View的型別(通俗的講就是實現多級不同的列表),如果沒有覆寫此方法的話type預設為-1,此時直接返回-1,那麼RecycleView的快取策略就不會區分快取itemView,這裡的mHasConsistItemType如果不設定的話預設為true,那麼所有的型別必須以子Adpter的type為依據,也就是說,此時如果有兩個子Adpter,那麼如果它們區分了不同的type,但裡面有相同的type值,佈局確不一樣,那麼現在就危險了,因為滑動的時候,快取儲存的資料很可能會覆蓋前面的,也就是說可能會出現itemView和position的錯位問題,這個時候需把mHasConsistItemType改為false,那麼這個時候type會根據第幾個子Adpter的索引和子Adapter的type做運算所得,所以,肯定保證子Adapter中的itemView保證快取獨立不會被覆蓋。
接著看一下它怎麼設定子Adpter的:
/*
* 設定ziView的Adapter
*/
public void setAdapters(@Nullable List<Adapter> adapters) {
clear();
if (adapters == null) {
adapters = Collections.emptyList();
}
List<LayoutHelper> helpers = new LinkedList<>();
boolean hasStableIds = true;
mTotal = 0;
Pair<AdapterDataObserver, Adapter> pair;
for (Adapter adapter : adapters) {
// every adapter has an unique index id
// 建立AdapterDataObserver資料觀察者
// AdapterDataObserver的上一個mTotal末尾為startPosition
// 每次index累加1
AdapterDataObserver observer = new AdapterDataObserver(mTotal,
mIndexGen == null ? mIndex++ : mIndexGen.incrementAndGet());
adapter.registerAdapterDataObserver(observer);
hasStableIds = hasStableIds && adapter.hasStableIds();
LayoutHelper helper = adapter.onCreateLayoutHelper();
helper.setItemCount(adapter.getItemCount());
mTotal += helper.getItemCount();
helpers.add(helper);
pair = Pair.create(observer, adapter);
// mIndex第幾個子Adapter
mIndexAry.put(observer.mIndex, pair);
mAdapters.add(pair);
}
// (!hasObservers()肯定返回false,因為recycleView預設會建立資料觀察者
if (!hasObservers()) {
// 所有子adapter的hasStableIds都為true,才為true
super.setHasStableIds(hasStableIds);
}
super.setLayoutHelpers(helpers);
}
這個方法就是設定子Adpter了,在用這個框架的時候為RecycleView設定為DelegateAdapter ,還要再呼叫這個方法為它設定子Adpter,而子Adpter真正實現了資料轉化為View場景,這裡的mTotal 表示的是資料來源的數量,它等於所有子View的getCount之和,AdapterDataObserver 是為每個子Adpter新增資料觀察者,最後建立子Adpter的佈局管理類,並把它們快取起來,用來二分查詢等。
接下來看一下RecycleView.Adapter最重要的兩個實現方法,資料繫結和和建立ViewHodler
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Pair<AdapterDataObserver, Adapter> pair = findAdapterByPosition(position);
if (pair == null) {
return;
}
pair.second.onBindViewHolder(holder, position
- pair.first.mStartPosition);
pair.second.onBindViewHolderWithOffset(holder, position
- pair.first.mStartPosition, position);
}
這個方法為資料繫結,看的出來最終的繫結資料還是用的子adpter的onBindViewHolder來實現,這裡的子Adpter,框架為它擴充套件了一個onBindViewHolderWithOffset方法,稍後再介紹它的用法。
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
if (mHasConsistItemType) {
//如果子類沒有設定viewtype
Adapter adapter = mItemTypeAry.get(viewType);
if (adapter != null) {
return adapter.onCreateViewHolder(parent, viewType);
}
return null;
}
// reverse Cantor Function
//Math.floor四捨五入,Math.sqrt求平方根
int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
int t = (w * w + w) / 2;
int index = viewType - t;
int subItemType = w - index;
Adapter adapter = findAdapterByIndex(index);
if (adapter == null) {
return null;
}
return adapter.onCreateViewHolder(parent, subItemType);
}
這個方法就是建立ViewHoder了,預設mHasConsistItemType為true時,直接從快取類裡取出子Adpter並呼叫它的子Adpter來建立ViewHodler,如果子Adpter沒有覆寫getViewType方法的話,它將利用反向康托爾演算法計算出近似值取出子Adpter,並進行建立ViewHodler
其實這個演算法就是
private static long getCantor(long k1, long k2) {
return (k1 + k2) * (k1 + k2 + 1) / 2 + k2;
}
此演算法的逆向
好了,看完DelegateAdapter的這幾個重要的方法,我們暫且已經能得出此介面卡並沒有實現真正意義上的獲得子itemView,它只做一箇中轉工作,所有的工作全部交給子Adpter來做,也就是所有工作交給客戶端自己來確定到底使用什麼樣的佈局,當然這也是介面卡模式的好處,也就是說此DelegateAdapter採用了委託者模式,所有的資料繫結,佈局工作它不管,全部委託給子Adpter來處理。其實原理和通用的介面卡原理一樣,介面卡只做管理和公共互動部分,將資料和ui繫結交給被委託者處理,從而實現程式碼分離。
好了既然資料怎麼繫結怎麼分離的我們看完了,那麼接下來就看此框架到底是怎麼排版的,如果你熟練使用RecycleView的話一定知道RecycleView沒有實現怎麼佈局子View而是把工作都交給了LayoutManager的子類來處理,像LinearLayoutManager水平或垂直排版子View等。這裡vlayout框架用了VirtualLayoutManager。好的,那麼就進入看看它到底是幹什麼的
public class VirtualLayoutManager extends ExposeLinearLayoutManagerEx implements
LayoutManagerHelper
它繼承與ExposeLinearLayoutManagerEx ,並實現了LayoutManagerHelper介面,看來它們就是重點了,接著進入ExposeLinearLayoutManagerEx 看一下
class ExposeLinearLayoutManagerEx extends LinearLayoutManager
哈哈,這不是我們所熟悉的LinearLayoutManager 嗎,一般要自定義LayoutMannager的話基本上重寫一些方法就ok
如果要自定義佈局屬性的話重寫它
public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
return new LayoutParams(c, attrs);
}
//預設佈局屬性必須重寫
public abstract LayoutParams generateDefaultLayoutParams();
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
return 0;
}
//垂直滾動時呼叫
public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
return 0;
}
//水平滾動時呼叫
public int scrollHorizontallBy(int dy, Recycler recycler, State state) {
return 0;
}
//只有這個方法返回true的時候才可以呼叫scrollHorizontallBy方法
public boolean canScrollHorizontally() {
return false;
}
//是否可以垂直滾動,只有它返回true時,方法scrollVerticallyBy才會呼叫
public boolean canScrollVertically() {
return false;
}
//滾動到確定的位置
public void scrollToPosition(int position) {
}
}
//平滑滾動到某個位置
public void smoothScrollToPosition(RecyclerView recyclerView,
State state, int position) {
}
好,OnLayoutChild肯定會被實現,因為只是自定義LayoutManager最重要的方法,找一下
public void onLayoutChildren(RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Trace.beginSection(TRACE_LAYOUT);
}
// 不是在滾動的話並且資料集結構改變了
if (mNoScrolling && state.didStructureChange()) {
mSpaceMeasured = false;
mSpaceMeasuring = true;
}
//佈局之前的回撥方法
runPreLayout(recycler, state);
try {
super.onLayoutChildren(recycler, state);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
// MaX_VALUE means invalidate scrolling offset - no scroll
runPostLayout(recycler, state, Integer.MAX_VALUE); // hack to
// indicate its
// an initial
// layout
}
//此處省略若干行
}
不出所料這個方法的確被實現,現在可以大體的看出來,此框架應該是把LayoutManager該乾的佈局那部分給拆出去了,那麼驗證猜測繼續
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
這裡省略若干行,基本上是一些引數判斷和賦值,比如是否資料改變了,是否有滑動偏移值
int startOffset;
int endOffset;
onAnchorReady(state, mAnchorInfo);
//釋放回收adapter
detachAndScrapAttachedViews(recycler);
mLayoutState.mIsPreLayout = state.isPreLayout();
mLayoutState.mOnRefresLayout = true;
//從底部開始佈局
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStartExpose(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEndExpose(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
} else {
// fill towards end
updateLayoutStateToFillEndExpose(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
//開始填充資料
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStartExpose(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
//此處再省略若干行
}
}
假如沒有設定錨點或者沒有設定從底部開始佈局子View或者沒有設定mLayoutFromEnd=true,那麼預設從頂部開始佈局,在開始填充之前先對以前的子View進行全部回收,那麼繼續看fill填充
protected int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutStateExpose(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResultCache.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResultCache);
if (layoutChunkResultCache.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResultCache.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResultCache.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResultCache.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResultCache.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResultCache.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutStateExpose(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResultCache.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrderExpose();
}
return start - layoutState.mAvailable;
}
這個方法的核心思想就是計算RecycleView的可以使用的空間大小,根據這個限制不斷的排版子View與新增子View,直到排到邊界為止,最終通過layoutChunk安排子View的位置
protected void layoutChunk(RecyclerView.Recycler recycler,
RecyclerView.State state, LayoutState layoutState,
com.alibaba.android.vlayout.layout.LayoutChunkResult result) {
final int position = layoutState.mCurrentPosition;
mTempLayoutStateWrapper.mLayoutState = layoutState;
// 得到LayoutHelper進行真正的佈局,找到adapter對應的LayoutHelper交給它進行佈局
LayoutHelper layoutHelper = mHelperFinder == null ? null
: mHelperFinder.getLayoutHelper(position);
if (layoutHelper == null)
layoutHelper = mDefaultLayoutHelper;
layoutHelper.doLayout(recycler, state, mTempLayoutStateWrapper, result,
this);
此處省略若干行…
}
進入這個方法之後發現它沒有實現真正的layout的而是把佈局交給子Adapter繫結的LayoutHelper 來進行佈局,也就是真正的確定子View的位置排列的並不是LayoutManager,而是LayoutHelper 類,再回到例子程式碼
if (FLOAT_LAYOUT) {
FloatLayoutHelper layoutHelper = new FloatLayoutHelper();
layoutHelper.setAlignType(FixLayoutHelper.BOTTOM_RIGHT);
layoutHelper.setDefaultLocation(100, 400);
LayoutParams layoutParams = new LayoutParams(150, 150);
adapters.add(new SubAdapter(this, layoutHelper, 1, layoutParams));
}
每次載入子Adpter的時候都為它繫結一個LayoutHelper,實現真正意義的位置確定,今天就分析到這