深入讲解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


相关文章
|
15天前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
39 8
|
Android开发 开发者
RecyclerView定制:通用ItemDecoration及全展开RecyclerView的实现
RecyclerView定制:通用ItemDecoration及全展开RecyclerView的实现
438 0
RecyclerView定制:通用ItemDecoration及全展开RecyclerView的实现
|
XML Java Android开发
Android——RecyclerView简单实现及Viewbinding优化
本文是博主对Adapter(适配器)的一些理解,为了加深对Adapter的理解以及记录自己的阶段学习而写,同时也适合初学者阅读,参考本条博客的逻辑进行学习。
390 0
|
缓存 Android开发
ListView的优化
ListView的优化
RecyclerView#smoothScrollToPosition调用RecyclerView#OnScrollListener的过程
项目中使用到了RecyclerView#smoothScrollToPosition(0)方法让Recyclerview滚动到顶部,同时给Recyclerview设置了监听器RecyclerView.OnScrollListener。
RecyclerView学习-RecyclerView#Adapter#notifyDataSetChanged是如何更新数据的?
RecyclerView学习-RecyclerView#Adapter#notifyDataSetChanged是如何更新数据的?
深入讲解RecyclerView布局动画原理(二)
深入讲解RecyclerView布局动画原理(二)
深入讲解RecyclerView布局动画原理(二)
|
缓存 Android开发
Android RecyclerView 绘制流程及Recycler缓存(上)
RecyclerView 源码一万多行,想全部读懂学会挺麻烦的,感兴趣的可以自己去瞅瞅,这篇文章重点来看下 RecyclerView是如何一步步将每一个 ItemView 显示到屏幕上,然后再分析在显示和滑动过程中,是如何通过缓存复用来提升整体性能的。 RecyclerView本质上也是一个 自定义控件 ,因此我们可以沿着分析其 onMeasure -> onLayout -> onDraw 这 3 个方法的路线来深入研究。
232 0
Android RecyclerView 绘制流程及Recycler缓存(上)
|
缓存 Android开发 容器
Android RecyclerView 绘制流程及Recycler缓存(下)
缓存复用原理 Recycler 缓存复用是 RecyclerView 中另一个非常重要的机制,这套机制主要实现了 ViewHolder 的缓存以及复用。
400 0
|
Java 容器
【RecyclerView】二、RecyclerView 简介 ( RecyclerView 特点 | RecyclerView 涉及到的类 )
【RecyclerView】二、RecyclerView 简介 ( RecyclerView 特点 | RecyclerView 涉及到的类 )
179 0