美文网首页程序员Android技术知识Android开发经验谈
Android属性动画基础:你是否真的了解插值器(TimeInt

Android属性动画基础:你是否真的了解插值器(TimeInt

作者: 乱世白衣 | 来源:发表于2018-05-31 20:04 被阅读62次

  插值器和估值器是我们可以改变动画更新值的两个切入点,通过自定义插值器和估值器,我们可以随意改变动画更新时值的计算方式以满足我们特定的需求。本文简单介绍属性动画插值器(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如下


相关文章

  • Android属性动画基础:你是否真的了解插值器(TimeInt

      插值器和估值器是我们可以改变动画更新值的两个切入点,通过自定义插值器和估值器,我们可以随意改变动画更新时值的计...

  • Android学习感悟之属性动画

    本篇包括Android属性动画的基本使用,理解插值器和估值器,自定义属性动画 简介 属性动画是Android3.0...

  • Android基础(7)动画

    1)Android属性动画特性2)插值器与估值器3)Android动画框架实现原理。Lottie 动画使用建议: ...

  • Android-动画估值器(TypeEvaluator)

    之前有对Android的View动画、帧动画、属性动画、动画插值器输出文章。这篇文章就来聊聊动画估值器,希望对看文...

  • 自定义属性动画框架

    通过本篇文章,你将会了解 安卓属性动画的基本架构 插值器和估值器在动画中的作用 手撸属性动画 设想一下,如果你是g...

  • Android应用开发三部曲 --- 动画

    目录 1、前言2、属性动画3、插值器与估值器4、其它动画方式5、源码走读 前言 android中有三种类型的动画,...

  • Android 动画

    动画类型 视图动画(补间动画、逐帧动画)属性动画 补间动画 逐帧动画 属性动画 对比 插值器:确定属性值从初始值过...

  • 【Android 动画】动画详解之属性动画(五)

    在前几篇中,我们了解了补间动画、插值器和属性动画中的ValueAnimator,这一篇,我们来了解下属性动画中的O...

  • Android动画(一)之插值器

    Android中的插值器有很多,下面分别讲解这些插值器到底有什么功能,根据功能来选择比较适合你的动画插值器。 ...

  • Android 动画 - 插值器

    系列文章传送门: Android 动画 - 帧动画 & 补间动画Android 动画 - 插值器 基本使用 在之前...

网友评论

    本文标题:Android属性动画基础:你是否真的了解插值器(TimeInt

    本文链接:https://www.haomeiwen.com/subject/bmsidftx.html