更流畅的动画框架-Additive Animation

泡在网上的日子 / 文 发表于2017-08-06 10:04 次阅读 动画

原文:Bringing smooth animation transitions to Android 

在说明什么是additive animation之前,先用一个简单的演示来解释为什么要使用它。

在这个sample app(从这里偷来的) 中,用户触摸划动屏幕,黄色方块视图不断的随着手指在做移动动画。

注:基于新坐标与旧坐标之间的动画,而不是直接点哪到哪。

这里是使用标准Android动画系统的普通版本:

1-bawqTp_FzU-7uw5tcN4yEA.gif

可以看到速度是被打断了的,当手指横跨整个屏幕的时候,view甚至不动-动画还没有运行就已经重新开始了。同样的交互使用additive animation是这样的:

1-Zi7Sgn6p73fHPbOI7eBKnQ.gif

Additive Animations

在我们的室内定位应用中,我们定期计算用户当前的位置(类似谷歌地图指示你位置的蓝色圆点),每当新位置计算出来的时候,我们用动画过渡到新的点,在Android中,这样会取消当前动画开始一个新的动画,从而导致不连贯的移动。iOS上实现这个功能一点问题都没有(因为iOS8之后每个基于 UIView的动画都是可以叠加执行的),在研究如何解决这个不连贯的问题时,我找到了一个正好与此有关的话题:

Additive animations: animateWithDuration in iOS 8
There's new behavior involving animateWithDuration: in iOS 8 that can help make certain "interruptible" animations a…iosoteric.com

The API

虽然Google不久才发布的 physics-based animation API 可以实现类似的效果,但是他们的API需要对代码大幅改动,而且大多数情况下使用起来都笨重,不直观。

相反,让我们看一看前一个例子中的代码:

AdditiveAnimator.animate(view).setDuration(1000)
                .x(touch.getX())
                .y(touch.getY())
                .start();

就是如此简单,如果觉得很熟悉,那是因为我有意让它接近ViewPropertyAnimator 的API,尽可能的让过渡更简单。

动画支持多种属性:

  • ViewPropertyAnimator 支持的所有属性( x, y, z, translationX, translationY, translationZ, rotation, alpha 等)

  • Margins, padding, size, elevation 以及具有恰当布局属性的view的滚动位置

  • 以ColorDrawable为背景的view的背景颜色

  • 几乎所有属性的Delta值 (ViewPropertyAnimator的*By() 方法).

  • 按照路径移动view

一个更复杂的例子

这里是一个稍微复杂的动画,涉及多个view,硬件层,延迟:

这是创建这个动画所需的代码:

AdditiveAnimator anim = new AdditiveAnimator().withLayer();
for(View v : views) {
    anim = anim.target(v).x(x).y(y).rotation(r).thenWithDelay(50);
}
anim.start();

这里发生了很多事情,让我们逐一分析:

withLayer() 为依次添加到动画的所有view启用了硬件动画。这个例子中的所有view都使用了小于1的透明度,以证明性能没有受到影响。得益于withLayer()以及AdditiveAnimator的高度优化,即使上百个动画同时运行也能如丝般顺滑。上面的例子中有800到1200个动画同时运行(可以秒杀大多数app了)。

target(View v)改变该动画作用的对象。对多个view使用动画无需使用AnimatorSet。

x(int x), y(int y), rotation(float degrees)为创建动画的方法,跟ViewPropertyAnimator的同类方法一样的作用。

最后一个方法- thenWithDelay(int millis)是AdditiveAnimator上我最喜欢的功能之一,值得单独讲解。

动画链

AnimatorSet最常见的使用场景之一是按序播放动画(除此之外还有同时播放动画)。

大体上,你需要这样做(感谢StackOverflow):

Animator translationAnimator = ObjectAnimator
        .ofFloat(view, View.TRANSLATION_Y, 0f, -100f)
        .setDuration(700);

final Animator alphaAnimator = ObjectAnimator
        .ofFloat(view, View.ALPHA, 1f, 0f)
        .setDuration(300);

final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(
        translationAnimator,
        alphaAnimator
);
animatorSet.start();

同样的动画,使用AdditiveAnimator来创建是这样的:

AdditiveAnimator.animate(view).setDuration(700)
                .translationY(-100)
                .then()
                .setDuration(300)
                .alpha(0f)
                .start();

