ViewPropertyAnimator介绍

来源:互联网 发布:淘宝类目分析模板 编辑:程序博客网 时间:2024/06/09 22:44

ViewPropertyAnimator介绍

译自:Introducing ViewPropertyAnimator


这篇博客的作者是 Chet Haase,它是一位专注于图像和动画领域的 Android 工程师。他比较偶然的在他自己的 CodeDependent 博客上(graphics-geek.blogspot.com)发表了与这个课题有关的视频和文章。 —— Tim Bray

在之前的文章(Animation in Honeycomb)中,我谈论了 Android 3.0 中加入的新的属性动画系统。这个新的动画系统使得任何对象做任何类型的属性的动画都变得容易,包括那些在 Android 3.0 中加入 View 中的新属性。在 Android 3.1 中,我们加入了一些工具类使得 View 对象做属性动画更加容易。

首先,如果你还不熟悉新的 View 属性(比如 alphatranslationX),你可以从 前一篇博客 的相应章节获得帮助。现在去读一下吧,没关系,我可以等你。

OK,现在准备好了吗?

温故知新:使用 ObjectAnimator

通过使用 3.0 中引入的 ObjectAnimator,你可以通过几行代码实现 View 的任意一个属性动画。创建一个 Animator,设置任意一个可选的属性以及一些可选参数(比如 durationrepetition 参数),然后调用 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 正在移动,那它的 xy (或者 translationXtranslationY)属性也可能同时在做动画。还有其他的情况使得多个属性动画在同时执行。如果我们知道有多个属性动画在同时执行的画,那么这里就会有相当大的性能提升空间。

Android 运行时对于“属性”并没有概念,所以 ObjectAnimator 通过一种技术来将 表示属性名字的字符串 转换成 对目标对象执行 setter 方法。例如,在 View 类中 alpha 字符换会被转换成对 setAlpha() 方法的引用。这个功能的实现要么通过反射实现,要么通过 JNI;这两者都很可靠,但是都会有一些额外的开销。但是据我们所知,对于一些对象和属性,比如 View 的属性,我们应该可以做到更好。通过了解一些 API 和做动画的属性的相关信息,我们可以直接给相应的属性赋值,从而避免反射和 JNI 带来的额外开销。

另外一部分的额外开销是 Animator 自身。尽管所有的动画都共享同一个计时机制因此不会因为计时而产生额外开销,但是它们是独立对不同的属性执行同样的操作。如果我们提前知道我们正在对多个属性同时做动画的话,我们可以把这些动画结合起来(combine)。在新的动画系统里,其中一个解决方式就是使用 PropertyValueHolder。这个类允许你在同一个 Animator 中对多个属性做动画,这样可以省去很多对每个单独的 Animator 的开销(per-Animator overhead)。但是种方法会需要更多代码,这使得一个简单的操作变得复杂。这里有一个新的解决途径,它允许我们以一个更加简单易读易写的方式来将多个动画结合起来。

最后,View 的每一个属性都会执行一些操作来确保它和它的父容器可以在合适的时候重绘(ensure proper invalidation)。例如,当对一个 Viewx 轴上的平移的时候会 invalidate 它之前所在的位置和它当前所在的位置,以此来告诉它的父容器需要重绘哪些部分。同样的,做 y 轴方向的平移时也会 invalidate 它之前和当前的位置。如果这两个属性同时做动画的话,就会有冗余的操作,因为如果我们知道有多个属性动画正在进行这些 invalidation 可以结合在一起的。ViewPropertyAnimator 就是用来做这件事的。

介绍:ViewPropertyAnimator

ViewPropertyAnimator 提供了一种可以使多个属性同时做动画的简单方法,而且它在内部只使用一个 Animator。当它计算完这些属性的值之后,它直接把那些值赋给目标 Viewinvalidate 那个对象,而它完成这些的方式比普通的 ObjectAnimator 更加高效。

废话少说:让我们看看代码。对于一个我们之前见过的 View 淡出动画,如果使用 ViewPropertyAnimator 的话应该是这样:

myView.animate().alpha(0);

