前言: 当前市场上有很多成熟的RecyclerView分析文章,但那始终是其他人总结出来的,还得自己动手分析,才知道自己理解了有多少,当然这个也算是加深对RecyclerView对理解吧;

官方简介:A flexible view for providing a limited window into a large data set.

一种灵活的视图,在有限的窗口,展示大量的数据集;

在开始之前,为了加深理解,我们需要带着疑问进行阅读;

(1),RecyclerView是怎么加载数据的?

(2),RecyclerView是怎么将View绘制到页面上的?

(3),RecyclerView是怎么复用item的?

1.1 总体结构

RecyclerView主体架构.png

由上图可知,RecyclerView主要由这几部分组成;那他们的关系是啥呢? 具体是如何关联的呢?且听完细细道来!

数据层面:首页RecyclerView需要将数据和view绑定起来,是通过Adapter加载ViewHolder来实现绑定数据的;

布局层面:RecyclerView的Item的布局是通过LayoutManager来进行布局的;

复用层面:LayoutManger从Recycler获取item来进行复用;

总结:

1,Adapter:将数据转化为RecyclerView可以识别的数据;

2,ViewHolder:将数据和item绑定起来;

3,LayoutManager:通过计算将Item布局到页面中;

4,Recycler:复用机制,统一管理Item,用于复用;

5,ItemDecoration:绘制item的样式;

1.2 具体流程:

1.2.1 RecyclerView 初始化流程

首先,先来看看RecyclerView 的初始化流程,先举个简单的例子;

//获取RecyclerView 控件

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);

//创建adapter

MyAdapter adapter = new MyAdapter(list);

//创建LayoutManager

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());

//设置LayoutManager

recyclerView.setLayoutManager(linearLayoutManager);

//设置Adapter

recyclerView.setAdapter(adapter);

1,我们先来看看RecyclerView 的构造方法做了啥?

public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

//创建观察者

this.mObserver = new RecyclerView.RecyclerViewDataObserver();

//创建回收器

this.mRecycler = new RecyclerView.Recycler();

//创建布局信息保存类

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的名称进行反射创建layoutManager,并设置给RecycleView

this.createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);

...

} else {

this.setDescendantFocusability(262144);

}

//设置是否支持嵌套滚动,默认为true

this.setNestedScrollingEnabled(nestedScrollingEnabled);

}

从构造方法可以看出,里面做了一大堆初始化的操作,最主要看一下这个创建layoutManager的方法createLayoutManager();

根据布局属性进行反射来创建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 {

//通过反射创建布局构造器

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);

//将创建出来的LayoutManger设置给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) {

//停止当前的滚动操作

this.stopScroll();

if (this.mLayout != null) {

//判断当前的layoutManager如果为空,则将该layoutManager的状态进行初始化;

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();

//将当前的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());

}

//将当前的RecyclerView赋值给layoutManager

this.mLayout.setRecyclerView(this);

if (this.mIsAttached) {

this.mLayout.dispatchAttachedToWindow(this);

}

}

//更新一下RecyclerView的缓存

this.mRecycler.updateViewCacheSize();

//触发重新布局

this.requestLayout();

}

}

总结:看完RecyclerView的构造方法,里面主要是做了一些初始化的操作,并创建了layoutManager设置给RecyclerView(如果布局属性有设置的话);

2,看完了RecyclerView的setLayoutManager()的流程,我们继续接着分析,看一下setAdapter()具体做了啥?

public void setAdapter(@Nullable RecyclerView.Adapter adapter) {

this.setLayoutFrozen(false);

//主要模块

this.setAdapterInternal(adapter, false, true);

this.processDataSetCompletelyChanged(false);

this.requestLayout();

}

跟进源码,我们主要分析setAdapterInternal()这个方法,让我们看看这个源码里面做了什么操作;

