android流程化步骤样式,Android RecyclerView 解析之绘制流程篇
前言: 當(dāng)前市場上有很多成熟的RecyclerView分析文章,但那始終是其他人總結(jié)出來的,還得自己動手分析,才知道自己理解了有多少,當(dāng)然這個也算是加深對RecyclerView對理解吧;
官方簡介:A flexible view for providing a limited window into a large data set.
一種靈活的視圖,在有限的窗口,展示大量的數(shù)據(jù)集;
在開始之前,為了加深理解,我們需要帶著疑問進(jìn)行閱讀;
(1),RecyclerView是怎么加載數(shù)據(jù)的?
(2),RecyclerView是怎么將View繪制到頁面上的?
(3),RecyclerView是怎么復(fù)用item的?
1.1 總體結(jié)構(gòu)
RecyclerView主體架構(gòu).png
由上圖可知,RecyclerView主要由這幾部分組成;那他們的關(guān)系是啥呢? 具體是如何關(guān)聯(lián)的呢?且聽完細(xì)細(xì)道來!
數(shù)據(jù)層面:首頁RecyclerView需要將數(shù)據(jù)和view綁定起來,是通過Adapter加載ViewHolder來實(shí)現(xiàn)綁定數(shù)據(jù)的;
布局層面:RecyclerView的Item的布局是通過LayoutManager來進(jìn)行布局的;
復(fù)用層面:LayoutManger從Recycler獲取item來進(jìn)行復(fù)用;
總結(jié):
1,Adapter:將數(shù)據(jù)轉(zhuǎn)化為RecyclerView可以識別的數(shù)據(jù);
2,ViewHolder:將數(shù)據(jù)和item綁定起來;
3,LayoutManager:通過計算將Item布局到頁面中;
4,Recycler:復(fù)用機(jī)制,統(tǒng)一管理Item,用于復(fù)用;
5,ItemDecoration:繪制item的樣式;
1.2 具體流程:
1.2.1 RecyclerView 初始化流程
首先,先來看看RecyclerView 的初始化流程,先舉個簡單的例子;
//獲取RecyclerView 控件
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
//創(chuàng)建adapter
MyAdapter adapter = new MyAdapter(list);
//創(chuàng)建LayoutManager
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
//設(shè)置LayoutManager
recyclerView.setLayoutManager(linearLayoutManager);
//設(shè)置Adapter
recyclerView.setAdapter(adapter);
1,我們先來看看RecyclerView 的構(gòu)造方法做了啥?
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//創(chuàng)建觀察者
this.mObserver = new RecyclerView.RecyclerViewDataObserver();
//創(chuàng)建回收器
this.mRecycler = new RecyclerView.Recycler();
//創(chuàng)建布局信息保存類
this.mViewInfoStore = new ViewInfoStore();
this.mUpdateChildViewsRunnable = new Runnable() {
public void run() {
if (RecyclerView.this.mFirstLayoutComplete && !RecyclerView.this.isLayoutRequested()) {
if (!RecyclerView.this.mIsAttached) {
RecyclerView.this.requestLayout();
} else if (RecyclerView.this.mLayoutFrozen) {
RecyclerView.this.mLayoutWasDefered = true;
} else {
RecyclerView.this.consumePendingUpdateOperations();
}
}
}
};
...
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
this.mClipToPadding = a.getBoolean(0, true);
a.recycle();
} else {
this.mClipToPadding = true;
}
...
this.mAccessibilityManager = (AccessibilityManager)this.getContext().getSystemService("accessibility");
this.setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
boolean nestedScrollingEnabled = true;
if (attrs != null) {
int defStyleRes = 0;
TypedArray a = context.obtainStyledAttributes(attrs, styleable.RecyclerView, defStyle, defStyleRes);
//從布局文件獲取Layoutmanger的名稱
String layoutManagerName = a.getString(styleable.RecyclerView_layoutManager);
int descendantFocusability = a.getInt(styleable.RecyclerView_android_descendantFocusability, -1);
if (descendantFocusability == -1) {
this.setDescendantFocusability(262144);
}
this.mEnableFastScroller = a.getBoolean(styleable.RecyclerView_fastScrollEnabled, false);
//通過layoutManger的名稱進(jìn)行反射創(chuàng)建layoutManager,并設(shè)置給RecycleView
this.createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
...
} else {
this.setDescendantFocusability(262144);
}
//設(shè)置是否支持嵌套滾動,默認(rèn)為true
this.setNestedScrollingEnabled(nestedScrollingEnabled);
}
從構(gòu)造方法可以看出,里面做了一大堆初始化的操作,最主要看一下這個創(chuàng)建layoutManager的方法createLayoutManager();
根據(jù)布局屬性進(jìn)行反射來創(chuàng)建layoutManager;
private void createLayoutManager(Context context, String className, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
if (className != null) {
className = className.trim();
if (!className.isEmpty()) {
className = this.getFullClassName(context, className);
try {
ClassLoader classLoader;
if (this.isInEditMode()) {
classLoader = this.getClass().getClassLoader();
} else {
classLoader = context.getClassLoader();
}
Class extends RecyclerView.LayoutManager> layoutManagerClass = classLoader.loadClass(className).asSubclass(RecyclerView.LayoutManager.class);
Object[] constructorArgs = null;
Constructor constructor;
try {
//通過反射創(chuàng)建布局構(gòu)造器
constructor = layoutManagerClass.getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
} catch (NoSuchMethodException var13) {
try {
constructor = layoutManagerClass.getConstructor();
} catch (NoSuchMethodException var12) {
var12.initCause(var13);
throw new IllegalStateException(attrs.getPositionDescription() + ": Error creating LayoutManager " + className, var12);
}
}
constructor.setAccessible(true);
//將創(chuàng)建出來的LayoutManger設(shè)置給RecycleView
this.setLayoutManager((RecyclerView.LayoutManager)constructor.newInstance(constructorArgs));
} catch (ClassNotFoundException var14) {
throw new IllegalStateException(attrs.getPositionDescription() + ": Unable to find LayoutManager " + className, var14);
} catch (InvocationTargetException var15) {
throw new IllegalStateException(attrs.getPositionDescription() + ": Could not instantiate the LayoutManager: " + className, var15);
} catch (InstantiationException var16) {
throw new IllegalStateException(attrs.getPositionDescription() + ": Could not instantiate the LayoutManager: " + className, var16);
} catch (IllegalAccessException var17) {
throw new IllegalStateException(attrs.getPositionDescription() + ": Cannot access non-public constructor " + className, var17);
} catch (ClassCastException var18) {
throw new IllegalStateException(attrs.getPositionDescription() + ": Class is not a LayoutManager " + className, var18);
}
}
}
}
再看一下setLayoutManager()這個方法里面做了啥操作?
public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) {
if (layout != this.mLayout) {
//停止當(dāng)前的滾動操作
this.stopScroll();
if (this.mLayout != null) {
//判斷當(dāng)前的layoutManager如果為空,則將該layoutManager的狀態(tài)進(jìn)行初始化;
if (this.mItemAnimator != null) {
this.mItemAnimator.endAnimations();
}
this.mLayout.removeAndRecycleAllViews(this.mRecycler);
this.mLayout.removeAndRecycleScrapInt(this.mRecycler);
this.mRecycler.clear();
if (this.mIsAttached) {
this.mLayout.dispatchDetachedFromWindow(this, this.mRecycler);
}
this.mLayout.setRecyclerView((RecyclerView)null);
this.mLayout = null;
} else {
this.mRecycler.clear();
}
this.mChildHelper.removeAllViewsUnfiltered();
//將當(dāng)前的layoutManager賦值給成員變量
this.mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView:" + layout.mRecyclerView.exceptionLabel());
}
//將當(dāng)前的RecyclerView賦值給layoutManager
this.mLayout.setRecyclerView(this);
if (this.mIsAttached) {
this.mLayout.dispatchAttachedToWindow(this);
}
}
//更新一下RecyclerView的緩存
this.mRecycler.updateViewCacheSize();
//觸發(fā)重新布局
this.requestLayout();
}
}
總結(jié):看完RecyclerView的構(gòu)造方法,里面主要是做了一些初始化的操作,并創(chuàng)建了layoutManager設(shè)置給RecyclerView(如果布局屬性有設(shè)置的話);
2,看完了RecyclerView的setLayoutManager()的流程,我們繼續(xù)接著分析,看一下setAdapter()具體做了啥?
public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
this.setLayoutFrozen(false);
//主要模塊
this.setAdapterInternal(adapter, false, true);
this.processDataSetCompletelyChanged(false);
this.requestLayout();
}
跟進(jìn)源碼,我們主要分析setAdapterInternal()這個方法,讓我們看看這個源碼里面做了什么操作;
private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
if (this.mAdapter != null) {
//解注冊之前的數(shù)據(jù)觀察者
this.mAdapter.unregisterAdapterDataObserver(this.mObserver);
this.mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
//進(jìn)行初始化操作,初始化layoutManger,初始化mRecycler
this.removeAndRecycleViews();
}
this.mAdapterHelper.reset();
RecyclerView.Adapter oldAdapter = this.mAdapter;
//將adapter賦值給當(dāng)前成員變量
this.mAdapter = adapter;
if (adapter != null) {
//adapter注冊數(shù)據(jù)觀察者,用于監(jiān)聽數(shù)據(jù)的增刪改查
adapter.registerAdapterDataObserver(this.mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (this.mLayout != null) {
this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter);
}
this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious);
this.mState.mStructureChanged = true;
}
這個方法里面主要是給adapter注冊數(shù)據(jù)監(jiān)聽,用于數(shù)據(jù)的增刪改查的刷新,并做一些初始化的操作;
我們再看一下這個觀察者里面主要做了什么操作,具體的實(shí)現(xiàn)是在RecyclerViewDataObserver 這個類里面;
private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
RecyclerViewDataObserver() {
}
public void onChanged() {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
RecyclerView.this.mState.mStructureChanged = true;
RecyclerView.this.processDataSetCompletelyChanged(true);
if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
RecyclerView.this.requestLayout();
}
}
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
this.triggerUpdateProcessor();
}
}
public void onItemRangeInserted(int positionStart, int itemCount) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
this.triggerUpdateProcessor();
}
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
this.triggerUpdateProcessor();
}
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
this.triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
} else {
RecyclerView.this.mAdapterUpdateDuringMeasure = true;
RecyclerView.this.requestLayout();
}
}
}
看到了我們很熟悉的方法,即adapter刷新數(shù)據(jù)所調(diào)用的方法;我們主要分析其中一個方法即可,讓我們來看一下onItemRangeChanged()這個方法;
這里面主要分為兩步:
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
//這里通過AdapterHelper將傳進(jìn)來的信息保存起來
if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
//重新布局
this.triggerUpdateProcessor();
}
}
(1)通過AdapterHelper將傳進(jìn)來的信息保存起來;
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (itemCount < 1) {
return false;
} else {
this.mPendingUpdates.add(this.obtainUpdateOp(4, positionStart, itemCount, payload));
this.mExistingUpdateTypes |= 4;
return this.mPendingUpdates.size() == 1;
}
}
(2)通過triggerUpdateProcessor()方法觸發(fā)RecyclerView重新布局;
void triggerUpdateProcessor() {
if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
//當(dāng)前有動畫正在執(zhí)行的時候會走這里
ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
} else {
//觸發(fā)重新布局
RecyclerView.this.mAdapterUpdateDuringMeasure = true;
RecyclerView.this.requestLayout();
}
}
1,RecyclerView的主要繪制流程;
2,復(fù)用機(jī)制;
2. 工作流程
2.1 主體關(guān)系
首先我們來看一下各個模塊的關(guān)系;
關(guān)系圖.png
通過上圖大體可以看出這幾個模塊的關(guān)系:
(1)RecyclerView通過LayoutManager來進(jìn)行布局操作;
(2)LayoutManager從Recycler里面獲取復(fù)用的item來進(jìn)行布局;
(3)Recycler管理著ViewHolder的創(chuàng)建與復(fù)用;
(4)Adapter將數(shù)據(jù)和ViewHolder綁定起來,并和RecyclerView注冊觀察者;
(5)RecyclerView通過ItemDecoration進(jìn)行item樣式的繪制;
接下來通過源碼來細(xì)細(xì)剖析,看看具體是怎么實(shí)現(xiàn)的;
那么我們接著上面分析的setAdapter()方法繼續(xù)分析,在setAdapter()方法里,最后調(diào)用來requestLayout(),來觸發(fā)RecyclerView 的繪制流程;
這個requestLayout()這個方法最終會調(diào)用到ViewRootImp里面的requestLayout()方法;
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//觸發(fā)繪制流程
scheduleTraversals();
}
}
在ViewRootImp里調(diào)用requestLayout()方法進(jìn)行繪制,我們主要看scheduleTraversals()方法,里面最終會調(diào)用到performTraversals()方法,源碼如下;
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
...
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
...
performTraversals()這個方法里面執(zhí)行了三大步驟,測量(measure),布局(layout),繪制(draw),完成的view的工作流程,將頁面繪制出來;
{
// cache mView since it is used so much below...
final View host = mView;
...
if (!mStopped || mReportNextDraw) {
//執(zhí)行view的測量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
} else {
...
}
...
if (didLayout) {
//執(zhí)行view的布局流程
performLayout(lp, mWidth, mHeight);
...
}
...
if (!cancelDraw && !newSurface) {
...
//執(zhí)行view的繪制流程
performDraw();
} else {
...
}
}
從上面整理的方法來看,繪制流程主要是這performMeasure(),performLayout(),performDraw();最終會觸發(fā)RecyclerView的onMeasure(),onLayout(),onDraw()方法,具體源碼這里就不過多分析了,感興趣的可以看一下View的繪制流程;
讓我們一個個來進(jìn)行分析,先看看RecyclerView的onMeasure()方法里面做了什么?
onMeasure()分析:
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
//1.判斷當(dāng)前的LayoutManger是否為空,為空則走RecyclerView默認(rèn)測量的方法 ;
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//2.LayoutManger開啟自動測量時走這里處理邏輯;
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
//3.LayoutManger沒有開啟自動測量時走這里處理邏輯;
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
onExitLayoutOrScroll();
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
resumeRequestLayout(false);
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
這里面主要分三種情況,而我們大部分情況都是走第三步,通過查看官方的LayoutManger的源碼得知,LinearLayoutManager和StaggeredGridLayoutManager都開啟了自動測試,而GridLayoutManager繼承自LinearLayoutManager;所以,官方的LayoutManager都開啟了自動測量,這里我們只需要關(guān)注第二步的邏輯;
從上面源碼可以看出,RecyclerView通過LayoutManger里的onMeasure()來進(jìn)行測量操作;
通過State這個類來進(jìn)行布局和測試狀態(tài)的記錄,這里的mLayoutStep 包括STEP_START、STEP_LAYOUT 、 STEP_ANIMATIONS三個狀態(tài);
從源碼分析,此時測量完畢之后,判斷當(dāng)前狀態(tài)為開始的時候(STEP_START),調(diào)用了dispatchLayoutStep1()進(jìn)行了一系列的操作,這個方法執(zhí)行完了之后,會將mLayoutStep 賦值為STEP_LAYOUT;后面就執(zhí)行了dispatchLayoutStep2(),在這個方法里將mLayoutStep 賦值為STEP_ANIMATIONS;
這里我們可以理解為,RecyclerView在測量完畢之后,就開始進(jìn)行布局了,分別執(zhí)行了dispatchLayoutStep1()和dispatchLayoutStep2()方法;到此onMeasure()分析完了;
讓我們繼續(xù)接著往下看,此時RecyclerView的onMeasure()已經(jīng)執(zhí)行完了,接下來會執(zhí)行onLayout()方法,讓我們看看這個方法里面做了啥?
onLayout()分析:
先看一下源碼
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection("RV OnLayout");
//執(zhí)行布局操作
this.dispatchLayout();
TraceCompat.endSection();
this.mFirstLayoutComplete = true;
}
主要看dispatchLayout()這個方法
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
通過上面源碼可以看出,之前在onMeasure()里的這個dispatchLayoutStep2()方法里面已經(jīng)把mLayoutStep 賦值為STEP_ANIMATIONS,那么這里就會走最后一個方法dispatchLayoutStep3();如果沒有執(zhí)行STEP_START方法,那么就會依次執(zhí)行dispatchLayoutStep1(),dispatchLayoutStep2(),dispatchLayoutStep3()這幾個布局方法;讓我們來一個個分析;
dispatchLayoutStep1():
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
mState.mIsMeasuring = false;
eatRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
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));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// This is NOT the only place where a ViewHolder is added to old change holders
// list. There is another case where:
// * A VH is currently hidden but not deleted
// * The hidden item is changed in the adapter
// * Layout manager decides to layout the item in the pre-Layout pass (step1)
// When this case is detected, RV will un-hide that view and add to the old
// change holders list.
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
resumeRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
這個方法主要做了ViewHolder信息的保存,里面通過遍歷當(dāng)前的子View,根據(jù)子view的位置信息創(chuàng)建ItemHolderInfo,并添加到 ViewInfoStore這個類里面進(jìn)行保存;
看一下ItemHolderInfo這個類;
public static class ItemHolderInfo {
public int left;
public int top;
public int right;
public int bottom;
public ItemHolderInfo() {
}
...
public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder,
@AdapterChanges int flags) {
final View view = holder.itemView;
this.left = view.getLeft();
this.top = view.getTop();
this.right = view.getRight();
this.bottom = view.getBottom();
return this;
}
}
class ViewInfoStore {
private static final boolean DEBUG = false;
/**
* View data records for pre-layout
*/
@VisibleForTesting
final ArrayMap mLayoutHolderMap = new ArrayMap<>();
@VisibleForTesting
final LongSparseArray mOldChangedHolders = new LongSparseArray<>();
/**
* Clears the state and all existing tracking data
*/
void clear() {
mLayoutHolderMap.clear();
mOldChangedHolders.clear();
}
/**
* Adds the item information to the prelayout tracking
* @param holder The ViewHolder whose information is being saved
* @param info The information to save
*/
void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE;
}
}
通過源碼可以看出,在dispatchLayoutStep1()方法里會先遍歷子view,并創(chuàng)建ItemHolderInfo,然后再通過ViewInfoStore的addToPreLayout()的這個方法將ItemHolderInfo賦值給InfoRecord,再保存到mLayoutHolderMap這個集合里面;
下面我們再來分析一下dispatchLayoutStep2()這個方法里面做來啥?
dispatchLayoutStep2():
private void dispatchLayoutStep2() {
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
// 開始真正的去布局
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
}
通過上面的源碼可以看出,dispatchLayoutStep2()里面就開始真正的去布局了,通過onLayoutChildre()方法進(jìn)行布局,具體的實(shí)現(xiàn)都在LayoutManager的子類里面;我們常用的LayoutManager基本上是LinearLayoutManager,那么這里我們具體來分析一下這個類里面是怎么實(shí)現(xiàn)的;
先看一下源碼:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
// 獲取布局的錨點(diǎn)
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
...
// 更新錨點(diǎn)信息
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
//判斷是否是從后往前開始布局
if (mAnchorInfo.mLayoutFromEnd) {
...
//布局操作
fill(recycler, mLayoutState, state, false);
...
} else {
...
// fill towards end
fill(recycler, mLayoutState, state, false);
// fill towards start
fill(recycler, mLayoutState, state, false);
...
}
...
}
這里把代碼簡化了,我們只需要關(guān)注幾個重點(diǎn)的方法;這里的布局操作是,通過尋找布局的錨點(diǎn)(mAnchorInfo),判斷是從后往前布局還是從前往后布局,然后調(diào)用fill()方法進(jìn)行布局;
尋找布局的錨點(diǎn)是通過updateAnchorInfoForLayout(recycler, state, mAnchorInfo)這個方法
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
AnchorInfo anchorInfo) {
...
if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
if (DEBUG) {
Log.d(TAG, "updated anchor info from existing children");
}
return;
}
...
}
這里我們只需要關(guān)注updateAnchorFromChildren這個方法,跟進(jìn)去看一下具體做了什么;
private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
RecyclerView.State state, AnchorInfo anchorInfo) {
if (getChildCount() == 0) {
return false;
}
final View focused = getFocusedChild();
if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
return true;
}
if (mLastStackFromEnd != mStackFromEnd) {
return false;
}
View referenceChild = anchorInfo.mLayoutFromEnd
? findReferenceChildClosestToEnd(recycler, state)
: findReferenceChildClosestToStart(recycler, state);
if (referenceChild != null) {
anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
...
}
return true;
}
return false;
}
從這里的源碼可以看出,先通過getFocusedChild()去獲取focused 這個view,當(dāng)獲取到了的時候?qū)⑵錁?biāo)記為錨點(diǎn),如果獲取不到那么就通過findReferenceChildClosestToEnd和findReferenceChildClosestToStart去尋找合適的view,并將其標(biāo)記為錨點(diǎn);
讓我們回到onLayoutChildren這個方法,當(dāng)獲取到錨點(diǎn)的時候,調(diào)用fill方法開始填充頁面,根據(jù)fill方法看看具體做了什么?
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
//回收沒有用到的view
recycleByLayoutState(recycler, layoutState);
}
...
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
}
這里通過recycleByLayoutState方法先將沒有用到view進(jìn)行回收,然后再通過while循環(huán)調(diào)用layoutChunk方法進(jìn)行布局;
看一下layoutChunk方法具體做了什么操作?
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
...
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
...
}
...
layoutDecoratedWithMargins(view, left, top, right, bottom);
...
}
到這里就是最終布局的地方了,先通過recycler獲取要布局的view,再通過addView方法將view添加到RecyclerView里去,然后根據(jù)參數(shù)調(diào)用layoutDecoratedWithMargins方法進(jìn)行布局;
public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
這里最終調(diào)用了view的layout方法進(jìn)行布局;到這里dispatchLayoutStep2()就分析完了,讓我們繼續(xù)接著看dispatchLayoutStep3()第三步里面做了啥;
dispatchLayoutStep3():
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
...
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
...
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
}
long key = getChangedHolderKey(holder);
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
...
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
...
mViewInfoStore.addToPostLayout(holder, animationInfo);
...
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
//觸發(fā)動畫
mViewInfoStore.process(mViewInfoProcessCallback);
}
...
}
這個方法里面只需要關(guān)注addToPostLayout這個方法就行,這里和第一步類似,也是通過遍歷viewholder信息來創(chuàng)建ItemHolderInfo,并保存到mViewInfoStore里去;
看一下addToPostLayout這個方法做了啥?
void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.postInfo = info;
也是通過將ItemHolderInfo信息轉(zhuǎn)化為InfoRecord類,然后保存到集合里去(mLayoutHolderMap);
到此,RecyclerView的onLayout流程就已經(jīng)走完了;那么接下來就要開始分析onDraw的流程了;
onDraw()分析
先看一下源碼;
public void draw(Canvas c) {
super.draw(c);
...
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
...
}
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);
}
}
很簡單,就幾行,mItemDecorations這個集合里面存的是ItemDecoration,也就是說,RecyclerView的onDraw是用來繪制ItemDecoration的;而itemView的繪制是在ViewGroup里面;
至此,RecyclerView的onMeasure,onLayout,onDraw,流程就已經(jīng)分析完畢了;
總結(jié):
RecyclerView的布局流程比較復(fù)雜,但是還是遵循viewGroup的繪制原理,即onMeasure,onLayout,onDraw這幾步流程;
繪制流程.png
那么到這里,布局到流程就已經(jīng)講完了,希望能對你有所幫助,后面會繼續(xù)分析RecyclerView的復(fù)用機(jī)制,敬請期待!
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的android流程化步骤样式,Android RecyclerView 解析之绘制流程篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 红芹菜的功效与作用、禁忌和食用方法
- 下一篇: 什么是病毒载量