深入讲解RecyclerView布局动画原理(一)

简介: 深入讲解RecyclerView布局动画原理(一)

背景知识



RecyclerView的Adapter有几个notify相关的方法:

  • notifyDataSetChanged()
  • notifyItemChanged(int)
  • notifyItemInserted(int)
  • notifyItemRemoved(int)
  • notifyItemRangeChanged(int, int)
  • notifyItemRangeInserted(int, int)
  • notifyItemRangeRemoved(int, int)
  • notifyItemMoved(int, int)

notifyDataSetChanged()与其他方法的区别:


  1. 会导致整个列表刷新,其它几个方法则不会;


  1. 不会触发RecyclerView的动画机制,其它几个方法则会触发各种不同类型的动画。

1. 布局放置



1.1 核心方法


RecyclerView#dispatchLayout()

1.2 作用


  1. 将View放置到合适的位置
  2. 记录布局阶段View的信息
  3. 处理动画


RecyclerView的布局我们可以分成三个阶段,也可以精细分成五个阶段。


1.2.1 三个阶段


1.2.1.1 预布局阶段


当需要做动画时,预布局阶段才会工作,否则没有实际意义,它对应dispatchLayoutStep1方法。动画有开始状态和结束状态,预布局完成后的RecyclerView是动画的开始状态。


1.2.1.2 布局阶段


无论是否需要做动画,布局阶段都会工作,它对应dispatchLayoutStep2方法。布局完成后的状态是用户最终看到的状态,也是动画的结束状态。


1.2.1.3 布局后阶段


布局完成后,需要执行动画操作,它对应的是dispatchLayoutStep3方法。当动画完成后,还会进行View回收操作。


1.2.2 五个阶段


1.2.2.1 预布局前


在dispatchLayoutStep1方法调用onLayoutChildren方法之前。它会保存当前RecyclerView上所有子View的信息到ViewInfoStore中,FLAG增加FLAG_PRE。表示View在预布局前就显示在RecyclerView上。


1.2.2.2 预布局中


在dispatchLayoutStep1方法调用onLayoutChildren方法时。它会根据算法,重新布置RecyclerView的子View,该阶段可能会添加新的子View。该阶段能够确定哪些View最终是不会展示给用户看的,FLAG增加FLAG_DISAPPEARED(例如:removed的View)。


1.2.2.3 预布局后


在dispatchLayoutStep1方法调用onLayoutChildren方法之后,将预布局完成后的子View与预布局前的子View对比,将新增的View的FLAG增加FLAG_APPEAR(调用notifyItemRemoved后,新填充的View)。


1.2.2.4 布局中


在dispatchLayoutStep2方法调用onLayoutChildren方法时。该阶段会把被挤出屏幕的View的FLAG增加FLAG_DISAPPEARED。


1.2.2.5 布局后


在dispatchLayoutStep3方法中。会将最终的子View的FLAG增加FLAG_POST。


1.2.3 动画类型


1.2.3.1 PERSISTENT


预布局前和布局后都存在的View所做的动画,位置有可能发生变化了,也有可能没有发生变化。


1.2.3.2 REMOVED


在布局前对用户可见,布局后不可见,而且数据已经从数据源中删除掉了。


1.2.3.3 ADDED


新增数据到数据源中,并且在布局后对用户可见。


1.2.3.4 DISAPPEARING


数据一直都存在于数据源中,但是布局后从可见变成不可见状态(例如因为其它View插入操作,导致被挤出屏幕外了)。


1.2.3.5 APPEARING


数据一直都存在于数据源中,但是布局后从不可见变成可见状态(例如因为其它View被删除,导致补位到屏幕内了)。


1.3 源码解析


1.3.1 RecyclerView#dispatchLayout()


  1. dispatchLayoutStep1()执行预布局,记录ViewHolder位置信息;
  2. dispatchLayoutStep2()执行布局,用户最终看到的效果;
  3. dispatchLayoutStep3()执行动画操作。


image.png


2. 预布局阶段



2.1 核心方法


  1. RecyclerView#dispatchLayoutStep1()
  2. RecyclerView#processAdapterUpdatesAndSetAnimationFlags()
  3. LinearLayoutManager#onLayoutChildren()
  4. LinearLayoutManager#updateAnchorInfoForLayout()


2.2 作用


  1. 处理Adapter变化
  2. 决定该执行哪种类型动画
  3. 保存当前RecyclerView上的子View的信息
  4. 如果需要执行动画,进行预布局


2.3 源码解析


2.3.1 RecyclerView#dispatchLayoutStep1()


  1. 判断是否需要开启动画功能
  2. 如果开启动画,将当前屏幕上的Item相关信息保存起来供后续动画使用
  3. 如果开启动画,调用mLayout.onLayoutChildren方法预布局
  4. 预布局后,与第二步保存的信息对比,将新出现的Item信息保存到Appeared中


