【译】RecyclerView专题之item动画实现原理(一)

【译】RecyclerView专题之item动画实现原理(一),第1张

原文 http://www.birbit.com/recyclerview-animations-part-1-how-animations-work/

Listview 是Android最受欢迎的控件之一,虽然它有许多特性,但是它也是相当复杂并且很难修改.

在Lollipop中,Android发布了一个新的控件--RecyclerView,它的插件化结构使得展现collection views更加简单,仅仅通过实现一些简单的contract就可以实现很多不同的功能:

1.how item are laid out

2.item animator

3.item decorations

4.recycling strategy

Predictive Animation

在这篇文章中,我想去深入剖析RecyclerView的内部实现原理,尤其是关于动画是如何实现的.

在Honeycomb中,Android 引入了布局动画LayoutTransition,来实现当ViewGroup布局变化时的过渡动画.这个框架会拿到ViewGroup布局变化前后的状态,然后在两种状态间创建动画进行改变.

但是,列表控件与普通ViewGroup有很大的区别,列表控件中的item与ViewGroup中的子view也有很大的区别,所以我们不能直接使用LayoutTransition.在普通ViewGroup中,如果View是被新加入到ViewGroup中的,它是被当做一个新的View对待的,并且可以使用fade in等动画.但是对于列表,例如,一个item的view变成可见的,可能是因为它前面的item从Adapter中被移除了.在这种情况下,如果使用fade in动画,就会让用户产生改item是被新插入的错觉,但是事实上这个item已经在列表中了,它应该是滚入屏幕的.RecyclerView知道这个item是否是新的,但是却不知道当这个item它原来的位置在哪.同样的,对于滚出屏幕的item(前提没有被adapter移除),RecyclerView同样不知道这个view要被放置在哪.

LayoutTransition failure for a list

RecyclerView如果通过LayoutManger拿到新View的的previous位置,那么LayoutManager不仅需要做一些记录的工作还要多出一些计算的工作.

那么RecyclerView是如何处理这种item的出现和消失动画的呢(指的是那些滚出/滚入屏幕的item)?没错,就是依赖LayoutManager,

LayoutManager通过处理预布局(predictive layout logic)的逻辑,来向RecyclerView提供变化前后item的位置.当Adapter发生改变时,RecyclerView使用了两次Layout处理:

1.第一次Layout(preLayout),RecyclerView会向LayoutManager提供一些信息,让LayoutManager对改变前的状态进行一次layout.

以上面的动图为例,LayoutManager会收到C将要被移除的信息,然后进行layout(将C留下的位置补上),整个环节最重要的部分就

是RecyclerView假装C仍在Adapter中.比如,当LayoutManager要获取index为2的view时,RecyclerView要返回‘C’.

2.第二次Layout(postLayout),RecyclerView让LayoutManager重新布局items,这次‘C’已经被Adapter移除,getViewForPosition(2)拿

到的是‘D’,getViewForPosition(4)拿到的是‘F’.Keep in mind that the backing item for 'C' was already removed from the Adapter,

but since RecyclerView has the View representation of it, it can behave as if 'C' is still there. In other words, RecyclerView

does the bookkeeping for the LayoutManager.

LayoutManger每次调用onLayoutChildren时,它都会暂时detach掉所有的View然后在进行布局,未发生改变的View之前的measure还是有效的,所以这中relayout是很cheap和simple的.

LinearLayoutManager pre layout result: (pink rectangle marks the area visible to the user)*

LinearLayoutManager post layout

在两次布局之后,RecyclerView就可以知道了View的previous location,然后进行正确的动画.

Predictive animation

你也许会问:LayoutManager没有对‘C’的view进行laid out,为什么C还是可见的?

事实上,'C'在pre_layout中是被LayoutManager lai'd out了,但是在post-layout没有被laid out因为它已经不再Adapter中了.它也确实不再是LayoutManager的child,但是它却仍旧是RecyclerView的child,所以此时ItemAnimator可以正常的执行.

Disappearing Items