很好,它非常简短而且可读性非常好。而且它非常容易将多个动画结合起来。例如,我们可以同时移动这个 Viewx 值和 y 值到 (500, 500) 这个位置:

myView.animate().x(500).y(500);

在这句代码中有以下几个需要注意的地方:

  • animate():整个系统从调用 View 的这个叫做 animate() 的新方法开始。这个方法会返回一个 ViewPropertyAnimator 对象,你可以通过调用这个对象的方法来设置需要做动画的属性。
  • 自动开始:注意我们没有显式调用过 start() 方法。在新的 API 中,启动动画是隐式的。当你声明完,动画就开始了。同时开始。这里有一个细节,就是这些动画实际上会在下一次界面刷新的时候启动,ViewPropertyAnimator 正是通过这个机制来将所有的动画结合在一起的。如果你继续声明动画,它就会继续将这些动画添加到将在下一帧开始的动画的列表中。而当你声明完毕并结束对 UI 线程的控制之后,事件队列机制开始起作用(kicks in)动画也就开始了。
  • 流畅(Fluent):ViewPropertyAnimator 拥有一个流畅的接口(Fluent interface),它允许你将多个方法调用很自然地串在一起并把一个多属性的动画写成一行代码。所有的调用(比如 x()y())都会返回一个 ViewPropertyAnimator 实例,所以你可以把其他的方法调用串在一起。

性能焦虑(Performance Anxiety)

即使是在一个简单的 alpha 动画上,这种新的解决途径也是性能完胜的。 ViewPropertyAnimator 并不使用反射或者 JNI 技术;例如,alpha() 方法是在每一帧直接改变 View 对象的 alpha 属性(field)值。

ViewPropertyAnimator 的另一个性能上的胜利来自于它可以将多个动画结合起来。为此我们来看另一个例子。

当你在屏幕上移动一个 View 的时候,你可能会同时移动它的 x 位置和 y 位置。例如,下面这个动画移动 myViewx 值和 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 来使所有指定的属性做动画。

一个例子

当我完成这篇文章的时候,阅读了一遍,觉得非常无聊。。。因为,坦白地讲,讲述与视觉效果有关的内容的时候最好有一些东西可以看。而遗憾的是当谈论到动画的时候看截屏并没有什么卵用。(比如:“在这张图中,你可以看到按钮在移动。好吧,你也许没看到,但是我在截屏的时候它的确是在移动的。相信我。”)所以我录制了一段关于一个小示例程序的视频,并附上对视屏中代码的讲解。

下面就是视频,在观看视频之前请确保你打开你的扬声器。这段视频绝对是最精彩的部分。

我看了一下,只是很简单的一个 按钮 在那里做几个动画,想看的话去原文中看吧,记得打开 vpn。

在视频中,左上角的操作按钮(“Fade In”、“Fade out”等等)依次被点击,同时你可以看到这些点击作用在最下面的按钮(动画按钮)上的效果。所有这些动画的效果都需要感谢 ViewPropertyAnimator 这个 API (那是当然的)。下面我将依次讲解一下视频中的每一个动画。

Activity 启动的时候,我将动画的持续事件设置为比默认值长很多,这是为了让你看清楚动画的过程。改变 animatingButton 的动画时长只需要一行代码 —— 获取按钮的 ViewPropertyAnimator 并设置 duration

animatingButton.animate().setDuration(2000);

其他的代码就是为每一个按钮设置 OnClickListener,并在里面指定每个按钮对应的动画。下面我会把第一个监听器的所有代码贴出来,对于其他的监听器我将只是贴出 onClick 方法内部的代码。

视频中的第一个动画是当 “Fade Out” 按钮被点击的时候开始的,它使得 “Animating Button”(正如你猜到的)做淡出动画。下面就是执行这个操作的 fadeOut 按钮的监听器:

fadeOut.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        animatingButton.animate().alpha(0);    }});

你可以看到,在这段代码中我们只是简单地告诉这个对象做动画到 alpha 为0。它将从当前的 alpha 值开始做动画。

下一个按钮执行淡入操作,使按钮的 alpha 值回到1(完全不透明)。

