RecyclerView animations - AndroidDevSummit write-up(译文)

  最近在看别人关于RecyclerView的博客,恰巧看到一位国外大神的博客,是关于RecyclerView动画的。我从那篇博客里面学习到了很多的东西,觉得有必要把它分享出来,所以我打算将它翻译成中文,不过还是建议大家去看英文原文。
  原文链接:RecyclerView animations - AndroidDevSummit write-up,原文是需要翻墙的哦。


  RecyclerView Animations and Behind the Scenes - 我们再来回到一个演示视频里面来。在所有移动平台上,有一个不争的事实,列表视图(list views)或者更一般的视图集合是最常见的视图模式,所以,尽可能了解他们的原理是非常重要的。
  今天,我们来借助于Android Dev的教学视频,来仔细的了解一下RecyclerViewItemAnimator

1. Default Animations in RecyclerView

  谷歌官方通过默认的几种操作,例如:addingremovingchanging,为我们提供了如下的动画:

  1. 淡入和淡出(对应着adding操作和removing操作)
  2. 位移(把剩余的items移动到正确位置上)
  3. 同时渐变(淡入和淡出同时执行,对应着update特定item的操作)

  官方为我们提供了RecyclerView.ItemAnimator的一个子类--DefaultItemAnimator实现了上面三种动画,同时DefaultItemAnimatorRecyclerView内部作为默认动画在使用。
  如果你对DefaultItemAnimator的原理感兴趣,推荐去看它的源码。源码只有600多行,非常的简洁。
  简而言之,DefaultItemAnimator的原理主要可以分为两步:

  1. 准备每个动画(每个操作可能会同时进行多个动画),然后放入即将执行的动画数组里面。
  2. 执行所有的动画。

  基于下面的Demo,让我们看看delete的动画是怎么执行的。



  (1 ~ 3步会以不同的顺序或者混合执行)

  1. 准备add动画(那些不可见但是即将出现在屏幕中的item,当然是必须保证有足够的空间)。
  2. 准备delete动画(被点击的item)
  3. 准备move动画(应该被移动到正确位置的items)
  4. 执行所有准备好的动画(这里面有很多的逻辑,比如,move动画执行完成之后,才执行位移动画--可以通过ViewCompat.postOnAnimationDelayed()了解得更多)。

2. PredictiveItemAnimations

  从用户体验来看,PredictiveItemAnimations确实比较简单,但是从实现原理来说的话,我们更应该关心在执行add或者remove操作之后,之前不可见但是此时应该显示的items和这些items执行着的动画。
  我们必须明白的是,从技术上来讲,在RecyclerView中,屏幕中可见的Item才算是实时存在的。因此,在这种情况下,也就是我们前面所说的操作(remove 操作),屏幕下方会有很多还没有移动的items。因为在此之前,他们还不存在。在这种情况下,那些在屏幕下方的items出现在屏幕时,会执行appearance的动画,在DefaultItemAnimator中,就是淡入的动画。
  再或者,我们可以知道的是,每个item从哪里来的。这里需要注意的是,RecyclerView.ItemAnimator只负责,每个ItemView从初始状态到最终状态的动画过程,而每个ItemView的如何布局,则依然还是LinearLayoutManager(或者RecyclerView.LayoutManager)。
  LayoutManager有一个public的方法supportsPredictiveItemAnimations(),这个方法默认返回为false。当LayoutManager的条件允许时,这个方法会返回为true。当屏幕下方的items出现在屏幕时,如果supportsPredictiveItemAnimations()返回为true,此时这些Item执行是位移动画,而不是appearance动画。
  在我们的代码中,我们是通过如下方式来设置是否开启的。

//MainActivity.java
LinearLayoutManager layoutManager = new LinearLayoutManager(this) {
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return cbPredictive.isChecked();
    }
};
rvColors.setLayoutManager(layoutManager);

  效果如下(仔细看,最后一个item是通过位移动画出现在屏幕当中的)


  如果你对supportsPredictiveItemAnimations()方法感兴趣的话,或者你正在设计自己的LayoutManager并且想要知道supportsPredictiveItemAnimations()方法相关细节,最好的方式就是看官方文档:supportsPredictiveItemAnimations()

3. Custom change Item animations

  在DefaultItemAnimator中, change动画是执行cross-fade(同时渐变)动画。基于一个例子,我们实现了一个同时执行很多动画的change动画。


  通过上面的例子,我们可以知道,我们的item将两个itemView的text拧在了一起,并且background的颜色也从一个颜色渐变成了另一个颜色。我们是通过继承DefaultItemAnimator来实现的。

4. Items animation under the hood

  change动画是展现ItemAnimator工作原理的最简单的方式。值得说一句的是,这里这个change动画实际上跟add动画或者remove动画没有很大的区别。
  这里有最终的实现--MyItemAnimator

(1). Notify change

  现在,让我们从头开始来看。我们的list views被展示出来,同时RecyclerView使用自定义的动画--MyItemAnimator为了每个itemView做动画。


  当我们点击一个itemView时,下面的代码会被执行:

