插值器和估值器是我们可以改变动画更新值的两个切入点,通过自定义插值器和估值器,我们可以随意改变动画更新时值的计算方式以满足我们特定的需求。本文简单介绍属性动画插值器(TimeInterpolator)。在读此文前,如果您还不了解属性动画执行流程,建议您先看一下这篇文章,简单了解一下:Android属性动画基础之流程解析
首先,看一下TimeInterpolator源码:
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
通过接口描述,可以知道,通过插值器可以更改动画的变化速率,其实类似于视频播放,我们可以快放也可以慢放,只不过快放和慢放的速率都是线性的。随着执行时间的流逝,动画不断进行更新,即根据动画执行的时间来更新其所操纵的数值或对象,getInterpolation(float input)方法中的input参数就是与动画当前执行周期内执行时间相关的归一化变量,取值范围[0,1],这点在上一篇文章Android属性动画基础之流程解析有所提及,只是碍于篇幅没有详细介绍,这篇文章会对其做较为详尽的解析。getInterpolation(float input)方法所计算出的数值会直接作为时间因子参与动画更新计算。我们先看一下方法api对input的描述,翻译过来大概是:"input参数取值范围[0,1],表示动画当前所处的节点,0代表动画开始,1代表动画结束"。但是,可但是,但可是,这个描述其实是不严谨的,稍后我们分析input参数的数值计算方式就会知道为何这个描述是不严谨的。
为了搞清楚上述input参数的计算方式,我们需要知道getInterpolation方法何时被触发,不用想,肯定是计算更新之前被触发的,这简直是废话,其实我们首先需要了解属性动画执行流程(请参考Android属性动画基础之流程解析),这里不做过多阐述,直接看相关代码(如对属性动画流程有疑问,:
1 boolean animateBasedOnTime(long currentTime) {
2 boolean done = false;
3 if (mRunning) {
4 final long scaledDuration = getScaledDuration();
5 final float fraction = scaledDuration > 0 ?
6 (float)(currentTime - mStartTime) / scaledDuration : 1f;
7 final float lastFraction = mOverallFraction;
8 final boolean newIteration = (int) fraction > (int) lastFraction;
9 final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
10 (mRepeatCount != INFINITE);
11 if (scaledDuration == 0) {
12 // 0 duration animator, ignore the repeat count and skip to the end
13 done = true;
14 } else if (newIteration && !lastIterationFinished) {
15 // Time to repeat
16 if (mListeners != null) {
17 int numListeners = mListeners.size();
18 for (int i = 0; i < numListeners; ++i) {
19 mListeners.get(i).onAnimationRepeat(this);
20 }
21 }
22 } else if (lastIterationFinished) {
23 done = true;
24 }
25 mOverallFraction = clampFraction(fraction);
26 float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
27 animateValue(currentIterationFraction);
28 }
29 return done;
30 }
31 private float clampFraction(float fraction) {
32 if (fraction < 0) {
33 fraction = 0;
34 } else if (mRepeatCount != INFINITE) {
35 fraction = Math.min(fraction, mRepeatCount + 1);
36 }
37 return fraction;
38 }
/**
* Calculates the fraction of the current iteration, taking into account whether the animation
* should be played backwards. E.g. When the animation is played backwards in an iteration,
* the fraction for that iteration will go from 1f to 0f.
*/
39 private float getCurrentIterationFraction(float fraction) {
40 fraction = clampFraction(fraction);
41 int iteration = getCurrentIteration(fraction);
42 float currentFraction = fraction - iteration;
43 return shouldPlayBackward(iteration) ? 1f - currentFraction : currentFraction;
44 }
/**
* Calculates the direction of animation playing (i.e. forward or backward), based on 1)
* whether the entire animation is being reversed, 2) repeat mode applied to the current
* iteration.
*/
45 private boolean shouldPlayBackward(int iteration) {
46 // 注意此处条件判断
47 if (iteration > 0 && mRepeatMode == REVERSE &&(iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
48 // if we were seeked to some other iteration in a reversing animator,
49 // figure out the correct direction to start playing based on the iteration
50 if (mReversing) {
51 return (iteration % 2) == 0;
52 } else {
53 return (iteration % 2) != 0;
54 }
55 } else {
56 return mReversing;
57 }
58 }
/**
* This method is called with the elapsed fraction of the animation during every
* animation frame. This function turns the elapsed fraction into an interpolated fraction
* and then into an animated value (from the evaluator. The function is called mostly during
* animation updates, but it is also called when the <code>end()</code>
* function is called, to set the final value on the property.
*
* <p>Overrides of this method must call the superclass to perform the calculation
* of the animated value.</p>
*
* @param fraction The elapsed fraction of the animation.
*/
@CallSuper
// 动画更新计算方法
59 void animateValue(float fraction) {
60 fraction = mInterpolator.getInterpolation(fraction);
61 mCurrentFraction = fraction;
62 int numValues = mValues.length;
63 for (int i = 0; i < numValues; ++i) {
// 动画更新计算
64 mValues[i].calculateValue(fraction);
65 }
66 if (mUpdateListeners != null) {
67 int numListeners = mUpdateListeners.size();
68 for (int i = 0; i < numListeners; ++i) {
69 mUpdateListeners.get(i).onAnimationUpdate(this);
70 }
71 }
72 }
先看一下animateBasedOnTime(long currentTime)方法第26行,再结合动画计算方法animateValue(float fraction),可以知道,插值器getInterpolation(float fraction)方法接收参数就是第26行计算出来的currentIterationFraction ,下面我们就看看该值是如何计算的。根据源码,很明显我们需要先了解第25行的mOverallFraction和fraction,这俩货在Android属性动画基础之流程解析中真的有做过说明,这里再简单说一下。fraction是当前时间currentTime与动画开始时间mStartTime的差值与动画后期的比值,不考虑边界条件的话,其实就是动画执行的整体时间进度(可能大于1哦,因为您可能会重复执行动画)。那么mOverallFraction是啥呢,它是根据fraction做边界处理之后得到的值,也就是考虑边界条件后的动画整体执行时间进度,假设您设置动画重复执行的次数为n,那么mOverallFraction的最大值为n+1。
接下来就要看第26行了,mOverallFraction作为参数传入getCurrentIterationFraction(float fraction)方法得到currentIterationFraction,currentIterationFraction又作参数传入插值器getInterpolation(float input)方法,看看getCurrentIterationFraction(float fraction)方法。定位到第41行,首先根据整体执行进度计算出动画的迭代次数(已重复执行的次数)iteration,第42行,整体进度减掉已重复执行次数得到当前执行周期内的时间进度currentFraction(其实就是周期归一化而已,取值范围[0,1]),如果您按照插值器getInterpolation(float input)方法api的描述来理解,那么currentFraction就应该是input参数的接收值,然而并不一定是~,因为input参数接收的值是第43行计算出来的,没办法,看一下shouldPlayBackward(int iteration)方法吧。
先看第47至第54行代码,只解析方法内使用的参数的意义,第47行的条件判断条件为真时,要求迭代次数即已重复执行的次数大于0;mRepeatMode(控制动画第偶数次执行方式:倒序执行或正序执行,就像影片播放一样,是从头至尾还是从尾至头)为REVERSE;已迭代次数小于等于目标重复次数。mReversing也是一个控制上述"影片"播放顺序的东东,它控制的是当前动画是否要在原来执行顺序的基础上做翻转,该参数可通过reverse()方法更改。通过第43行代码及shouldPlayBackward(int iteration)方法源码,可以很明确的将,插值器getInterpolation(float input)方法所接收的参数值未必是当前动画执行周期内真正的时间进度,当您需要倒序执行动画的时候,input = 1-currentFraction = 1 * (1-currentFraction),正序执行时input = currentFraction。但是,该值的的确确是应该参与动画更新计算的时间因子,这点并没有问题。我们可以以影片播放来举例说明,假设影片片长10s,共100帧,设播放的时间为t,那么正序播放的情况下,应该播放第(int) (10 * t)帧,倒序播放的话应该播放 (int) (100 - 10 * t = 10 * (10 - t)),那么现在你再看看,10 * (10 - t) 与上述1 * (1-currentFraction)有什么区别?其实没区别,非要说有区别,也就是周期是否归一化而已,因为这里的10就是影片周期。
综上所述,传入插值器getInterpolation(float input)方法中的参数值,就是原本应该参与动画更新计算的时间因子,但是就像影片播放一样,我们想要快放或者慢放,怎么办?很明显,将原本的时间因子"篡改一下"就好了,这就是getInterpolation(float input)所做的事情,该方法根据原本真实的时间因子,计算出一个新的时间因子,然后传入animateValue(float fraction)方法参与最终的计算(见第27行)。比影片快放慢放更强大的是,我们可以随意"篡改",非线性的都可以。
到此为止,我们应该已经了解插值器的用途,下一篇文章将会介绍估值器(TypeEvaluator)
简单示例gif如下