private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {

if (this.mAdapter != null) {

//解注册之前的数据观察者

this.mAdapter.unregisterAdapterDataObserver(this.mObserver);

this.mAdapter.onDetachedFromRecyclerView(this);

}

if (!compatibleWithPrevious || removeAndRecycleViews) {

//进行初始化操作,初始化layoutManger,初始化mRecycler

this.removeAndRecycleViews();

}

this.mAdapterHelper.reset();

RecyclerView.Adapter oldAdapter = this.mAdapter;

//将adapter赋值给当前成员变量

this.mAdapter = adapter;

if (adapter != null) {

//adapter注册数据观察者,用于监听数据的增删改查

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注册数据监听,用于数据的增删改查的刷新,并做一些初始化的操作;

我们再看一下这个观察者里面主要做了什么操作,具体的实现是在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刷新数据所调用的方法;我们主要分析其中一个方法即可,让我们来看一下onItemRangeChanged()这个方法;

这里面主要分为两步:

public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {

RecyclerView.this.assertNotInLayoutOrScroll((String)null);

//这里通过AdapterHelper将传进来的信息保存起来

if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {

//重新布局

this.triggerUpdateProcessor();

}

}

(1)通过AdapterHelper将传进来的信息保存起来;

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()方法触发RecyclerView重新布局;

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();

}

}

1,RecyclerView的主要绘制流程;

2,复用机制;

2. 工作流程

2.1 主体关系

首先我们来看一下各个模块的关系;

关系图.png

通过上图大体可以看出这几个模块的关系:

(1)RecyclerView通过LayoutManager来进行布局操作;

(2)LayoutManager从Recycler里面获取复用的item来进行布局;

(3)Recycler管理着ViewHolder的创建与复用;

(4)Adapter将数据和ViewHolder绑定起来,并和RecyclerView注册观察者;

(5)RecyclerView通过ItemDecoration进行item样式的绘制;

接下来通过源码来细细剖析,看看具体是怎么实现的;

那么我们接着上面分析的setAdapter()方法继续分析,在setAdapter()方法里,最后调用来requestLayout(),来触发RecyclerView 的绘制流程;

这个requestLayout()这个方法最终会调用到ViewRootImp里面的requestLayout()方法;

public void requestLayout() {

if (!mHandlingLayoutInLayoutRequest) {

checkThread();

mLayoutRequested = true;

//触发绘制流程

scheduleTraversals();

}

}

在ViewRootImp里调用requestLayout()方法进行绘制,我们主要看scheduleTraversals()方法,里面最终会调用到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()这个方法里面执行了三大步骤,测量(measure),布局(layout),绘制(draw),完成的view的工作流程,将页面绘制出来;

{

// cache mView since it is used so much below...

final View host = mView;

...

if (!mStopped || mReportNextDraw) {

//执行view的测量流程

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

} else {

...

}

...

if (didLayout) {

//执行view的布局流程

performLayout(lp, mWidth, mHeight);

...

}

...

if (!cancelDraw && !newSurface) {

...

//执行view的绘制流程

performDraw();

} else {

...

}

}

从上面整理的方法来看,绘制流程主要是这performMeasure(),performLayout(),performDraw();最终会触发RecyclerView的onMeasure(),onLayout(),onDraw()方法,具体源码这里就不过多分析了,感兴趣的可以看一下View的绘制流程;

让我们一个个来进行分析,先看看RecyclerView的onMeasure()方法里面做了什么?

onMeasure()分析:

