参考:
https://juejin.cn/post/6844904146684870669
https://blog.csdn.net/c10WTiybQ1Ye3/article/details/107193802
RecyclerView 版本
androidx.recyclerview:recyclerview:1.1.0-alpha05
从RecyclerView的onLayout入手
RecyclerView.onLayout(...)
->RecyclerView.dispatchLayout()
// dispatchLayoutStep1方法等同于pre layout;
//源码注释:这里决定哪个动画运行,保存当前View信息等;
///dispatchLayoutStep2方法处理真正布局的地方
->RecyclerView.dispatchLayoutStep1()
->RecyclerView.dispatchLayoutStep2()
//mLayout是LayoutManager的实例
->mLayout.onLayoutChildren(mRecycler, mState);
//此处查看LinearLayoutManager.fill() 注释:填充给定Layout
->LinearLayoutManager.fill(recycler, mLayoutState, state, false);
//循环调用,每次返回一个
->LinearLayoutManager.layoutChunk(recycler, layoutState)
->LinearLayoutManager.LayoutState.next()
//通过 Recycler 获取指定位置的 ItemView
->RecyclerView.recycler.getViewForPosition(int position)
//获取ViewHolder 返回ViewHolder中的ItemView
->RecyclerView.tryGetViewHolderForPositionByDeadline(***)
获取ViewHolder流程
代码展示
1 如果是预布局,尝试从mChangedScrap 中获取ViewHolder
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
...
}
2 尝试从mAttachedScrap/mHiddenViews/mCachedViews 中获取ViewHolder
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...
}
3 如果存在StableId 尝试使用ID从mAttachedScrap中获取ViewHolder
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
...
}
3.1 如果用户有自定义缓存,尝试从mViewCacheExtension中获取ViewHolder,一般不会自定义
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);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
4 尝试从mRecyclerPool中获取ViewHolder
if (holder == null) { // fallback to pool
...
holder = getRecycledViewPool().getRecycledView(type);
...
}
5 如果以上方法均未获取到则创建一个ViewHolder
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
关于Pre-layout(预布局)
当adapter调用notifyItemChanged()或者notifyItemRangeChanged()的时候,onLayoutChildren()会调用两次,一次是预布局,一次是实际布局。通过对比两次布局的不同,RecyclerView可以完成预测动画。
好比当前有A,B,C三个Item,当前A,B Item显示在屏幕上,这时候我们做一个将B移除C展示的操作。这时候为了更好地用户体验需要增加一个C平缓进入B原先位置的动画。如果只布局一次的话,我们只知道C的最终位置(也就是B原先的位置),但是并不知道C的起始位置(也就是不知道该从哪个坐标开始启动动画,因为滑动方向不确定,每个LayoutManager滑动方式也不确定)、而如果有预布局的话,可以在预布局的时候将ABC三个Item都布局出来,然后通过与实际布局对比,就能知道动画开始的坐标从而开启动画
关于StableID
stableID 作用在于调用notifyDataSetChanged方法后,LayoutManager重新布局的的时候将ViewHolder回收到何处
没有设置StableId,viewHolder被回收到RecyclerViewPool 如果设置了StableId,viewHolder被回收到Scrap中
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
...
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
//回收到RecyclerViewPool
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
//回收到Scrap中
recycler.scrapView(view);
...
}
}
关于Scrap
mChangedScrap 和 mAttachedScrap 是RecyclerView最先查找ViewHolder的地方, 只在布局阶段使用,布局完成后这两个地方的ViewHolder会移到mCachedViews 或者mRecyclerPool中。
当LayoutManager开始布局的时候(预布局或者真正布局),当前布局中所有ViewHolder都会被回收到Scrap中,然后LayoutManager挨个的取回ViewHolder,除非View发生了变化,否则它会立马从Scrap中回到原来位置。
这样做是为了能让RecyclerView和LayoutManager更好地分离,LayoutManager不需要去关心那个View需要应该保留哪个View需要移到RecyclerPool中,这些都是RecyclerView的职责,他只需要去Scrap中取ViewHolder就好了。
//LinearLayoutManager.onLayoutChildren()
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler,
RecyclerView.State state) {
...
//回收到Scrap中
detachAndScrapAttachedViews(recycler);
...
}
mChangedScrap 和 mAttachedScrap区别
1、添加时机不同,只有在Item发生了变化(notifyItemChanged或者notifyItemRangeChanged被调用),并且ItemAnimator调用canReuseUpdatedViewHolder()返回false时才会添加到mAttachedScrap,否则添加到mChangedScrap中
CanReuseUpdateViewHolder返回‘false’表示要使用不同的ViewHolder来完成动画,true表示使用相同的ViewHolder完成动画例如淡入淡出
2、mChangedScrap 只在预布局的时候会使用到,mAttachedScrap在整个布局中都可以使用
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_INVALID)|| !holder.isUpdated() ||
canReuseUpdatedViewHolder(holder)) {
...
holder.setScrapContainer(this, false);
//被同时标记为Removed和Invalid,或者没有更新的,或者ItemAnimator为null,
//或者ItemAnimator.canReuseUpdatedViewHolder返回true时
//添加到mChangedScrap 中
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList();
}
holder.setScrapContainer(this, true);
//添加到mChangedScrap 中
mChangedScrap.add(holder);
}
关于mHiddenViews
找到mHiddenViews添加的地方,发现在addAnimatingView(ViewHolder viewHolder) 方法中为调用逻辑处。
注释明确的说明将视图添加到animatingViews列表中纯粹是出于动画目的,他们与常规的视图区分管理,并且对LayoutManager是不可见的
/**
* Adds a view to the animatingViews list.
* mAnimatingViews holds the child views that are currently being kept around
* purely for the purpose of being animated out of view. They are drawn as a regular
* part of the child list of the RecyclerView, but they are invisible to the LayoutManager
* as they are managed separately from the regular child views.
* @param viewHolder The ViewHolder to be removed
*/
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
final boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
if (viewHolder.isTmpDetached()) {
// re-attach
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if (!alreadyParented) {
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
}
}
性能优化方向