这里面的 then() 方法创建了一个新的AdditiveAnimator,与前面的那个是父-子关系,它拷贝了其所有属性(比如target,duration等),并且在它的内部把startOffset设置为前一个动画的duration(这里是700毫秒)。新的animator也可以根据需要配置和修改。

then()还有更多的功能,它们都比标准的动画API要方便很多:

  • thenAfterDelay(int millis) 上一个动画开始之后等待millis毫秒开始执行下一个动画。

  • thenBeforeEnd(int millis) 前一个动画结束之前多长时间开始。

  • thenAfterEnd(int millis) 前一个动画结束之后多久开始。

这里是这些方法在使用相同millis值时的效果比较:

1-wI9MVQ0XVz3F9MHQomkXVw.gif

thenAfterDelay(400),

1-9PooPiifOITecymxcpsWcQ.gif

 thenBeforeEnd(400) 

1-DMXdaVeQkqcSnt8jG_XzlQ.gif

thenAfterEnd(400)

考虑到在一篇文章中介绍完内容太多,这只列出最主要功能得纲要。

AdditiveAnimator…

  • 可以通过继承来实现扩展。为子类暴露了一些必要的方法,如用于组合或者动画的方法。
    假设你在你的MyAnimator定义了一个 bounceTwice() :

new MyAnimator(view).x(100).then().bounceTwice().start();
  • 也可以不使用继承来动画自定义属性。通过为属性值定义一个getter 和 setter ,并提供自定义的TypeEvaluators来实现。下面是使用这它们为TextView的文字颜色应用动画的例子:

TextView animatedView = ...;
FloatProperty textColorProperty = new FloatProperty("textColor") {
    public Float get(View object) { 
        return Float.valueOf(textView.getCurrentTextColor()); 
    }
    public void set(View object, Float value) {
        textView.setTextColor(value.intValue()); 
    }
}
AdditiveAnimator.animate(textView)
                .property(Color.RED, // target value
                          new ColorEvaluator(), // TypeEvaluator
                          textColorProperty)
                .start();
  • 无需使用AnimatorSet或者其它的封装就能实现多view同时动画

  • 可以沿路径动画-因为所有动画都是可以叠加的,你还可以同时沿几条路径动画,制造一个这样的动画。

  • 支持语法优雅的链式动画(then(), thenAfterDelay(int millis)等)。

  • 允许中途切换插值器(TimeInterpolator),比如当你想对位置执行弹簧动画,却不想让弹跳影响到透明度值的时候,这个特性就非常有用。

AdditiveAnimator.animate(view)
                .setInterpolator(new BounceInterpolator())
                .x(100).y(200)
                .switchInterpolator(new LinearInterpolator())
                .alpha(0)
                .start();
  • automatically handles shortest-distance rotation computation for you (never worry about computing the shortest angle again).

  • 支持硬件层动画,使用熟悉的 withLayer() 语法。

  • 包含直观的标准API,比如动画的取消(所有或者特定的动画),添加多个 start/end/update listener,设置重复模式/次数,获得当前动画作用的对象(或者还没有开始的队列中的动画对象)。

  • 如果没有指定值的话,将使用全局的默认值(比如TimeInterpolator 或者 animation duration)。

要在一篇文章中介绍完这些特性内容就长了,所以详情还是看demo源码。

获得library

该库支持API Level 14+ (Android 4.0+)。库源码以及demo演示的源码见GitHub,如果要在项目中引用此库,在 build.gradle 文件中添加:

dependencies {
    compile 'at.wirecube:additive_animations:1.3'
}
...
repositories {
    jcenter()
}

如果你有什么更好的建议,欢迎在github留言!

收藏 赞 (0) 踩 (0)
上一篇:原型模式-下拉刷新的全局设置
本文出自Zone的博客,如需转载请标明出处,尊重原创谢谢 博客地址: https://luhaoaimama1.github.io/2017/04/04/Refresh/ 目的 全局替换刷新头部 头部可拔插 :需要应用策略模式(组合与接口的方式set get); 全局替换 :就是原型模式 设置一个全局头部,那么每次
下一篇:RecyclerView 与 LayoutAnimation 实现的进入动画(一 ): List
介绍 本教程我们将带大家学习一个为RecyclerView添加初始动画的简单方法。实现这种动画有几种办法,比如: 实现一个自定义的ItemAnimator 在Adapter的onBindViewHolder()方法中添加动画 我将采用第三种办法:LayoutAnimation。它非常简单,而且只需很少的代