Android中的RecyclerView源码分析
RecyclerView组件
- Adapter
提供数据以及数据变更通知 - LayoutManager
布局管理,负责测绘、指定item位置、item回收、 - ItemAnimator
Item变更动画
关键实现
1、ViewHolder复用
三层缓存
第一层:Recycler中的mCachedViews
第二层:ViewCacheExtension 由开发者实现的缓存策略,可通过setViewCacheExtension设置到RecyclerView中
第三层:RecycledViewPool中的mScrap 可在多个RecyclerView共享的View缓存池
Scrapped View(废弃的View):一个View虽然仍附属在它的父视图RecyclerView上,但是已经被标记为已使用或已迁移。这样的View就叫做Scrapped View。
获取一个View的过程
//RecyclerView.Recyclerfinal 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; }
2、数据变更通知(观察者模式)
观察者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时,需要设置此值为falseif (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变更动画
5、其他
notifyDataSetChanged发生了什么
1、为页面中存在的ViewHolder和Recycler中mCachesView中缓存的ViewHolder添加一个标志:ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN
2、检查有没有等待执行的更新操作(add/remove/update/move)。如果没有的话,并且当前没有处于布局状态,并且没有设置禁止LayoutFrozen,调用requestLayout()方法重新布局。
3、如果有等待执行的更新操作怎么办?
不允许刷新
notifyItemInserted发生了什么
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。
更多相关文章
- Android如何读写CSV文件方法示例
- Android(安卓)Handler机制3--SystemClock类
- Android封装SDK生成Jar包以及混淆的方法
- Android(安卓)架构师之路22 响应式编程RxJava 整体变换
- 【Android基础】(1)四大核心组件之Activity
- Android(安卓)8.0 启动Service适配(Not allowed to start servic
- 【Android】 Activity Lifecycle
- Android(安卓)RecyclerView 二级列表实现
- android实现点击按钮切换不同的fragment布局