protected void onMeasure(int widthSpec, int heightSpec) {

if (mLayout == null) {

//1.判断当前的LayoutManger是否为空,为空则走RecyclerView默认测量的方法 ;

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都开启了自动测量,这里我们只需要关注第二步的逻辑;

从上面源码可以看出,RecyclerView通过LayoutManger里的onMeasure()来进行测量操作;

通过State这个类来进行布局和测试状态的记录,这里的mLayoutStep 包括STEP_START、STEP_LAYOUT 、 STEP_ANIMATIONS三个状态;

从源码分析,此时测量完毕之后,判断当前状态为开始的时候(STEP_START),调用了dispatchLayoutStep1()进行了一系列的操作,这个方法执行完了之后,会将mLayoutStep 赋值为STEP_LAYOUT;后面就执行了dispatchLayoutStep2(),在这个方法里将mLayoutStep 赋值为STEP_ANIMATIONS;

这里我们可以理解为,RecyclerView在测量完毕之后,就开始进行布局了,分别执行了dispatchLayoutStep1()和dispatchLayoutStep2()方法;到此onMeasure()分析完了;

让我们继续接着往下看,此时RecyclerView的onMeasure()已经执行完了,接下来会执行onLayout()方法,让我们看看这个方法里面做了啥?

onLayout()分析:

先看一下源码

protected void onLayout(boolean changed, int l, int t, int r, int b) {

TraceCompat.beginSection("RV OnLayout");

//执行布局操作

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()方法里面已经把mLayoutStep 赋值为STEP_ANIMATIONS,那么这里就会走最后一个方法dispatchLayoutStep3();如果没有执行STEP_START方法,那么就会依次执行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信息的保存,里面通过遍历当前的子View,根据子view的位置信息创建ItemHolderInfo,并添加到 ViewInfoStore这个类里面进行保存;

看一下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,并创建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()方法进行布局,具体的实现都在LayoutManager的子类里面;我们常用的LayoutManager基本上是LinearLayoutManager,那么这里我们具体来分析一下这个类里面是怎么实现的;

先看一下源码:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

...

final View focused = getFocusedChild();

if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION

|| mPendingSavedState != null) {

mAnchorInfo.reset();

// 获取布局的锚点

updateAnchorInfoForLayout(recycler, state, mAnchorInfo);

mAnchorInfo.mValid = true;

} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)

>= mOrientationHelper.getEndAfterPadding()

|| mOrientationHelper.getDecoratedEnd(focused)

<= mOrientationHelper.getStartAfterPadding())) {

...

// 更新锚点信息

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);

...

}

...

}

这里把代码简化了,我们只需要关注几个重点的方法;这里的布局操作是,通过寻找布局的锚点(mAnchorInfo),判断是从后往前布局还是从前往后布局,然后调用fill()方法进行布局;

寻找布局的锚点是通过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;

}

...

}

这里我们只需要关注updateAnchorFromChildren这个方法,跟进去看一下具体做了什么;

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,当获取到了的时候将其标记为锚点,如果获取不到那么就通过findReferenceChildClosestToEnd和findReferenceChildClosestToStart去寻找合适的view,并将其标记为锚点;

让我们回到onLayoutChildren这个方法,当获取到锚点的时候,调用fill方法开始填充页面,根据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进行回收,然后再通过while循环调用layoutChunk方法进行布局;

看一下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里去,然后根据参数调用layoutDecoratedWithMargins方法进行布局;

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);

}

这里最终调用了view的layout方法进行布局;到这里dispatchLayoutStep2()就分析完了,让我们继续接着看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

//触发动画

mViewInfoStore.process(mViewInfoProcessCallback);

}

...

}

这个方法里面只需要关注addToPostLayout这个方法就行,这里和第一步类似,也是通过遍历viewholder信息来创建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信息转化为InfoRecord类,然后保存到集合里去(mLayoutHolderMap);

到此,RecyclerView的onLayout流程就已经走完了;那么接下来就要开始分析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,流程就已经分析完毕了;

总结:

RecyclerView的布局流程比较复杂,但是还是遵循viewGroup的绘制原理,即onMeasure,onLayout,onDraw这几步流程;

绘制流程.png

那么到这里,布局到流程就已经讲完了,希望能对你有所帮助,后面会继续分析RecyclerView的复用机制,敬请期待!

