首先是RecyclerView的一般用法
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
//设置为垂直布局,这也是默认的
layoutManager.setOrientation(OrientationHelper. VERTICAL);
//设置Adapter
recyclerView.setAdapter( recycleAdapter);
//设置分隔线
recyclerView.addItemDecoration( new DividerGridItemDecoration(this ));
//设置增加或删除条目的动画
recyclerView.setItemAnimator( new DefaultItemAnimator());
- constructor 构造函数根据设置属性进行了初始化工作,如果设置了 layoutManager 属性,也会通过反射实例化 layoutManager。
- setLayoutManager 会先清空之前缓存的View,通过 requestLayout 通知执行测量与绘制 onMeasure、onLayout、onDraw。
RecyclerView 会将测量与布局交给 LayoutManager 来做,并且 LayoutManager 有一个 mAutoMeasure 的属性来控制自动测量,否则 LayoutManager 就要重写 onMeasure 来处理测量工作
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout
+ " is already attached to a RecyclerView: " + layout.mRecyclerView);
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
- onMeasure 如果宽高都为 EXACTLY是,可以直接设置对应的宽高,然后return,也就是 skipMeasure。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
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 {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
...
}
}
- 如果不是 EXACTLY, onMeasure 就会根据 MeasureSpec 处理布局。dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3来布局,其中布局状态保存在 RecyclerView.State
RecyclerView.State
这个类封装了当前RecyclerView的有用信息。
public static class State {
@IntDef(flag = true, value = {
STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
})
@Retention(RetentionPolicy.SOURCE)
@interface LayoutState {}
@LayoutState
int mLayoutStep = STEP_START; // RecyclerView 的布局状态
}
step1负责记录状态,step2负责布局,step3则与step1进行比较,根据变化来触发动画。
RecyclerView是支持 WRAP_CONTENT 属性的,比如我们可以很容易的在 RecyclerView 的下面放置其它的View,RecyclerView 会根据子 View 所占大小动态调整自己的大小,这时候,RecyclerView 就会将子控件的 measure 与 layout 提前到 Recycler 的 onMeasure 中,因为它需要确定子空间的大小与位置后,再来设置自己的大小。所以这时候就会在 onMeasure 中完成 step1 与 step2。否则,就需要在 onLayout 中去完成整个布局过程。
- onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Trace.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
Trace.endSection();
mFirstLayoutComplete = true;
}
这里我们看到如果在onMeasure中已经完成了step1与step2,则只会执行step3,否则三步会依次触发。
现在我们来分析一下这三步
- step1
它的目的就是记录View的状态,首先遍历当前所有的View依次进行处理,mItemAnimator会根据每个View的信息封装成一个ItemHolderInfo,这个ItemHolderInfo中主要包含的就是当前View的位置状态等。然后ItemHolderInfo 就被存入mViewInfoStore中
private void dispatchLayoutStep1(){
...
if (mState.mRunSimpleAnimations) {
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
...
}
}
...
mState.mLayoutStep = State.STEP_LAYOUT;
}
数据被存储到 ViewInfoStore 中的,可以查看ArrayMap介绍
class ViewInfoStore {
@VisibleForTesting
final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
@VisibleForTesting
final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
// ArrayMap和SparseArray一样,也会对key使用二分法进行从小到大排序,
// 在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,
// 然后通过index来进行添加、查找、删除等操作,
// ArrayMap 的 key 可以是任意值, SparseArray 的 key 只能为int,
}
其中 addToPreLayout 的功能就是利用 ArrayMap 的二分查找先快速根据holder来查询InfoRecord信息,如果没有,则生成,然后将info信息赋值给InfoRecord的preInfo变量。最后标记FLAG_PRE信息。
void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE;
}
// 主要包含的就是当前View的位置状态等
public static class ItemHolderInfo {
public int left;
public int top;
public int right;
public int bottom;
@AdapterChanges
public int changeFlags;
public ItemHolderInfo() {
}
}
- step2
private void dispatchLayoutStep2() {
...
mLayout.onLayoutChildren(mRecycler, mState);
...
mState.mLayoutStep = State.STEP_ANIMATIONS;
}
onLayoutChildren
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
mPendingSavedState != null) {
// 找到 anchor 点
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
}
...
// 确定布局方向
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
LayoutState.ITEM_DIRECTION_TAIL;
}
...
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
...
if (mAnchorInfo.mLayoutFromEnd) {
...
} else {
// 根据anchor一直向 End 方向布局,直至填充满anchor点前面的所有区域
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
...
// 根据anchor一直向 Start 方向布局,直至填充满anchor点后面的所有区域
updateLayoutStateToFillStart(mAnchorInfo);
...
fill(recycler, mLayoutState, state, false);
...
}
...
}
确定 anchor 是通过 updateAnchorFromChildren,首先寻找被focus的child,找到的话以此child作为anchor,否则根据布局的方向寻找最合适的child来作为anchor,如果找到则将child的信息赋值给anchorInfo,其实anchorInfo主要记录的信息就是view的物理位置与在adapter中的位置。找到后返回true,否则返回false则交由上一步的函数做处理。
还可以通过重写onAnchorReady方法,来实现定位。
确定布局方向后就调用 fill 进行填充
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 根据当前信息对不需要的 View 进行回收
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
return start - layoutState.mAvailable;
}
回收 View,回收的是逃离边界的 View,recycleViewsFromStart -> recycleChildren -> removeAndRecycleViewAt
public void removeAndRecycleViewAt(int index, Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);
recycler.recycleView(view);
}
接下来是 layoutChunk
layoutChunk获取缓存
- step3
private void dispatchLayoutStep3() {
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
...
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
mViewInfoStore.process(mViewInfoProcessCallback);
}
...
}
这个方法在 onLayout 中执行,子View 已经布局完成,通过 recordPostLayoutInformation 和 addToPostLayout ,来记录布局之后的状态。最后调用 process 方法,这个方法就是根据mViewInfoStore中的View信息,来执行动画逻辑。
接下来就是draw
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
...
}
@Override
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);
}
}
- Adapter
adapter 根据 data 返回绑定的 ViewHolder,同时在 setAdapter 时还注册了 RecyclerViewDataObserver ,可以在 notify 的时候,就会通知更新 View,在 setAdapter 还作了:
- 如果之前存在 Adapter,先移除原来的,注销观察者,和从RecyclerView Detached。
- 然后根据参数,决定是否清除原来的 ViewHolder
- 然后重置 AdapterHelper,并更新 Adapter,注册观察者。
现在来分析 RecyclerViewDataObserver
private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
// 1) 断言不在布局或者滚动过程中
assertNotInLayoutOrScroll(null);
// 2) 这里小心,不要小看if括号中的内容,这是关键。我们去看看这个方法的实现。
// 见下面注释 3),在 3) 返回true之后执行triggerUpdateProcessor方法,
// triggerUpdateProcessor方法分析请看注释 4)。
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
// 4) 触发更新处理操作,分为两种情况,在 版本大于16 且 已经Attach 并且 设置了大小固定 的情况下,
// 进行mUpdateChildViewsRunnable中的操作。否则请求布局。
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
RecyclerView.this.postOnAnimation(mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
// 5) 其中核心代码是consumePendingUpdateOperations()
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
...
consumePendingUpdateOperations();
}
};
private void consumePendingUpdateOperations() {
...
if (mAdapterHelper.hasAnyUpdateTypes(UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(UpdateOp.ADD | UpdateOp.REMOVE | UpdateOp.MOVE)) {
// 6) 如果只有更新类型的操作(这里指内容的更新,不影响View位置的改变)的情况下,
// 先进行预处理,然后在没有View更新的情况下消耗延迟的更新操作,否则调用
// dispatchLayout方法对RecyclerView中的View重新布局。那么接下来分析
// preProcess()方法。
mAdapterHelper.preProcess();
if (!mLayoutRequestEaten) {
if (hasUpdatedView()) {
dispatchLayout();
} else {
mAdapterHelper.consumePostponedUpdates();
}
}
resumeRequestLayout(true);
} else if (mAdapterHelper.hasPendingUpdates()) {
// 7) 在既有更新操作又有添加或者删除或者移动中任意一个的情况下,调用
// dispatchLayout方法对RecyclerView中的View重新布局
dispatchLayout();
}
}
// 8) 预处理做了以下几件事情,<1> 先将待处理操作重排。<2> 应用所有操作 <3> 清空待处理操作列表,
// 以ADD为例分析流程。
void preProcess() {
mOpReorderer.reorderOps(mPendingUpdates);
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
case UpdateOp.ADD:
applyAdd(op);
break;
case UpdateOp.REMOVE:
applyRemove(op);
break;
case UpdateOp.UPDATE:
applyUpdate(op);
break;
case UpdateOp.MOVE:
applyMove(op);
break;
}
if (mOnItemProcessedCallback != null) {
mOnItemProcessedCallback.run();
}
}
mPendingUpdates.clear();
}
// 9) 直接看 postponeAndUpdateViewHolders
private void applyAdd(UpdateOp op) {
postponeAndUpdateViewHolders(op);
}
// 10) 先将操作添加到推迟的操作列表中。然后将操作的内容交给回调处理。
private void postponeAndUpdateViewHolders(UpdateOp op) {
mPostponedList.add(op);
switch (op.cmd) {
case UpdateOp.ADD:
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
break;
case UpdateOp.MOVE:
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
break;
case UpdateOp.REMOVE:
mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
op.itemCount);
break;
case UpdateOp.UPDATE:
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
break;
default:
throw new IllegalArgumentException("Unknown update op type for " + op);
}
}
// 11) 直接看offsetPositionRecordsForInsert
@Override
public void offsetPositionsForAdd(int positionStart, int itemCount) {
offsetPositionRecordsForInsert(positionStart, itemCount);
mItemsAddedOrRemoved = true;
}
// 12) 该方法主要是遍历所有的ViewHolder,然后把在插入位置之后的ViewHolder的位置
// 向后移动插入的个数,最后在对Recycler中缓存的ViewHolder做同样的操作,最后申请重新布局。
void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
holder.offsetPosition(itemCount, false);
mState.mStructureChanged = true;
}
}
mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
requestLayout();
}
class AdapterHelper implements OpReorderer.Callback {
// 3) 该方法将插入操作的信息存储到一个UpdateOp中,并添加到待处理更新操作列表 mPendingUpdates 中,
// 如果操作列表中的值是1,就返回真表示需要处理操作,等于1的判断避免重复触发处理操作。
// obtainUpdateOp内部是通过池来得到一个UpdateOp对象。那么下面回去看我们注释 4)。
boolean onItemRangeInserted(int positionStart, int itemCount) {
if (itemCount < 1) {
return false;
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.ADD;
return mPendingUpdates.size() == 1;
}
}
注意,在上面 4) 步的时候有个优化 POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached
,就是可以通过 setHasFixedSize(true),来减少一定程度的计算,绕过requestlayout,只走自身的布局流程,提高性能。
- 滑动
RecyclerView的滑动过程可以分为2个阶段:手指在屏幕上移动,使RecyclerView滑动的过程,可以称为scroll;手指离开屏幕,RecyclerView继续滑动一段距离的过程,可以称为fling。
我们可以参考下图
RecyclerView滑动过程- 当RecyclerView接收到ACTION_MOVE事件后,会先计算出手指移动距离(dy),并与滑动阀值(mTouchSlop)比较,当大于此阀值时将滑动状态设置为SCROLL_STATE_DRAGGING,而后调用scrollByInternal()方法,使RecyclerView滑动,这样RecyclerView的滑动的第一阶段scroll就完成了。其中
scrollByInternal
调用的是LinearLayoutManager.scrollBy
方法 - 当接收到ACTION_UP事件时,会根据之前的滑动距离与时间计算出一个初速度yvel,这步计算是由 VelocityTracker 实现的,然后再以此初速度,调用方法fling(),完成RecyclerView滑动的第二阶段fling。
fling
方法调用的是mViewFlinger.fling
,其实借助的是Scroller
实现的
postOnAnimation()保证了RecyclerView的滑动是流畅,这里涉及到著名的“android 16ms”机制,简单来说理想状态下,上段代码会以16毫秒一次的速度执行,也就是每16毫秒平移一次。
我们再看看 scrollBy 方法
- 当RecyclerView接收到ACTION_MOVE事件后,会先计算出手指移动距离(dy),并与滑动阀值(mTouchSlop)比较,当大于此阀值时将滑动状态设置为SCROLL_STATE_DRAGGING,而后调用scrollByInternal()方法,使RecyclerView滑动,这样RecyclerView的滑动的第一阶段scroll就完成了。其中
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
...
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
...
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
// mOrientationHelper.offsetChildren()作用就是平移ItemView
mOrientationHelper.offsetChildren(-scrolled);
...
}
public void fling(int velocityX, int velocityY) {
setScrollState(SCROLL_STATE_SETTLING);
mLastFlingX = mLastFlingY = 0;
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postOnAnimation();
}
在ViewFling 的 run 方法中,我们看到,如果没有结束,会继续滑动下去
...
if (scroller.isFinished() || !fullyConsumedAny) {
setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
} else {
postOnAnimation();
if (mGapWorker != null) {
mGapWorker.postFromTraversal(RecyclerView.this, dx, dy);
}
}
...
- 缓存
根据列表位置获取ItemView,先后从scrapped、cached、exCached、recycled集合中查找相应的ItemView,如果没有找到,就创建(Adapter.createViewHolder()),最后与数据集绑定。其中scrapped、cached和exCached集合定义在RecyclerView.Recycler中,分别表示将要在RecyclerView中删除的ItemView、一级缓存ItemView和二级缓存ItemView,cached集合的大小默认为2,exCached是需要我们通过RecyclerView.ViewCacheExtension自己实现的,默认没有;recycled集合其实是一个Map,定义在RecyclerView.RecycledViewPool中,将ItemView以ItemType分类保存了下来,通过RecyclerView.RecycledViewPool可以实现在不同的RecyclerView之间共享ItemView,只要为这些不同RecyclerView设置同一个RecyclerView.RecycledViewPool就可以了。 - 回收
View的回收并不像View的创建那么复杂,这里只涉及了两层缓存mCachedViews与mRecyclerPool,mCachedViews相当于一个先进先出的数据结构,每当有新的View需要缓存时都会将新的View存入mCachedViews,而mCachedViews则会移除头元素,并将头元素放入mRecyclerPool,所以mCachedViews相当于一级缓存,mRecyclerPool则相当于二级缓存,并且mRecyclerPool是可以多个RecyclerView共享的
最后与 AdapterView 比较
区别 | AdapterView | RecyclerView |
---|---|---|
点击事件 | 提供click、LongClick事件 | 使用 onTouchListener(复杂) |
分割线 | divider | ItemDecoration(复杂,定制型强) |
布局 | listView(流式布局)和GridView(网格时) | LayoutManager(简单) |
缓存 | RecyclerBin | Recycler |
局部刷新 | 复杂 | notifyItemChanged(position,payload) |
动画 | 复杂 | 简单 |
嵌套布局 | 支持头部和尾部 | 通过 Type实现 |
多选 | 自身优点 | 复杂 |
网友评论