Android的动画可以分为三类:
(1)View Animation:最早的Android动画实现,这种动画只能设置给view使用。
(2)Drawable Animation:实现类似幻灯片放映的效果,一张张切换Drawable。
(3)Property Animation:最后出现的动画实现,属性动画,在Android3.0以后才可以使用,这种动画可以设置给任何Object使用,且可以扩展,支持实现任何效果的动画。
让我们一个一个来看
(1)View Animation
Animation的既可以以xml的形式使用,也可以使用Java代码的方式。
现在先让我写一个最简单的透明动画的使用demo:
1 | <?xml version="1.0" encoding="utf-8"?> |
以xml的形式定义了一个简单的动画集合,这个集合里目前只有一个透明渐变的动画。
注意,AnimationSet是Animation的子类,它也是一种动画,只不过和它的名字一样,是一个动画的集合,通过这个类,我们可以把多种简单的动画组合在一起,实现一个更为复杂的动画,我演示的demo里只有一个简单的渐变动画。
xml里的这几个属性值都很容易理解,duration就是动画的总时间,fromAlpha和toAlpha是起始透明度和目标透明度,这个动画会让一个view在200ms的时间内由透明变为不透明。
接下来我们在Activity中使用这个Animation:
1 | package com.zuliangwang.learnanimation; |
代码内容很简单,点击Button就可以让动画作用于textview。
有一点一定要记住,View Animation不能使view的实际位置发生变化,也就是说如果你使用了平移动画,最后你看到的view位置点击是无效的,这是你点击原来的位置才能触发触摸的时间。
View Animation的简单使用到这里结束,下面是第二个动画,Drawable Animation。
(2)Drawable Animation
我查了一些东西,发现这个动画用的是比较少的,而且场景非常单一,它主要用来实现一些代码难以直接实现的逐帧动画,例如京东app上的奔跑的快递员的动画。
官网上给了明确的使用方法,我也写了一个简单的demo:
1 | <?xml version="1.0" encoding="utf-8"?> |
还是使用xml的形式使用,Drawable Animation的xml文件是放在drawable文件夹而不是anim,这点要注意一下。
动画的效果是从第一张图片开始,一直播放到最后一张图片。如果onshot设置为false,则会循环不停播放,如果为true,那么一次之后就会停止。
在Activity里的使用:
1 | package com.zuliangwang.learnanimation; |
Drawable Animation到这里结束。最后是最复杂的属性动画。
(3)Property Animation
使用属性动画前需要先了解一下插值器(Interpolator)的概念,因为插值器是使用属性动画的核心。
插值器,简单的来说就说一个数值控制器,它根据输入的值和预设的计算方法控制输出值,这个输出值最后交给属性动画,从而控制属性动画的变化速率。输入值可以是任何值,最后改变一个对象的某个filed。
插值器本身不参与关于UI的任何行为,它只是一个计算工具,真正去实现动画的还是Property Animation。
The property animation system is a robust framework that allows you to animate almost anything. You can define an animation to change any object property over time, regardless of whether it draws to the screen or not. A property animation changes a property’s (a field in an object) value over a specified length of time. To animate something, you specify the object property that you want to animate, such as an object’s position on the screen, how long you want to animate it for, and what values you want to animate between.
以上说明来自Google的官方文档,可以看到,不管是不是我们可以在屏幕上看到的属性(位置、长度等),都可以使用属性动画来进行修改,这个行为实际上是通过插值器来实现的,但是通常来说,我们使用属性动画的目标就是来修改位置、长度等属性,从而实现动画的效果。
由于属性动画本身非常复杂,使用起来也比较麻烦,但是相比View Animation它能实现更多更好的动画效果,我们跟着官方文档一步步走,把属性动画的使用搞清楚。
The property animation system lets you define the following characteristics of an animation:
- Duration: You can specify the duration of an animation. The default length is 300 ms.
- Time interpolation: You can specify how the values for the property are calculated as a function of the animation’s current elapsed time.
- Repeat count and behavior: You can specify whether or not to have an animation repeat when it reaches the end of a duration and how many times to repeat the animation. You can also specify whether you want the animation to play back in reverse. Setting it to reverse plays the animation forwards then backwards repeatedly, until the number of repeats is reached.
- Animator sets: You can group animations into logical sets that play together or sequentially or after specified delays.
- Frame refresh delay: You can specify how often to refresh frames of your animation. The default is set to refresh every 10 ms, but the speed in which your application can refresh frames is ultimately dependent on how busy the system is overall and how fast the system can service the underlying timer.
属性动画系统允许你定义一下几种动画的特性。
- Duration:你可以指定动画的具体间隔,默认情况下是300ms。
- Time interpolation:你可以指定如何根据属性值的来计算当前动画的运行时间。
- Repeat count and behavior:你可以指定当动画按照duration结束后是否让动画重复。你可以指定是否让动画从后向前播放。设置它来反向播放动画,先正常播放然后从后向前播放,重复这样的操作直到运行到指定次数。
- Animator sets:你可以把多个动画设置成一组放置在set里,它们可以同时播放、按照顺序播放、在指定延迟后播放。
- Frame refresh delay:你可以指定多久刷新一次动画帧。默认情况下每10ms刷新一次,但是实际上刷新速度取决于你的系统整体的忙碌程序和响应时间。这个意思就是说你可以指定刷新频率,但是实际刷新频率最终取决于底层的一些东西,你的频率仅仅是一个参考值。
接下来官方文档说明了属性动画是如何工作的,让我们来看一看。
First, let’s go over how an animation works with a simple example. Figure 1 depicts a hypothetical object that is animated with its
x
property, which represents its horizontal location on a screen. The duration of the animation is set to 40 ms and the distance to travel is 40 pixels. Every 10 ms, which is the default frame refresh rate, the object moves horizontally by 10 pixels. At the end of 40ms, the animation stops, and the object ends at horizontal position 40. This is an example of an animation with linear interpolation, meaning the object moves at a constant speed.
Figure 1. Example of a linear animation
首先,来看一个例子,阶段1描述一个假设对象对它的x属性值进行动画处理,x代表它在屏幕上的水平位置。动画间隔设置为40ms,移动的距离为40px。每隔10ms(默认的帧刷新时间),这个对象在水平方向上移动10px。在40ms结束后,这个动画结束,这个对象在水平方向40px处。这个例子使用了线性插值器,代表着这个对象以常数速度移动。
You can also specify animations to have a non-linear interpolation. Figure 2 illustrates a hypothetical object that accelerates at the beginning of the animation, and decelerates at the end of the animation. The object still moves 40 pixels in 40 ms, but non-linearly. In the beginning, this animation accelerates up to the halfway point then decelerates from the halfway point until the end of the animation. As Figure 2 shows, the distance traveled at the beginning and end of the animation is less than in the middle.
Figure 2. Example of a non-linear animation
这是第二个例子,非线性插值器的使用。
你也可以指定动画使用非线性插值器。在例子2中假设对象在动画开始阶段加速,在结束阶段减速。这个对象仍然移动40px和40ms,但是不是线性的。这个动画在前半段路程中加速,并在后半段路程减速。
这是使用非线性插值器的动画,可以看到动画一定的速度可以不是一个固定值。
Let’s take a detailed look at how the important components of the property animation system would calculate animations like the ones illustrated above. Figure 3 depicts how the main classes work with one another.
Figure 3. How animations are calculated
现在来仔细看一看属性动画系统里的重要组件是怎样计算动画的,图片描述了它的主要的类相互工作的方式。
The ValueAnimator object keeps track of your animation’s timing, such as how long the animation has been running, and the current value of the property that it is animating.
The ValueAnimator encapsulates a TimeInterpolator, which defines animation interpolation, and a TypeEvaluator, which defines how to calculate values for the property being animated. For example, in Figure 2, the TimeInterpolator used would be AccelerateDecelerateInterpolator and the TypeEvaluator would be IntEvaluator.
To start an animation, create a ValueAnimator and give it the starting and ending values for the property that you want to animate, along with the duration of the animation. When you call start() the animation begins. During the whole animation, the ValueAnimator calculates an elapsed fraction between 0 and 1, based on the duration of the animation and how much time has elapsed. The elapsed fraction represents the percentage of time that the animation has completed, 0 meaning 0% and 1 meaning 100%. For example, in Figure 1, the elapsed fraction at t = 10 ms would be .25 because the total duration is t = 40 ms.
When the ValueAnimator is done calculating an elapsed fraction, it calls the TimeInterpolator that is currently set, to calculate an interpolated fraction. An interpolated fraction maps the elapsed fraction to a new fraction that takes into account the time interpolation that is set. For example, in Figure 2, because the animation slowly accelerates, the interpolated fraction, about .15, is less than the elapsed fraction, .25, at t = 10 ms. In Figure 1, the interpolated fraction is always the same as the elapsed fraction.
When the interpolated fraction is calculated, ValueAnimator calls the appropriate TypeEvaluator, to calculate the value of the property that you are animating, based on the interpolated fraction, the starting value, and the ending value of the animation. For example, in Figure 2, the interpolated fraction was .15 at t = 10 ms, so the value for the property at that time would be .15 X (40 - 0), or 6.
The com.example.android.apis.animation package in the API Demos sample project provides many examples on how to use the property animation system.
ValueAnimator对象保持追踪你的动画的timing,例如动画运行了多久,amimating的属性值现在是多少。
ValueAnimator封装了一个TimeInterpolator和一个TypeEvaluator,前者定义了动画插值,后者定义了如何计算animate的属性的值。举例说明,在图2中,TimeInterpolator的实现是AccelerateDecelerateInterpolator,TypeEvaluator的实现是IntEvaluator。
为了开始一个动画,创建一个ValueAnimator并且给它你要animate的属性的初始值和结束值。当你调用start函数时,动画开启,在整个动画期间,基于动画持续时间和已经运行的时间,ValueAnimator从0到1计算运行时的值。这个运行时的值代表动画已经执行的时间所占的总时间百分比。
当ValueAnimator计算完运行时值,它会调用当前设置的TimeInterpolator来计算插值,一个插值映射到运行值作为一个新的值。例如在图2中,因为动画缓慢的加速,在10ms时,插值,大概0.15,小于运行时值0.25。在图1里,插值总是等于运行时值。
当计算插值时,ValueAnimator调用合适的TypeEvaluator。在图2中,插值在10ms时等于0.15,所以那时的属性值为0.15*(40-0),也就是6。
通过这个计算,我们可以发现属性值的计算方法,当前的属性值=插值*(end-begin),这个公式不涉及到运行时值。
绕了这么一大堆,我自己都感觉一头雾水,这些东西需要简明的叙述一遍:
首先,使用属性动画的直接类是ValueAnimator和ObjectAnimator(继承自ValueAnimator)。而它们内部的实现依赖于Interpolator(计算插值)和TypeEvaluator(计算属性值)。Interpolator决定了动画的执行进度,它输出一个0到1之间的小数,比如0.5就代表动画已经执行了一半,而具体到我们使用,我们可以自己实现这个类定义什么是动画的进度、动画的进度怎么计算,比如如果是一个平移,我们以平移对象的移动距离和总距离比来决定当前动画的进度。这个进度值随后会输出给TypeEvaluator,TypeEvaluator根据进度来决定如何计算属性值,这个属性值就是动画的key值,比如一个旋转动画里这个值是旋转的角度,最后这个TypeEvaluator的输出结果就决定了动画的效果。
使用属性动画最重要的就是搞清楚Interpolator和TypeEvaluator到底是干什么的。
接下来我们来自己完成简单的demo。
首先是ValueAnimator:
先来看看最后的效果,绘制一个矩形,并让它从开始位置移动到结束位置。
为了示意自定义,我没有使用sdk提供的Rectangular类,自己定义了一个,很简单。
1 | package com.zuliangwang.learnanimation; |
自定义TypeEvaluator,根据动画进度来决定现在矩形的位置。
1 | package com.zuliangwang.learnanimation; |
最后是自定义这个矩形view:
1 | package com.zuliangwang.learnanimation; |
重点是这里的UpdateListener,这里是实现这个动画的关键。
简要说明一下逻辑:
ValueAimator其实是一个很简单的数据计算工具,它不像ObjectAnimator那样可以直接实现对view的操作,因此实际上我们使用ValueAnimator要自己实现的内容会更多,在这里ValueAnimator每次计算完当前帧的结果就会回调update监听器,我们在监听器里重新设置矩形的坐标值并手动调用
invalidate
重绘矩形。ObjectAnimator相比ValueAnimator其实就是把手动设置Update这一步省略了而已,你只要输入属性值,它会自动去实现类似Update这种逻辑。
我们再来看一个更简单的例子:
1 | package com.zuliangwang.learnanimation; |
这里就是旋转一个TextView,在这里很明显的可以看到ValueAnimator仅仅只是一个计算工具,你可能会问这里为什么没有调用invalidate()
,这是因为sdk里的view和我们自己手动定义的view不同,在这个setRotation方法里它会主动去调用invalidate()
。
接下来来看ObjectAnimator:
1 | package com.zuliangwang.learnanimation; |
看到了没有,这三行代码和上面的ValueAnimator完成的功能是完全相同的。ObjectAnimator做了一个什么工作呢?它根据我们输入的对象和属性名去这个对象里找这个属性,然后每次更新时去更新这个属性值,其实就是上面的那一回事。
需要注意的是ObjectAnimator搜寻更新属性值的方法:非常的简单粗暴,传入对象.setXXX()和传入对象.getXXX(),因此你如果要使用ObjectAnimator,你传入的属性值必须有get和set方法。如果没有,那你只能另想办法了。
Animator还有一个子类AnimatorSet,这个类我不想讲了,如果搞懂了前面的ValueAnimator和ObjectAnimator,那使用这个集合是非常简单的。
动画的分析到这里就结束了,属性动画更深入的用法还是在生产环境上再讨论。
参考网站:
Android官方网站
https://developer.android.com/guide/topics/graphics/view-animation.html
博客: