(三)RecyclerView简单滑动场景分析
场景
列表页加载完毕,一个列表页A含有 10 个 item, 轻轻向上滑动,RecyclerView 做了哪些操作?
今天结合源码分析一下这个场景
前提
文章分析基于RecyclerView API 25
仅分析上面这一个场景
额外创建几个ViewHolder 复用?
答案:
4 个,额外执行 4 次createViewHolder()方法
为什么?
总结如下:
- 根据源码我们知道最后一步获取 ViewHolder 的的逻辑是执行createViewHolder()方法 倒数第二步是从RecycledViewPool中获取,即只要RecycledViewPool中有 ViewHolder 了就不会在创建 ViewHolder 了
- 假设一屏可显示完整10个 item,因此屏幕最多可显示 10+1 个 item,即 11 个item. 因为稍微滑动一下 第一个和第十一个都只显示一部分.
- mCachedViews 大小为 2 因为预加载机制mCachedViews大小 +1 为 3
- 即 2+1+1 第一个 1: 预加载 将mCachedViews+1 第二个 1: 屏幕显示 10+1 个 item 接下来的滑动操作RecycledViewPool中都会有 ViewHolder 了
涉及的集合:
这个场景只涉及mCachedView和recycledViewPool这两个集合
当我们在屏幕上滑动的时候,移除的 ViewHolder 会首先放入mCachedViews,mCachedViews不满,ViewHolder 不会放入RecycledViewPool中
源码分析流程
预加载在onTouchEvent()方法中
mGapWorker.postFromTraversal() 开始预加载流程
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (action) {
//...
case MotionEvent.ACTION_MOVE: {
//...
if (mScrollState == SCROLL_STATE_DRAGGING) {
//...
if (mGapWorker != null && (dx != 0 || dy != 0)) {
//当你的指尖稍微一滑动 dy!=0 执行这个方法了
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
break;
}
}
}
final class GapWorker implements Runnable {
@Override
public void run() {
try {
//...
//执行预加载
prefetch(nextFrameNs);
} finally {
TraceCompat.endSection();
}
}
//
void prefetch(long deadlineNs) {
buildTaskList();
//刷新任务
flushTasksWithDeadline(deadlineNs);
}
//
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
//...
RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
try {
view.onEnterLayoutOrScroll();
//获取 holder
//关键代码
holder = recycler.tryGetViewHolderForPositionByDeadline(
position, false, deadlineNs);
if (holder != null) {
if (holder.isBound() && !holder.isInvalid()) {
//放入mCachedViews
recycler.recycleView(holder.itemView);
} else {
//放入recycledViewPool
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}
//...
} finally {
view.onExitLayoutOrScroll(false);
}
return holder;
}
}
GapWorker 是一个 runnable,在Run() 方法被调用时,经过层层调用,最终来到prefetchPositionWithDeadline()方法
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
//...
ViewHolder holder = null;
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
// 从mCachedView获取viewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//...
}
//...
if (holder == null) {
//...
if (holder == null) {
//...
// 回调Adapter.createViewHolder()方法
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
//...
return holder;
}
}
因此 一上来prefetch 首先会 执行
createViewHolder
1.执行createViewHolder逻辑创建一个ViewHolder,顺手放入mCachedViews
2.此时ViewHolder在集合mCachedViews中,还没在屏幕显示
3.屏幕接着滑动,当layoutChunk()方法调用的时候, 直接从mCachedViews中获取,填充 item. 屏幕显示
4.再次执行步骤 1即预加载流程,如此往复,直至recycledViewPool中含有 ViewHolder 就不在创建ViewHolder 了. 当然一共循环 4 次.
这样的好处是在layoutChunk()填充 item 的时候,不用立即创建,而是用创建好的,滑动更流畅,更丝滑.
其他知识点
在一次点击事件中多次调用adapter.notifyItemChanged()方法 item 刷新几次?
一次,因为onItemRangeChanged()方法的返回值决定是否执行刷新,为 true 的条件就是list 的 size==1,
因此不管在一次点击事件中notifyItemChanged()几次,只有一次刷新.
class AdapterHelper implements OpReorderer.Callback {
//......
/**
* @return True if updates should be processed.
*/
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (itemCount < 1) {
return false;
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
// 这里只有 size=1 的时候 return,也就是说,若是开发者adapter.notifyItemChanged()方法
// 同时调用 2 次以上,只有一次生效.即只会刷新一次
return mPendingUpdates.size() == 1;
}
//......
}
更多内容 欢迎关注公众号
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android 实现图片转二进制流及二进制转字符串
- Android 如何实现exclude aar包中的某个jar包
- android 实现控件左右或上下抖动教程
- Android Studio引入FFmpeg的方法
- Android Studio 中获取屏幕宽度实例
- Android studio 生成带Kotlin文档的实现方式
- 基于Android FileProvider 属性配置详解及FileProvider多节点问题
- Android 通过API获取数据库中的图片文件方式
- android尺子的自定义view——RulerView详解
- 浅谈Android Studio3.6 更新功能
- Android 自定义View手写签名并保存图片功能
- android 使用Xml文件定义Shape方式
- Android 判断所有字段是否已经输入的实例
- Android 将网络的Url资源转换为Drawable资源方式
- Android实现动态改变shape.xml中图形的颜色