但是现在还有一个问题就是,将要消失的item.考虑下面这个例子,有一个item被加入到列表中,会将另一个item退出屏幕外.下面是用LayoutTransition实现的效果:

Add Animation Failure

当‘X’被添加到‘A’的后面,F会被挤出屏幕外.LayoutTransition认为‘F’已经被移除,然后对F使用了Fade out 动画.但事实上F仍在列表中.

为了解决这个问题,RecyclerView给LayoutManger提供了API来获取这个信息.在第二次Layout的最后(postLayout),LayoutManager可以调用getScrapList()方法获取那些不会被LayoutManager布局但是却仍旧在Adapter中的Views.然后LayoutManager会假设RecyclerView大到可以展示这些View,对这些View进行lay out.

LinearLayoutManager post layout

这有一个很重要的细节就是,对于那些在动画结束后不再有用的View,LayoutManger会通过调用addDisappearingView而不是addView来告诉RecyclerView,这个View在动画结束后应该被移除.RecyclerView也会添加这个View到the list of hidden views,当postLayout方法返回时,这个View就会被从LayoutManager的children list中被移除.

也许你会认为,对于LinearLayoutManager来说,完全可以单独计算View原来的位置或者将要被放置的的位置,也不必进行两次Layout *** 作.但是对于有多个item类型的Adapter,如果多个类型同时发生改变,会产生许多临界情况.此外,对于像StaggeredGridLayout这种复杂的LayoutManager,计算item的位置是很繁琐的.目前的这种方式,可以减轻LayoutManager的负担,仅仅需要一点代价就可以完成动画.

Predictive Add Animation

,

用了RecyclerView一段时间了,最近才知道是可以通过viewType来动态改变每个item的样式的。整理记录一下。

步骤如下:

根据item的坐标位置position,来返回不同的viewType。

根据不同的viewType,来生成不同的RecyclerView.ViewHolder,使用不同的布局。

在RecyclerView中,使用 notifyItemInserted(int) 和 notifyItemRemoved(int) 会有动画效果, 而使用 notifyDataSetChanged() 则没有。

参考:

DiffUtil 16年出来的,是为了我饿们你在更新列表数据时可以实现动画效果

样子是这样的:

基础部分我就不写了,大家看这里就行,写的挺清楚:

本文例子:

接下来我就来说说注意事项

DiffUtil 想出动画效果,必须给 RecyclerView 添加 item 动画,item 动画的默认是先类是 DefaultItemAnimator,并且 add,remove,update,move可以分贝设置时间

默认的 item 动画一般,推荐大家自己去做自己的效果

我这里为了明显,每个动画是 3秒

DiffUtil 动画的执行顺序:

我们只有知道自己的数据怎么变化,才能猜测出动画执行的轮廓,然后调整不是

DiffUtil 对对比新旧2个集合对应 position 位置 item

areItemsTheSame 需要特别注意,areItemsTheSame 书写不正当,非常容易触发系统 bug

这个 bug 是系统抛出来的,无解,我们处理不了, 根本原因在于 areItemsTheSame 我们没处理好

areItemsTheSame 方法的目的是:判断新旧2个列表相同 positon 位置的 item 是否是同一个,这个不光是判断 item 是不是同一个 itemtype,更要判断这个 item 的身份是不是同一个,就好比时同一个人,比如这个人的衣服,前一分钟是绿色的,然后变成了红色,目的就是这样,看是不是同一个 item, 同一个 item 有可能默写数据有变化,我们在 areContentsTheSame 方法再去判断数据是不是有变化,有变化更新数据

我的例子是这样写的:

我的列表里有2种 itemtype ,之前只判断了 itemtype 是不是一样,结果频繁报 IndexOutOfBoundsException 这个异常,搞了半天想死的心都有,最后还是再次品鉴了翻 areItemsTheSame 方法的意义才 OK 的,大家这里碰到同样的问题,请不要着急,按我的思路求做,一准没问题


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/bake/7921184.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-11
下一篇 2023-04-11

发表评论

登录后才能评论

评论列表(0条)

保存