android流程化步骤样式,Android RecyclerView 解析之绘制流程篇相关推荐

  1. Android源码解析:UI绘制流程之控件绘制

    带着问题看源码 再接再厉,我们来分析UI绘制流程最后一步绘制流程 入口ViewRootImpl.performDraw()方法 private void performDraw() {//...try ...

  2. Android自定义View系列之详解View的绘制流程

    目录 一.开场白 二.View的绘制流程 2.1测量的过程 2.2布局的过程 2.3绘制的过程 一.开场白 开讲之前我们先预设一种自定义ViewGroup的场景:我们知道LinearLayout.Fr ...

  3. android自定义漂亮按钮样式,Android开发之漂亮Button样式

    开发中各种样式的Button,其实这些样式所有的View都可以共用的,可能对于你改变的只有颜色 所有的都是用代码实现 150CC48D90067F05BFAC966F4EE3E21D.jpg 边框样式 ...

  4. android的checkbox设置样式,android自定义checkBox的样式

    释放双眼,带上耳机,听听看~! 今天,随便讲讲自定义CheckBox的样式. 第一种方法: 1.在drawable文件新建checkbox_style.xml. 2.定义一个style,使用上面的xm ...

  5. Android源码解析:UI绘制流程之测量.md

    带着问题看源码 书接上文,做安卓开发都知道只要我们在xml布局中填写控件,并设置宽高大小与位置,安卓系统就会将我们想要的布局展示出来,但是这一步是系统是如何做到的呢?这就是上文讲到的UI绘制过程,他一 ...

  6. android组件化架构 书,Android MVVM组件化架构方案

    MVVMHabitComponent 关于Android的组件化,相信大家并不陌生,网上谈论组件化的文章,多如过江之鲫,然而一篇基于MVVM模式的组件化方案却很少.结合自身的调研和探索,在此分享一篇基 ...

  7. Android系统自带样式(@android:style/)

    在AndroidManifest.xml文件的activity中配置   (API 18中Manifest文件中,<activity />要有android:theme="@an ...

  8. android 自定义edittext方框样式,Android之EditText自定义边框和边框颜色(转载)

    介绍一种比较常见的用法 第一步:准备两张图片大小一样,颜色不同的图片.图片名称分为:editbox_focus.png和editbox_normal.png 放入工程的drawable文件夹下. 第二 ...

  9. android自定义进度条样式,Android 自定义进度条

    效果 国际惯例,效果图奉上 在这里插入图片描述 目录 在这里插入图片描述 前言 写在前面,由于之前其实已经写了部分自定义View的方法,所以本来应该按照之前的系列,来进行下载暂停动画进度条,但是我把之 ...

最新文章

  1. python url请求
  2. Django分页的基本实现办法
  3. NA-NP-IE系列实验13:使用子网地址
  4. Linux 组调度学习
  5. 软件架构设计_给非专业人士介绍——软件架构设计工作
  6. stm32 lwip 如何发送不出_mbedtls | 移植mbedtls库到STM32裸机的两种方法
  7. azure云数据库_保护Azure SQL数据库免于意外删除
  8. MySQL之Got fatal error 1236 from master when reading data from binary log
  9. Makefile 编写教程(由简至难)
  10. 电脑拆机清灰及机械硬盘安装记录
  11. java实现将.acc格式转化为mp3格式
  12. 十个英文原版电子书下载网站(无需翻墙)
  13. 通信常识:波特率、数据传输速率与带宽的相互关系
  14. 数据分析的思维逻辑步骤
  15. 深度学习目前的局限性之AI识别彻底懵逼!这到底是「牛」还是「鲨」?
  16. C语言之逻辑移位与算术移位
  17. window下Python查看已经启动的进程名称并关闭
  18. ViewPager+Fragment实现页卡切换
  19. 赵小楼《天道》《遥远的救世主》深度解析(56)芮小丹的“精神绝症”和“心之地狱”
  20. Mac升级pip3 | pip install --upgrade pip

热门文章

  1. secondarynamenode异常
  2. DataGridView插入一行数据和用DataTable绑定数据2种方式
  3. 面试奇葩——交换两变量值的一些邪门歪道
  4. Seam - 无缝集成 JSF,第 3 部分: 用于 JSF 的 Ajax
  5. 重新过一遍ASP.NET 2.0(C#)(5) - Localization(本地化,多语言)
  6. 关于.Net 1.1 Windows Forms 控件的一个小问题
  7. Java中BigDecimal解决精度丢失问题
  8. MySQL查询时通过修改字段的排序规则来忽略大小写的操作讲解
  9. 网页静态化和网页伪静态化之间的区别与选择
  10. 安装错误 服务尚未启动_原创 | 西门子300软件安装出错处理大全