ViewPropertyAnimator属性动画
使用 ObjectAnimator
通过使用 3.0 中引入的 ObjectAnimator,你可以通过几行代码实现 View 的任意一个属性动画。创建一个 Animator,设置任意一个可选的属性以及一些可选参数(比如 duration 和 repetition 参数),然后调用 start() 方法。例如,想要让一个叫做 myView 的对象做淡出动画,你可以这样做:
ObjectAnimator.ofFloat(myView, "alpha", 0f).start();
这显然一点都不难,编写代码不难,理解起来也不难。你通过想要做动画的对象的信息、想要做动画的属性的名字以及动画的属性结束值。非常简单。
但是在我们看来它还可以被优化。尤其是,既然 View 的这些属性会非常频繁的被调用做动画,那么我们可以引入一些新的 API 使得让这些属性做动画更加的简单和易读(readable)。与此同时,我们还想提高这些属性做动画的性能特征。第二个原因值得被解释一下,下一段中的内容全都是关于它的。
在 3.0 的动画系统中,关于 View 的属性动画,有三个方面的性能值得提升。其中一个是,我们在一个没有固定的 “属性” 概念的语言中做属性动画。(One of the elements concerns the mechanism by which we animate properties in a language that has no inherent concept of “properties”.)另一个性能问题是关于同时对多个属性做动画的。当做淡出动画的时候,你只需要对 alpha 属性做动画。但是如果这个 View 正在移动,那它的 x 和 y (或者 translationX 和 translationY)属性也可能同时在做动画。还有其他的情况使得多个属性动画在同时执行。如果我们知道有多个属性动画在同时执行的画,那么这里就会有相当大的性能提升空间。
Android 运行时对于“属性”并没有概念,所以 ObjectAnimator 通过一种技术来将 表示属性名字的字符串 转换成 对目标对象执行 setter 方法。例如,在 View 类中 alpha 字符换会被转换成对 setAlpha() 方法的引用。这个功能的实现要么通过反射实现,要么通过 JNI;这两者都很可靠,但是都会有一些额外的开销。但是据我们所知,对于一些对象和属性,比如 View 的属性,我们应该可以做到更好。通过了解一些 API 和做动画的属性的相关信息,我们可以直接给相应的属性赋值,从而避免反射和 JNI 带来的额外开销。
另外一部分的额外开销是 Animator 自身。尽管所有的动画都共享同一个计时机制因此不会因为计时而产生额外开销,但是它们是独立对不同的属性执行同样的操作。如果我们提前知道我们正在对多个属性同时做动画的话,我们可以把这些动画结合起来(combine)。在新的动画系统里,其中一个解决方式就是使用 PropertyValueHolder。这个类允许你在同一个 Animator 中对多个属性做动画,这样可以省去很多对每个单独的 Animator 的开销(per-Animator overhead)。但是种方法会需要更多代码,这使得一个简单的操作变得复杂。这里有一个新的解决途径,它允许我们以一个更加简单易读易写的方式来将多个动画结合起来。
最后,View 的每一个属性都会执行一些操作来确保它和它的父容器可以在合适的时候重绘(ensure proper invalidation)。例如,当对一个 View 做 x 轴上的平移的时候会 invalidate 它之前所在的位置和它当前所在的位置,以此来告诉它的父容器需要重绘哪些部分。同样的,做 y 轴方向的平移时也会 invalidate 它之前和当前的位置。如果这两个属性同时做动画的话,就会有冗余的操作,因为如果我们知道有多个属性动画正在进行这些 invalidation 可以结合在一起的。ViewPropertyAnimator 就是用来做这件事的。
介绍:ViewPropertyAnimator
ViewPropertyAnimator 提供了一种可以使多个属性同时做动画的简单方法,而且它在内部只使用一个 Animator。当它计算完这些属性的值之后,它直接把那些值赋给目标 View 并 invalidate 那个对象,而它完成这些的方式比普通的 ObjectAnimator 更加高效。
废话少说:让我们看看代码。对于一个我们之前见过的 View 淡出动画,如果使用 ViewPropertyAnimator 的话应该是这样:
myView.animate().alpha(0);
很好,它非常简短而且可读性非常好。而且它非常容易将多个动画结合起来。例如,我们可以同时移动这个 View 的 x 值和 y 值到 (500, 500) 这个位置:
myView.animate().x(500).y(500);
在这句代码中有以下几个需要注意的地方:
1、animate():整个系统从调用 View 的这个叫做 animate() 的新方法开始。这个方法会返回一个 ViewPropertyAnimator 对象,你可以通过调用这个对象的方法来设置需要做动画的属性。
2、自动开始:注意我们没有显式调用过 start() 方法。在新的 API 中,启动动画是隐式的。当你声明完,动画就开始了。同时开始。这里有一个细节,就是这些动画实际上会在下一次界面刷新的时候启动,ViewPropertyAnimator 正是通过这个机制来将所有的动画结合在一起的。如果你继续声明动画,它就会继续将这些动画添加到将在下一帧开始的动画的列表中。而当你声明完毕并结束对 UI 线程的控制之后,事件队列机制开始起作用(kicks in)动画也就开始了。
3、流畅(Fluent):ViewPropertyAnimator 拥有一个流畅的接口(Fluent interface),它允许你将多个方法调用很自然地串在一起并把一个多属性的动画写成一行代码。所有的调用(比如 x()、y())都会返回一个 ViewPropertyAnimator 实例,所以你可以把其他的方法调用串在一起。
即使是在一个简单的 alpha 动画上,这种新的解决途径也是性能完胜的。 ViewPropertyAnimator 并不使用反射或者 JNI 技术;例如,alpha() 方法是在每一帧直接改变 View 对象的 alpha 属性(field)值。
ViewPropertyAnimator 的另一个性能上的胜利来自于它可以将多个动画结合起来。为此我们来看另一个例子。
当你在屏幕上移动一个 View 的时候,你可能会同时移动它的 x 位置和 y 位置。例如,下面这个动画移动 myView 的 x 值和 y 值到50和100:
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
上面这段代码创建了两个动画,并通过 AnimatorSet 来是他们同时播放。这种方式在处理上存在着额外的开销,因为它需要额外创建一个 AnimatorSet 并且在同时执行两个动画。这里有另一种方法,使用 PropertyValuesHolder 来把多个属性的动画全部放到一个 Animator 中:
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
这种解决方案避免了多个 Animator 的额外开销,在 ViewPropertyAnimator 之前它是一个正确的方法。并且代码看起来不是太糟。但是使用 ViewPropertyAnimator 的话,它会更加简单:
myView.animate().x(50f).y(100f);
再次重复一次,这段代码更加简单也更加易读。而且它和上面使用 PropertyValuesHolder 的例子一样具有单一 Animator 的优点,因为 ViewPropertyAnimator 在内部只运行一个 Animator 来使所有指定的属性做动画。