//ColorsAdapter.java
public void changeItemAtPosition(int position) {
    colors.set(position, ColorsHelper.getRandomColor());
    notifyItemChanged(position);
}

  从notifyItemChanged方法开始(直接调用notifyItemChanged方法,同时给出点击的position和被点击的item数量。),我们就会通知RecyclerView--item应该被更新了。

(2). Record recent state

  现在,Animators通过调用 recordPreLayoutInformation(RecyclerView.State state, RecyclerView.ViewHolder viewHolder, int changeFlags, List payloads)(通过文档可以了解得更多)记录每个ItemView最近的状态值了。在这个方法里面,我们会访问被更新的View的ViewHolder,同时还会保存一些状态值(尤其是做动画的那些属性)。
  状态值保存在一个返回对象ItemHolderInfo里面的。
  这是我们的实现:

private class ColorTextInfo extends ItemHolderInfo {
    int color;
    String text;
    
    //...
    
}

  默认的ItemHolderInfo保存着ItemView的边界信息,我们在里面额外的加了做动画的background color 和text。
recordPreLayoutInformation()方法会收集显示的Views的信息,虽然有些ItemView没有被更新(在这种情况,ItemViewchangeFlags被设置为ViewHolder.FLAG_BOUND,等于0)。其它的flags表示正在请求某种操作(change动画对应着值为2 - ViewHolder.FLAG_UPDATE)。
  此时效果如下(我怀疑大佬将gif上传成png了):

(3). Bind new view

  此时,我们的Adapter正在请求bind一个新的ViewHolder,作为ItemView最新的数据。

@Override
public void onBindViewHolder(ColorViewHolder holder, int position) {
    int color = colors.get(position);
    holder.itemView.setBackgroundColor(color);
    holder.tvColor.setText("#" + Integer.toHexString(color));
}

  这就意味着,我们的ItemView将会被改变,效果如下:


(4). Record new state

  现在我们回到ItemAnimator。此时就会调用recordPostLayoutInformation(RecyclerView.State state, RecyclerView.ViewHolder viewHolder)方法来记录ItemView最终的状态值(通过文档可以了解得更多)。此时,我们会再一次的访问ItemView的ViewHolder,但是此时的ItemView是新的ItemView,将重要的信息保存在ItemHolderInfo(我们的ColorTextInfo)。

(5). Play (or just prepare animation)

  RecyclerView.ItemAnimator有一系列的方法用于不同的动画操作,前面我们说的animateChange方法就会被调用,同时这里也是准备动画绝佳时机。我们有如下4个参数来帮助我们完成动画的执行(或者准备):

  • RecyclerView.ViewHolder oldHolder
  • RecyclerView.ViewHolder newHolder
  • ItemHolderInfo preInfo
  • ItemHolderInfo postInfo
      oldHoldernewHolder分别表示itemView布局前和布局后。在我们的case下,这俩是同一个对象,因为如下代码:
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
    return true;
}

  这就意味着,对于动画,我们没有必要将ViewHolder分离(因为,我们执行动画是基于ItemHolderInfo
  preInfopostInfo 对象 分别来自于recordPreLayoutInformation()方法和recordPostLayoutInformation()方法。
  正如你所知,我们的View已经bind了最终的状态,此时我们可以调用animateChange方法,ItemView就从初始状态(保存在preInfo)开始,准备动画和可选的执行动画到最终状态。
  为什么强调可选呢?
  animateChange()方法返回的是一个布尔值,其中false表示动画已经被执行了,true表示动画正在准备,保存和等待执行。
  在false的情形下,我们需要记得在执行最后调用dispatchAnimationFinished方法来告诉RecyclerView动画已经结束了。代码如下:

overallAnim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        dispatchAnimationFinished(newHolder);
    }
});

  这就告诉了RecyclerView,此时这个ViewHolder可以被复用了。
  其他情况就是animateChange()方法返回为true。

(6). Play all pending animations

  这次更新,我们同时会执行多个动画(在add或者remove操作中,有位移动画、出现的动画或者消失的动画),在执行animate...()方法时,会通过某一个方式来保存所有即将执行的动画(在DefaultItemAnimator中,使用的ArrayList),然后会调用runPendingAnimations方法来执行所有准备好的动画。
  这里还有其他的操作没有介绍(比如,cancel动画),这里我留给大家,让大家自己去探索。
  整个执行流程以简短的方式来展示,效果如下:

5. Example app and source code

  Chet Haase 和 Yigit Boyar 的视频有实现的代码: RecyclerView Animations and Behind the Scenes,这些不是谷歌官方的,只是比较相似而已。
 本文没有介绍重复点击一个ItemView,会cancel之前的动画,然后执行新的动画这种情况,但是在视频里面有介绍。

6. Source code

  本文完整的代码已经上传到github:https://github.com/frogermcs/RecyclerViewAnimations

7. Author

  Miroslaw Stanek
  移动开发主管:Azimo Money Transfer

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容