animatingButton.animate().alpha(1);

“Move Over” 按钮和 “Move Back” 按钮对两个属性同时执行动画:xy。这是通过在 Animator 中连续调用两个属性的方法来实现的。对于 “Move Over” 按钮,是这样做的:

int xValue = container.getWidth() - animatingButton.getWidth();int yValue = container.getHeight() - animatingButton.getHeight();animatingButton.animate().x(xValue).y(yValue);

而对于 “Move Back” 按钮(我们只是想将按钮放回到它在容器中原来的位置 (0, 0)),代码如下:

animatingButton.animate().x(0).y(0);

视频中需要注意的一个细节是,当 “Move Over” 和 “Move Back” 动画做完之后,我又点击了它们一次,这一次我在 “Move Over” 动画正在执行的过程中点击 “Move Back” 按钮。”Move Back” 动画和 “Move Over” 动画作用的属性相同(xy),这导致第一个动画被取消,而第二个动画从当前的位置开始执行。这是 ViewPropertyAnimator 的功能的内部实现的一部分(an internal part of functionality)。它根据你的指令来对属性做动画,并且,如果有必要的话,会在执行动画之前取消任何那个属性正在做的动画。

最后,我们拥有 3D 旋转效果,按钮围绕 y 轴做了两次旋转。这明显是一个更加复杂的动画,所以它需要比其他动画多更多的代码(或许并没有):

animatingButton.animate().rotationYBy(720);

视频中关于旋转动画的一个非常值得注意的一点是,它是和 “Move” 动画的一部分一起执行的。也就是说,我先点击了 “Move Over” 按钮,然后点击 “Rotate” 按钮。这样做的结果是,按钮先是开始移动,然后在它移动的过程中开始旋转。由于每个动画持续两秒,旋转动画是在移动动画结束之后才结束的。同样的事情发生在按钮返回原位置的动画中 —— 当按钮回到 (0, 0) 位置之后,旋转还在继续。这展示了独立的动画(这些动画没有被安排到同一组(not grouped together))是如何在一个 ObjectAnimator 内部完全分离开,独立地、并行地执行的。

在看一下这个示例,把代码下载下来,and groove to the awesome soundtrack for 16.75(这句真的不会翻译)。如果你想要完整的代码(它真的只是五个 OnClick 监听器里面包含了上面的动画代码)的话,你可以在这里下载。

那么

作为一个关于 ViewPropertyAnimator 的完整故事,你现在可能会想去查阅 SDK 文档。首先,View 类有一个 animate() 方法。第二,有一个 ViewPropertyAnimator 类。我已经在这篇文章中提到了这个类的基本方法,但是这个类还有更多的方法,这些方法大多是为了对 View 的各种属性做动画的。第三,有… 呃,没有了。只是 View 类的 animate() 方法和 ViewPropertyAnimator 类本身。

ViewPropertyAnimator 类并不是 3.0 中添加的属性动画 API 的替代者。怎么可能是呢,我们才把他们加进去!实际上,3.0 中添加的那些动画能力(animation capabilities)为 ViewPropertyAnimator 以及系统中其他的动画能力提供了重要的基础支持(provide important plumbing)。而 ObjectAnimator 的那些能力提供了一个非常灵活易用的动画基础设施(facility),几乎是为任何东西提供的( well, just about anything)! 但是如果你想更容易地对 View 的任何标准的属性做动画,那么这个能力有限的 ViewPropertyAnimator API 符合你的需求,否则你需要考虑一下(是否使用它)。

注意:我并不希望让你过于担心 ObjectAnimator 的额外开销;反射、JNI或者 Animator 过程中的其他额外开销相对于程序中执行的其他代码都是非常小的。只是说当你在某些情况下需要做大量的 View 属性动画的时候,ViewPropertyAnimator 会带来性能上的优势。 但是对我而言,新的 API 中最好的部分是你自己写的部分。这才是最好的 API :简洁的、易读的。希望你能同意这句好并在你需要的时候使用 ViewPropertyAnimator 来做 View 的属性动画。

0 0