1、属性动画

属性动画通过改变对象的属性来展示的动画效果,补间动画只是设置当前View在区域内移动,产生的动画效果,其实原View的还在原地,没有发生改变。
但属性动画改变了对象的属性。也就是改变了对象的颜色,位置,宽高等。

2、示例

public class MainActivity extends AppCompatActivity {
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageView);
        ObjectAnimator translationX = new ObjectAnimator().ofFloat(imageView,"translationX",0,600f);
        ObjectAnimator translationY = new ObjectAnimator().ofFloat(imageView,"translationY",0,0);

        AnimatorSet animatorSet = new AnimatorSet();  //组合动画
        animatorSet.setInterpolator(new LinearInterpolator()); //设置时间插值器
        animatorSet.playTogether(translationX,translationY); //设置动画
        animatorSet.setDuration(3000);  //设置动画时间
        animatorSet.start(); //启动

    }
}

3、插值器和估值器

1)下图描述了一个对象,该对象的X属性,表示屏幕上的水平位置。动画的持续时间设置为40毫秒,移动的距离为40像素。每10毫秒,该对象是默认帧刷新率,对象水平移动10像素。在40ms结束时,动画停止,对象在水平位置40结束。这是一个线性插值器动画的例子,以恒定速度移动。

Android中正确的取消属性动画_属性动画

2)还可以指定动画以进行非线性插值。图2示出了在动画开始时加速的对象,并在动画结束时减速。对象仍然在40毫秒内移动40个像素,但非线性。开始时,动画加速,然后减速直到动画结束。如图2所示,动画的开始和结束的距离小于中间的距离。

Android中正确的取消属性动画_Android中正确的取消属性动画_02

4、属性动画

属性动画的重要组件

Android中正确的取消属性动画_属性值_03

1)ValueAnimator追踪对象,比如对象的运行时间,当前的属性值。
2)ValueAnimator封装了TimeInterpolator,它定义了动画的插值算法,而且定义了一个TypeEvaluator,它计算了如何去计算属性动画。
3)要开始一个动画,首先需创建一个ValueAnimator并且要定义动画的属性的开始和结束的值,还有动画持续的时间。当调用start() 时,动画就开始了。在动画进行时,ValueAnimator 跟据动画的持续时间和已经过的时间,计算出一个百分比(0和1之间),进度分数代表了动画已进行的时间的百分比,0代表0%,1代表100%。例如,图1中进度分数 在t = 10 ms时值为0.25,因为总时间是t = 40 ms。
4)当ValueAnimator 计算完成动画的进度时,调用TimeInterpolator 去计算一个插值分数。插入分数结合所设置的时间插值把进度分数映射到一个新的分数。例如,在图2中,因为动画缓慢加速,在 t = 10 ms时,插值分数为0.15,小于进度分数为0.25。在图1中,插值分数进度分数永远相等。
5)计算插值函数时,ValueAnimator 会调用适当的TypeEvaluator来基于插值函数、开始值、结束值计算你在动画的属性的值。例如,在图2中,插值函数值在 t = 10 ms时为0.15 ,所以些时属性的值是6。

5、属性动画原理

1、start()方法动画启动

@Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

如果有和当前执行一样的动画,则取消。然后就是调用super.start()方法。
2、start()方法

/**
     * Start the animation playing. This version of start() takes a boolean flag that indicates
     * whether the animation should play in reverse. The flag is usually false, but may be set
     * to true if called from the reverse() method.
     *
     * <p>The animation started by calling this method will be run on the thread that called
     * this method. This thread should have a Looper on it (a runtime exception will be thrown if
     * this is not the case). Also, if the animation will animate
     * properties of objects in the view hierarchy, then the calling thread should be the UI
     * thread for that view hierarchy.</p>
     *
     * @param playBackwards Whether the ValueAnimator should start playing in reverse.
     */
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                // Calculate the fraction of the current iteration.
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        // Resets mLastFrameTime when start() is called, so that if the animation was running,
        // calling start() would put the animation in the
        // started-but-not-yet-reached-the-first-frame phase.
        mLastFrameTime = 0;
        AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

        if (mStartDelay == 0 || mSeekFraction >= 0) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            //调用startAnimation方法,启动动画
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

可以看到,实际上动画的执行,是调用父类的start()方法。
如果没有动画延迟,则开始执行动画,并设置监听事件。则调用startAnimation()开启动画

3)进入到startAnimation()方法

private void startAnimation() {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                    System.identityHashCode(this));
        }

        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }

很遗憾,在这它又只是做了初始化动画方法,接着进入到initAnimation()

4)最终,进入到initAnimation()方法

/**
     * This function is called immediately before processing the first animation
     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
     * function is called after that delay ends.
     * It takes care of the final initialization steps for the
     * animation.
     *
     *  <p>Overrides of this method should call the superclass method to ensure
     *  that internal mechanisms for the animation are set up correctly.</p>
     */
    @CallSuper
    void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
            //计算每帧动画的属性值 
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

上面代码中mValues[i].init()方法便是计算动画的属性值,那么它通过什么来计算呢?先看mValues,属性动画的集合

/**
     * The property/value sets being animated.
     */
    PropertyValuesHolder[] mValues;

也就是我们在定义属性动画的时候,有set方法,我们通过set方法来给动画设置属性,最张一步一步的完成动画的。