参考文章:
总结:
- RecyclerView它的绘制其实是交给了LayoutManager处理,如果没有设置,则不会测量子View。如果RecyclerView是固定的长宽,在OnMeasure中是不会测量子View的,而是会在onLayout中测量
- 绘制其实会区分正向绘制和倒置绘制
- 绘制的过程是先确定一个瞄点,然后分别向上,向下绘制,如果对与RecyclerView来说还有剩余空间,则会再执行一次fill方法
- LayoutManager获得View是从RecyclerView中的Recycler.next()方法获得,涉及到RecyclerView的缓存策略,如果缓存没有拿到,则走我们自己重写的onCreateView方法
- onLayout主要的作用就是重置一些参数,但当RecyclerView的长宽是精确值得时候,则还有这测量子View的功能
基本用法
rv.layoutManager= layoutManager
rv.adapter=myAdapter
这个跟ListView有点不同的是增加了个layoutManager,我们都知道,如果在RecyclerView中没有设置layoutManager,数据是显示不出来的,具体的实现就不说了
源码分析
这两个方法都会执行requestlayout方法,这个方法我们都知道其实就是执行View的onMeasure,onLayout,onDraw方法,那先来看看onMeasure方法:
protected void onMeasure(int widthSpec, int heightSpec) {
//这里就说明了,如果没有设置layoutManager,就不会绘制子Children了
//我们后面也会看到,如果没设置mLayout,onLayout其实也不会实现什么
if (mLayout == null) {
//这里直接绘制本身,如果本身不是确定的大小,不会绘制children
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//官方定义的LayoutManager这个值都是true
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//如果长宽都是精确值,则执行defaultOnMeasure方法,可能有疑问了,那不就跟Layout==null的时候一样了?后面再说
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
//最后还是会执行上面的defaultOnMeasure的方法
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
//mLayoutStep默认值是 State.STEP_START
if (mState.mLayoutStep == State.STEP_START) {
/**
* 1.处理Adapter的更新
* 2.决定那些动画需要执行
* 3.保存当前View的信息
* 4.如果必要的话,执行上一个Layout的操作并且保存他的信息
* 这里跟绘制并没有什么关系
*/
dispatchLayoutStep1();
//执行完dispatchLayoutStep1()后是State.STEP_LAYOUT
}
// 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.
//如果Child没有填充满RecyclerView,继续
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);
}
}
.....
}
private void dispatchLayoutStep2() {
....
//这里我们就比较熟悉,State..mItemCount也就是数据个数目
mState.mItemCount = mAdapter.getItemCount();
.....
//为了分析,我们就来看看LinearLayoutManager的onLayoutChildren方法吧
mLayout.onLayoutChildren(mRecycler, mState);
.....
}
LinearLayoutManager.java:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
.....
// resolve layout direction
//确定layout的方向,顺序呢,还是倒序
resolveShouldLayoutReverse();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
mPendingSavedState != null) {
mAnchorInfo.reset();
//mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
//找到瞄点的位置
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
}
.....
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
LayoutState.ITEM_DIRECTION_TAIL;
}
//上面这部分是用来判断RecyclerView绘制是顺序还是倒序
......
//这部分主要就是绘制了,关键的就是fill方法
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
//其实就是确定当前方向上锚点的相关的状态信息。
updateLayoutStateToFillStart(mAnchorInfo);
.....
fill(recycler, mLayoutState, state, false);
......
}else {
...
}
//这个方法关键的是layoutChunk
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
......
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
.....
}
return start - layoutState.mAvailable;
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//获取到子View,稍后我们具体分析分析
View view = layoutState.next(recycler);
....
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
//测量ChildView
measureChildWithMargins(view, 0, 0);
......
}
上述过程大致就是取出子View,然后加进去,紧接着绘制子View。现在我们来看看上面说的layoutState.next方法:
LinearLayoutManager&LayoutState.java:
View next(RecyclerView.Recycler recycler) {
//默认mScrapList=null,
//但是执行layoutForPredictiveAnimations方法的时候不会为空,执行完后又变为空,所以这里暂时不考虑
if (mScrapList != null) {
return nextViewFromScrapList();
}
//这里是关键
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
RecyclerView&Recycler.java:
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) {
.....
//这里就比较熟悉了
holder = mAdapter.createViewHolder(RecyclerView.this, type);
return holder;
}
到此,onMeasure方法就结束了,接下来我们来看看onLayout方法吧:
RecyclerView.java:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
....
}
void dispatchLayout() {
//这里注意了,adapter和mLayout没设置的话,相当于跳过onLayout方法,
if (mAdapter == null) {
return;
}
if (mLayout == null) {
return;
}
mState.mIsMeasuring = false;
//这个点是:当RecyclerView长宽都是精确值的话,直接回跳过onMeasure
//这里就是为了绘制子View
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);
}
//其实不严谨的来讲onLayout的作用就仅仅是dispatchLayoutStep3()
//而 dispatchLayoutStep3()方法的作用除了重置一些参数外还和执行动画有关。
dispatchLayoutStep3();
}
扩展:
假如现在有个需求,RecyclerView最大的高度为4,即当RecyclerView的Item数量超过4个的时候,RecyclerView的高度是固定的,假如就显示4个Item
我们来回顾下,在上述的源码分析中,我们知道测绘子View其实是在layoutChunk中
layoutChunk其实是在一个循环中的
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state))
前两个比较复杂去实现,但最后一个:
layoutState.hasMore(state)
boolean hasMore(RecyclerView.State state) {
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}
根据上述的绘制流程我们知道,State的mItemCount属性的值其实是mAdapter.getItemCount(),这个方法就比较熟悉了,所以我们只需这样写就行了:
override fun getItemCount(): Int {
return if (data.size >= 4) 4
else
super.getItemCount()
}
其实很多网上的做法是不可行的,他们其实就是重写了LinearLayoutManager的onMeasure方法。但通过我们这的分析,子View的测量根本不在LinearLayoutManager的onMeasure方法,而是在LinearLayoutManager的onLayoutChildren方法。