image.png

image.png

2.3.2 RecyclerView#processAdapterUpdatesAndSetAnimationFlags()


作用:判断是否需要开启动画


image.png

2.3.3 LinearLayoutManager#onLayoutChildren()


以垂直方向的RecyclerView为例子,我们填充RecyclerView的方向有两种,从上往下填充和从下往上填充。开始填充的位置不是固定的,可以从RecyclerView的任意位置处开始填充。


  1. 寻找填充的锚点(最终调用findReferenceChild方法);
  2. 移除屏幕上的Views(最终调用detachAndScrapAttachedViews方法);
  3. 从锚点处从上往下填充(调用fill和layoutChunk方法);
  4. 从锚点处从下往上填充(调用fill和layoutChunk方法);
  5. 如果还有多余的空间,继续填充(调用fill和layoutChunk方法);
  6. 布局完成后有可能产生GAP,需要修复GAP;
  7. dispatchLayoutStep2阶段调用layoutForPredictiveAnimation将scrapList中多余的ViewHolder填充(调用fill和layoutChunk方法)。


image.png

image.png

image.png

image.png

image.png

2.3.3.1 寻找填充的锚点


  1. 优先返回全部在屏幕内,未标记removed的View;
  2. 次优先级返回不可见的View;
  3. 最低优先级返回删掉的view。


image.png

2.3.3.2 移除屏幕上的Views


  1. 调用notifyItemChanged(position),position对应的ViewHolder会放入到mChangedScrap缓存中;
  2. 否则会放入到mAttachedScrap缓存中

image.png

2.3.3.3 ~ 2.3.3.5 填充


调用LinearLayoutManager#fill()和LinearLayoutManager#layoutChunk()


  1. 从缓存中获取View或者创建View
  2. 如果是step1预布局阶段,调用addView(),将标记为removed的view放入到DISAPPEARED动画列表中
  3. 如果是step2布局阶段,调用addDisappearingView(),将被挤出屏幕的view放入到DISAPPEARED动画列表中
  4. 如果是removed的或者changed,不会记录消耗的填充量


image.png

image.png

image.png

2.3.3.6 修复GAP


通过mOrientationHelper.offsetChildren(gap)直接填补GAP


image.png

2.3.3.7 layoutForPredictiveAnimation


为了做动画,增加额外的Item


  1. 不需要做动画,或者是预布局直接返回
  2. 从mAttachedScrap中遍历到非removed的ViewHolder,但是返回的结果可能包含removed ViewHolder
  3. 如果遍历找到了非Removed ViewHolder,填充View

image.png


相关文章
|
JavaScript
【vue】 vue2 自定义指令 实现全屏 、对话框拖拽
【vue】 vue2 自定义指令 实现全屏 、对话框拖拽
457 2
|
数据采集 机器学习/深度学习 算法
Python实现Stacking分类模型(RandomForestClassifier、ExtraTreesClassifier、AdaBoostClassifier、GradientBoostingClassifier、SVC)项目实战
Python实现Stacking分类模型(RandomForestClassifier、ExtraTreesClassifier、AdaBoostClassifier、GradientBoostingClassifier、SVC)项目实战
|
12月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
208 8
|
12月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
369 1
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
385 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
文字识别
分享:如何ocr识别身份证复印件并导出至excel表格 ? 图片批量识别导出excel表格应用,图片批量识别转excel表格的方法
该软件是一款OCR身份证识别工具,能批量处理图片,自动提取身份证信息并导出为Excel。支持百度网盘和腾讯云盘下载。用户界面直观,操作简单,适合新手。识别过程包括:打开图片、一键识别、导出结果。特别注意,此程序仅适用于身份证识别,不适用于其他类型的图片识别。
988 1
分享:如何ocr识别身份证复印件并导出至excel表格 ? 图片批量识别导出excel表格应用,图片批量识别转excel表格的方法
|
人工智能 API Python
国内大模型调用实战
前面我们一览了国内主要大模型厂商的API价格,今天我们就来具体看下具体API的使用【6月更文挑战第3天】
604 0
|
自然语言处理 Java
中文分词之Java实现使用IK Analyzer实现
中文分词之Java实现使用IK Analyzer实现
910 0
|
Android开发
关于安卓底部dialogfragment封装
关于安卓底部dialogfragment封装
494 0
|
Java Android开发
如何设置底部控件view随着软键盘的弹出而上移_Android基础篇(Java)
如何设置底部控件view随着软键盘的弹出而上移_Android基础篇(Java)
1256 0
如何设置底部控件view随着软键盘的弹出而上移_Android基础篇(Java)

热门文章

最新文章