三层缓存
第一层:Recycler中的mCachedViews
第二层:ViewCacheExtension 由开发者实现的缓存策略,可通过setViewCacheExtension设置到RecyclerView中
第三层:RecycledViewPool中的mScrap 可在多个RecyclerView共享的View缓存池
Scrapped View(废弃的View):一个View虽然仍附属在它的父视图RecyclerView上,但是已经被标记为已使用或已迁移。这样的View就叫做Scrapped View。
获取一个View的过程
//RecyclerView.Recycler
final ArrayList mAttachedScrap = new ArrayList<>();
private ArrayList mChangedScrap = null;
final ArrayList mCachedViews = new ArrayList();
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
//所有获取View调用的入口
public View getViewForPosition(int position) {
//调用内部实现
//参数 position 获取一个View的位置
// false 不进行预检操作 dryrun
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
//参数检查
...
//如果处于提前布局状态,尝试从mChangedScrap列表寻找
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
//如果没找到,则从mAttachedScrap列表中寻找
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
//如果没找到,或者找到的ViewHolder和OffsetPosition不匹
//配,则重新计算OffsetPosition。并且根据stable ids在
//mAttachedScrap中寻找(如果mAdapter存在stableIds)。
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
//如果还找不到,则从mViewCacheExtension中寻找,
//前提是开发者为RecyclerView设置了ViewCacheExtension
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
....
....
//如果没有设置ViewCacheExtension,肯定还是找不到的,
//那么就会从RecycledViewPool中寻找。
//因为RecycledViewPool是多个RecyclerView共享的,如果找到合适的ViewHolder,要先更新需要显示的信息。
holder = getRecycledViewPool().getRecycledView(type);
...
...
//最后一层缓存也找不到的话,只能新建了
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
//填充数据
mAdapter.bindViewHolder(holder, offsetPosition);
...
return holder.itemView;
}
观察者AdapterDataObserver,具体实现为RecyclerViewDataObserver,当数据源发生变更时,及时响应界面变化
public static abstract class AdapterDataObserver {
public void onChanged() {
// Do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// fallback to onItemRangeChanged(positionStart, itemCount) if app
// does not override this method.
onItemRangeChanged(positionStart, itemCount);
}
public void onItemRangeInserted(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
// do nothing
}
}
被观察者AdapterDataObservable,内部持有观察者AdapterDataObserver集合
static class AdapterDataObservable extends Observable {
public boolean hasObservers() {
return !mObservers.isEmpty();
}
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
// since onItemRangeChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
// since onItemRangeInserted() is implemented by the app, it could do anything,
// including removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
// since onItemRangeRemoved() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
public void notifyItemMoved(int fromPosition, int toPosition) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
}
}
}
Adapter内部持有AdapterDataObservable对象。
当我们为RecyclerView设置Adapter时,会向Adapter注册一个观察者RecyclerViewDataObserver。
adapter.registerAdapterDataObserver(mObserver);
当数据变更时,调用notify**方法时,Adapter内部的被观察者会遍历通知已经注册的观察者的对应方法,这时界面就会响应变更。
3、多种布局类型展示
基类LayoutManager
子类有LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager
测量入口:RecyclerView的onMeasure方法
//mAutoMeasure代表是否是由已经存在的LayoutManager去执行测绘工作。
//当使用自定义的LayoutManager时,需要设置此值为false
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//如果RecyclerView的测量模式为EXACTLY(准确模式)
//则不必依赖子视图的尺寸来确定RecyclerView本身的高度
//子视图的测量工作会延迟到onLayout方法中
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;
//此方法会真正执行LayoutManager的测量布局操作
dispatchLayoutStep2();
.....
.....
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
...
...
//mLayout即RecyclerView设置的LayoutManager
mLayout.onLayoutChildren(mRecycler, mState);
...
...
}
//LayoutManager的这个方法是一个空实现,具体布局操作由子类决定
//当自定义LayoutManager时,这是必须要实现的一个方法
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
//这里以LinearLayoutManager为例,说明布局过程
//通过注释可以很好的理解测量和布局算法流程
@Override
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.
...
...
//第一步,通过检查子视图和其他变量,找出锚点坐标和item位置锚点
//第二步,从底部向起始位置填充
//第三步,从顶部向末尾位置填充
//第四步,滚动要满足要求的位置,例如当从底部填充时
...
...
//当锚点计算完成后,就开始填充view
//LayoutManager的三个子类都有这个填充方法,但是不同的
//的布局类型,此方法会有不同的实现
fill(recycler, mLayoutState, state, false);
4、Item变更动画
1、为页面中存在的ViewHolder和Recycler中mCachesView中缓存的ViewHolder添加一个标志:ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN
2、检查有没有等待执行的更新操作(add/remove/update/move)。如果没有的话,并且当前没有处于布局状态,并且没有设置禁止LayoutFrozen,调用requestLayout()方法重新布局。
3、如果有等待执行的更新操作怎么办?
不允许刷新
1、RecyclerView把Item的插入、删除、移动、变更抽象成了一个个命令UpdateOp,并在AdapterHelper中维护了一个命令池。当插入一条Item时,向更新的命令队列中添加一个“ADD”命令。
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
2、然后会触发更新处理器,来处理新的命令。
3、RecyclerView会启动一个Runnable来处理等待执行的命令。
/**
* Note: this Runnable is only ever posted if:
* 1) We've been through first layout
* 2) We know we have a fixed size (mHasFixedSize)
* 3) We're attached
*/
private final Runnable mUpdateChildViewsRunnable = new Runnable() {
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (mLayoutFrozen) {
mLayoutRequestEaten = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
但是我们在单步调试的时候,发现条件isLayoutRequested()为true,所以这个Runnable什么都没干,直接返回了。
????
既然isLayoutRequested()为true,说明接下来会重新布局,可能执行更新的地方放在了layout中。
我们在dispatchLayout()方法中的第二步dispatchLayoutStep1();发现了processAdapterUpdatesAndSetAnimationFlags()方法;
该方法主要做了两件事:第一,根据操作类型和数量更新Adapter中Item的位置(例,插入一个新的Item,原列表中Item的Position要依次加一)。第二,设置RecyclerView的状态mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations为true。