深入理解 RecyclerView 的绘制流程和滑动原理,直面春招

本文深入探讨了Android RecyclerView的绘制流程,从onDraw()和onLayoutChildren()方法解析了绘制步骤,强调了预布局、实际布局和最后布局的细节。同时,文章详细阐述了滑动事件处理,包括Down、Move、Up、Cancel等事件,以及滑动过程中LinearLayoutManager的角色,如onLayoutChildren()、fill()和layoutChunk()方法。通过对滑动事件的响应,揭示了RecyclerView如何处理触摸事件和惯性滑动。
摘要由CSDN通过智能技术生成

归纳分发布局的三个步骤:

  • dispatchLayoutStep1():  表示进行预布局,适配器更新、动画运行、保存当前视图的信息等工作;

  • dispatchLayoutStep2():  表示对最终状态的视图进行实际布局,有必要时会多次执行;

  • dispatchLayoutStep3():  表示布局最后一步,保存和触发有关动画的信息,相关清理等工作。

1.3 onDraw()

来到最后一步的绘制onDraw()方法中,如果不需要一些特殊的效果,在TextView、ImageView控件中已经绘制完了。

@Override

public void onDraw(Canvas c) {

super.onDraw©;//所有itemView先绘制

//分别绘制ItemDecoration

final int count = mItemDecorations.size();

for (int i = 0; i < count; i++) {

mItemDecorations.get(i).onDraw(c, this, mState);

}

}

1.4 绘制流程总结:

1、RecyclerView的itemView可能会被测量多次,如果RecyclerView的宽高是固定值或者match_parent,那么在onMeasure()阶段是不会提前测量ItemView布局,如果RecyclerView的宽高是wrap_content,由于还没有知道RecyclerView的实际宽高,那么会提前在onMeasure()阶段遍历测量itemView布局确定内容显示区域的宽高值来确定RecyclerView的实际宽高;

2、dispatchLayoutStep1()dispatchLayoutStep2()dispatchLayoutStep3()这三个方法一定会执行,在RecyclerView的实际宽高不确定时,会提前多次执行dispatchLayoutStep1()dispatchLayoutStep2()方法,最后在onLayout()阶段执行 dispatchLayoutStep3(),如果有itemView发生改变会再次执行dispatchLayoutStep2()

3、正在的测量和布局itemView实际在dispatchLayoutStep2()方法中。

RecyclerView的绘制三个步骤流程图: 在这里插入图片描述

2、LinearLayoutManager填充、测量、布局过程


RecyclerView的绘制经过measure、layout、draw三个步骤,但是itemView的真正布局时委托给各个的LayoutManager中处理,上面LinearLayoutManager可以知道dispatchLayoutStep2()是实际布局视图步骤,通过LayoutManager调用onLayoutChildren()方法进行布局itemView,它是绘制itemView的核心方法,表示从给定的适配器中列出所有相关的子视图。

2.1 onLayoutChildren()布局itemView

@Override

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

// 1) 检查子类和其他变量找到描点坐标和描点位置

// 2) 从开始填补,从底部堆积

// 3) 从底部填补,从顶部堆积

// 4) 从底部堆积来满足需求

// 创建布局状态

if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {

if (state.getItemCount() == 0) {

removeAndRecycleAllViews(recycler);//移除所有子View

return;

}

}

ensureLayoutState();

mLayoutState.mRecycle = false;//禁止回收

//颠倒绘制布局

resolveShouldLayoutReverse();

final View focused = getFocusedChild();//获取目前持有焦点的child

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

|| mPendingSavedState != null) {

mAnchorInfo.reset();//重置锚点信息

mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;

//1. 计算更新描点位置和坐标

updateAnchorInfoForLayout(recycler, state, mAnchorInfo);

mAnchorInfo.mValid = true;

}

·······

//计算第一布局的方向

int startOffset;

int endOffset;

final int firstLayoutDirection;

onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);

detachAndScrapAttachedViews(recycler);//暂时分离已经附加的view,即将所有child detach并通过Scrap回收

mLayoutState.mInfinite = resolveIsInfinite();

mLayoutState.mIsPreLayout = state.isPreLayout();

mLayoutState.mNoRecycleSpace = 0;

//2.开始填充,从底部开始堆叠;

if (mAnchorInfo.mLayoutFromEnd) {

//描点位置从start位置开始填充ItemView布局

updateLayoutStateToFillStart(mAnchorInfo);

fill(recycler, mLayoutState, state, false);//填充所有itemView

//描点位置从end位置开始填充ItemView布局

updateLayoutStateToFillEnd(mAnchorInfo);

fill(recycler, mLayoutState, state, false);//填充所有itemView

endOffset = mLayoutState.mOffset;

}else { //3.向底填充,从上往下堆放;

//描点位置从end位置开始填充ItemView布局

updateLayoutStateToFillEnd(mAnchorInfo);

fill(recycler, mLayoutState, state, false);

//描点位置从start位置开始填充ItemView布局

updateLayoutStateToFillStart(mAnchorInfo);

fill(recycler, mLayoutState, state, false);

startOffset = mLayoutState.mOffset;

}

//4.计算滚动偏移量,如果有必要会在调用fill方法去填充新的ItemView

layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);

}

首先是状态判断和一些准备工作,对描点信息选择和更新detachAndScrapAttachedViews(recycler)暂时将已经附加的view分离,缓存Scrap中,下次重新填充时直接拿出来复用。然后计算是从哪个方向开始布局。布局算法如下:

  • 1.通过检查子元素和其他变量,找到一个锚点坐标和一个锚点项的位置;

  • 2.开始填充,从底部开始堆叠;

  • 3.向底填充,从上往下堆放;

  • 4.滚动以满足要求,如堆栈从底部。

2.2 fill()开始填充itemView

填充布局交给了fill()方法,表示填充由layoutState定义的给定布局。为什么要fill两次呢?我们来看看fill()方法:

//填充方法,返回的是填充itemView的像素,方便后续滚动时使用

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,

RecyclerView.State state, boolean stopOnFocusable) {

recycleByLayoutState(recycler, layoutState);//回收滑出屏幕的view

int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;

LayoutChunkResult layoutChunkResult = mLayoutChunkResult;

//核心 == while()循环 ==

while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {//一直循环,知道没有数据

layoutChunkResult.resetInternal();

//填充itemView的核心方法

layoutChunk(recycler, state, layoutState, layoutChunkResult);

······

if (layoutChunkResult.mFinished) {//布局结束,退出循环

break;

}

layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;//根据添加的child高度偏移计算

}

······

return start - layoutState.mAvailable;//返回这次填充的区域大小

}

fill()核心就是一个while()循环,循环执行layoutChunk()填充一个itemView到屏幕,同时返回这次填充的区域大小。首先根据屏幕还有多少剩余空间remainingSpace,根据这个数值减去子View所占的空间大小,小于0时布局子View结束,如果当前所有子View还没有超过remainingSpace时,调用layoutChunk()安排View的位置。

2.3 layoutChunk()对itemView创建、填充、测量、布局

layoutChunk()作为最终填充布局itemView的方法,对itemView创建、填充、测量、布局,主要有以下几个步骤:

  • 1.layoutState.next(recycler)从缓存中获取itemView,如果没有则创建itemVie

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值