Android属性动画源代码解析(超详细)

本文假定你已经对属性动画有了必定的了解,至少使用过属性动画。下面咱们就从属性动画最简单的使用开始。html

ObjectAnimator
      .ofInt(target,propName,values[])
      .setInterpolator(LinearInterpolator)
      .setEvaluator(IntEvaluator)
      .setDuration(500)
      .start();

相信这段代码对你必定不陌生,代码中有几个地方是本文中将要重点关注的,setInterpolator(...)setEvaluator(...)setDuration(...)在源代码中是如何被使用的。另外,咱们也将重点关注Android中属性动画是如何一步步地实现动画效果的(精确到每一帧(frame))。最后啰嗦几句,本文中使用的代码是Android 4.2.2。java

上面代码的做用就是生成一个属性动画,根据ofInt()咱们知道只是一个属性值类型为Int的View的动画。先放过其余的函数,从ObjectAnimator的start()函数开始。android

public void  start() {
        //...省略没必要要代码 
        super.start();
    }

从代码中咱们知道,它调用了父类的start()方法,也就是ValueAnimator.start()。这个方法内部又调用了自身类内部的start(boolean playBackwards)方法。数组

/**
     * 方法简要介绍:
     * 这个方法开始播放动画。这个start()方法使用一个boolean值playBackwards来判断是否需
     * 要回放动画。这个值一般为false,但当它从reverse()方法被调用的时候也
     * 可能为true。有一点须要注意的是,这个方法必须从UI主线程调用。
     */
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // 在动画实际运行前,设置动画的初始值
            setCurrentPlayTime(0);
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

对代码中几个值的解释数据结构

  • mPlayingStated表明当前动画的状态。用于找出何时开始动画(if state == STOPPED)。固然也用于在animator被调用了cancel()end()在动画的最后一帧中止它。可能的值为STOPPED, RUNNING, SEEKED.
  • mStarted是Animator中一个额外用于标识播放状态的值,用来指示这个动画是否须要延时执行。
  • mStartedDelay指示这个动画是否已经从startDelay中开始执行。
  • AnimationHandler animationHandler 是一个实现了Runnable接口的ValueAnimator内部类,暂时先放过,后面咱们会具体谈到。

从上面这段代码中,咱们了解到一个ValueAnimator有它本身的状态(STOPPED, RUNNING, SEEKED),另外是否延时也影响ValueAnimator的执行。代码的最后调用了animationHandler.start(),看来动画就是从这里启动的。别急,咱们还没初始化ValueAnimator呢,跟进setCurrentPlayTime(0)看看。app

public void setCurrentPlayTime(long playTime) {
        initAnimation();
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        if (mPlayingState != RUNNING) {
            mSeekTime = playTime;
            mPlayingState = SEEKED;
        }
        mStartTime = currentTime - playTime;
        doAnimationFrame(currentTime);
    }

这个函数在animation开始前,设置它的初始值。这个函数用于设置animation进度为指定时间点。playTime应该介于0到animation的总时间之间,包括animation重复执行的时候。若是animation尚未开始,那么它会等到被设置这个时间后才开始。若是animation已经运行,那么setCurrentTime()会将当前的进度设置为这个值,而后从这个点继续播放。ide

接下来让咱们看看initAnimation()函数

void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

这个函数一看就以为跟初始化动画有关。这个函数在处理动画的第一帧前就会被调用。若是startDelay不为0,这个函数就会在就会在延时结束后调用。它完成animation最终的初始化。oop

那么mValues是什么呢?还记得咱们在文章的开头介绍ObjectAnimator的使用吧?还有一个ofInt(T target, Property<T, Integer> property, int... values)方法没有介绍。官方文档中对这个方法的解释是:构造并返回一个在int类型的values数值之间ObjectAnimator对象。当values只有一个值的时候,这个值就做为animator的终点值。若是有两个值的话,那么这两个值就做为开始值和结束值。若是有超过两个以上的值的话,那么这些值就做为开始值,做为animator运行的中间值,以及结束值。这些值将均匀地分配到animator的持续时间。post

先中断ObjectAnimator.start()流程的分析,回到开头ObjectAnimator.ofInt(...)

接下来让咱们深刻ofInt(...)的内部看看。

public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setIntValues(values);
        return anim;
    }

对这个函数的解释:

  • target 就是将要进行动画的对象
  • propertyName 就是这个对象属性将要进行动画的属性名
  • values 一组值。随着时间的推移,动画将根据这组值进行变化。

再看看anim.setIntValues这个函数

public void setIntValues(int... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofInt(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
            }
        } else {
            super.setIntValues(values);
        }
    }

一开始的时候,mProperty确定尚未初始化,咱们进去setValues(PropertyValuesHolder.ofInt(mPropertyName, values))看看。这里涉及到PropertyValuesHolder这个类。PropertyValuesHolder这个类拥有关于属性的信息和动画期间须要使用的值。PropertyValuesHolder对象能够用来和ObjectAnimator或ValueAnimator一块儿建立能够并行操做不PropertyValuesHolder同属性的animator。

那么PropertyValuesHolder.ofInt()是干吗用的呢?它经过传入的属性和values来构造并返回一个特定类型的PropertyValuesHolder对象(在这里是IntPropertyValuesHolder类型)。

public static PropertyValuesHolder ofInt(String propertyName, int... values) {
        return new IntPropertyValuesHolder(propertyName, values);
    }

在IntPropertyValuesHolder内部

public IntPropertyValuesHolder(String propertyName, int... values) {
        super(propertyName);
        setIntValues(values);
    }

    @Override
    public void setIntValues(int... values) {
        super.setIntValues(values);
        mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
    }

跳转到父类(PropertyValuesHolder)的setIntValues

public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframeSet = KeyframeSet.ofInt(values);
    }

这个函数其实跟咱们前面介绍到的 PropertyValueHolder的构造函数类似,它就是设置动画过程当中须要的值。若是只有一个值,那么这个值就假定为animator的终点值,动画的初始值会自动被推断出来,经过对象的getter方法获得。固然,若是全部值都为空,那么一样的这些值也会在动画开始的时候也会自动被填上。这套自动推断填值的机制只在PropertyValuesHolder对象跟ObjectAnimator一块儿使用的时候才有效,而且有一个能从propertyName自动推断出的getter方法这些条件都成立的时候才能用,否则PropertyValuesHolder没有办法决定这些值是什么。
接下来咱们看到KeyframeSet.ofInt(values)方法。KeyframeSet这个类持有Keyframe的集合,在一组给定的animator的关键帧(keyframe)中会被ValueAnimator用来计算值。这个类的访问权限为包可见,由于这个类实现Keyframe怎么被存储和使用的具体细节,外部不须要知道。

接下来咱们看看KeyframeSet.ofInt(values)方法。

public static KeyframeSet ofInt(int... values) {
        int numKeyframes = values.length;
        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
        } else {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new IntKeyframeSet(keyframes);
    }

在这个方法里面,咱们看见从最开始的ObjectAnimator.ofInt(target,propName,values[]),也就是咱们在文章的开头使用系统提供的动画初始化函数中传入的int数组,在这里获得了具体的使用。先不关心具体的使用Keyframe.ofInt(...)。从这里咱们就能够知道原来Android SDK经过传入的int[]的长度来决定animator中每一个帧(frame)的值。具体的对传入的int[]的使用能够参考文章里面对ObjectAnimator.ofInt(...)的介绍。在KeyframeSet.ofInt这个函数的最后一句话使用了IntKeyframeSet的构造函数来初始化这些Keyframe。

public  IntKeyframeSet(IntKeyframe... keyframes) {
         super(keyframes);
    }

在IntKeyframeSet的构造函数中又调用父类KeyframeSet的构造函数来实现。

public KeyframeSet(Keyframe... keyframes) {
        mNumKeyframes = keyframes.length;
        mKeyframes = new ArrayList<Keyframe>();
        mKeyframes.addAll(Arrays.asList(keyframes));
        mFirstKeyframe = mKeyframes.get(0);
        mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
        mInterpolator = mLastKeyframe.getInterpolator();
    }

从这个构造函数中咱们又能够了解到刚刚初始化后的Keyframe数组的第一项和最后一项(也就是第一帧和最后一帧)获得了优先的待遇,做为在KeyframeSet中的字段,估计是为了后面计算动画开始和结束的时候方便。

小结ObjectValue、PropertyValueHolder、KeyframeSet的关系

咱们绕了好久,不知道是否把你弄晕了,这里稍稍总结一下。咱们就不从调用的顺序一步步分析下来了,太长了。我直接说说ObjectValue、PropertyValueHolder、KeyframeSet之间的关系。这三个类比较有特色的地方,ObjectAnimator无疑是对的API接口,ObjectAnimator持有PropertyValuesHolder做为存储关于将要进行动画的具体对象(一般是View类型的控件)的属性和动画期间须要的值。而PropertyValueHolder又使用KeyframeSet来保存animator从开始到结束期间关键帧的值。这下子咱们就了解animator在执行期间用来存储和使用的数据结构。废话一下,从PropertyValueHolder、KeyframeSet这个两个类的源代码来看,这三个类的API的设计挺有技巧的,他们都是经过将具备特定类型的实现做为一个大的概况性的类的内部实现,经过这个大的抽象类提供对外的API(例如,PropertyValuesHolder.ofInt(...)的实现)。

回到ObjectAnimator.start()流程的分析

不知道是否把你上面你是否能清楚,反正不太影响下面对ObjectAnimator.start()流程的分析。从上面一段的分析咱们了解到ValueAnimator.initAnimation()中的mValue是 PropertyValuesHolder类型的东西。在initAnimation()里mValues[i].init()初始化它们的估值器Evaluator

void init() {
        if (mEvaluator == null) {
            // We already handle int and float automatically, but not their Object
            // equivalents
            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                    (mValueType == Float.class) ? sFloatEvaluator :
                    null;
        }
        if (mEvaluator != null) {
            // KeyframeSet knows how to evaluate the common types - only give it a custom
            // evaluator if one has been set on this class
            mKeyframeSet.setEvaluator(mEvaluator);
        }
    }

mEvaluator固然也可使用ObjectAnimator.setEvaluator(...)传入;为空时,SDK根据mValueType为咱们初始化特定类型的Evaluator。这样咱们的初始化就完成了。接下来,跳出initAnimation()回到
setCurrentPlayTime(...)

public void setCurrentPlayTime(long playTime) {
        initAnimation();
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        if (mPlayingState != RUNNING) {
            mSeekTime = playTime;
            mPlayingState = SEEKED;
        }
        mStartTime = currentTime - playTime;
        doAnimationFrame(currentTime);
    }

对animator三种状态STOPPED、RUNNING、SEEKED的解释

  • static final int STOPPED    = 0; // 还没开始播放
  • static final int RUNNING    = 1; // 正常播放中
  • static final int SEEKED     = 2; // 定位到一些时间值(Seeked to some time value)

对mSeekedTime、mStartTime的解释

  • mSeekedTime 当setCurrentPlayTime()被调用的时候设置。若是为负数,animator还没能定位到一个值。
  • mStartTime 第一次在animation.animateFrame()方法调用时使用。这个时间在第二次调用animateFrame()时用来肯定运行时间(以及运行的分数值)

setCurrentPlayTime(...)中doAnimationFrame(currentTime) 以前的代码其实都是对Animator的初始化。看来doAnimator(...)就是真正处理动画帧的函数了。这个函数主要主要用来处理animator中的一帧,并在有必要的时候调整animator的开始时间。

final boolean doAnimationFrame(long frameTime) {
        //对animator的开始时间和状态进行调整
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = frameTime;
            } else {
                mStartTime = frameTime - mSeekTime;
                // Now that we're playing, reset the seek time
                mSeekTime = -1;
            }
        }
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        return animationFrame(currentTime);
    }

看来这个函数就是调整了一些参数,真正的处理函数还在animationFrame(...)中。咱们跟进去看看。

boolean animationFrame(long currentTime) {
        boolean done = false;
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
            float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
            if (fraction >= 1f) {
                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                    // Time to repeat
                    if (mListeners != null) {
                        int numListeners = mListeners.size();
                        for (int i = 0; i < numListeners; ++i) {
                            mListeners.get(i).onAnimationRepeat(this);
                        }
                    }
                    if (mRepeatMode == REVERSE) {
                        mPlayingBackwards = mPlayingBackwards ? false : true;
                    }
                    mCurrentIteration += (int)fraction;
                    fraction = fraction % 1f;
                    mStartTime += mDuration;
                } else {
                    done = true;
                    fraction = Math.min(fraction, 1.0f);
                }
            }
            if (mPlayingBackwards) {
                fraction = 1f - fraction;
            }
            animateValue(fraction);
            break;
        }

        return done;
    }

这个内部函数对给定的animation的一个简单的动画帧进行处理。currentTime这个参数是由定时脉冲(先不要了解这个定时脉冲是什么,后面咱们会涉及)经过handler发送过来的(固然也多是初始化的时候,被程序调用的,就像咱们的分析过程同样),它用于计算animation已运行的时间,以及已经运行分数值。这个函数的返回值标识这个animation是否应该中止(在运行时间超过animation应该运行的总时长的时候,包括重复次数超过的状况)。

咱们能够把这个函数里面的fraction简单地理解成animator的进度条的当前的位置。if (fraction >= 1f) 注意到函数里面的这句话,当animator开始须要重复执行的时候,那么就须要执行这个if判断里面的东西,这里面主要就是记录和改变重复执行animator的一些状态和变量。为了避免让这篇文章太复杂,咱们这里就不进行分析了。经过最简单的animator只执行一次的状况来分析。那么接下来就应该执行animateValue(fraction)了。

void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

在每个animator的帧,这个函数都会被调用,结合传入的参数:已运行时间分数(fraction)。这个函数将已经运行的分数转为interpolaterd分数,而后转化成一个可用于动画的值(经过evaluator、这个函数一般在animation update的时候调用,可是它也可能在end()函数调用的时候被调用,用来设置property的最终值)。

在这里咱们须要理清一下Interpolaterd和evaluator之间的关系。

  • Interpolator:用来定义animator变化的速率。它让基础的动画效果(渐变、拉伸、平移、旋转)有加速、减速、重复等效果。在源代码中,其实interpolation的做用就是根据某一个时间点来计算它的播放时间分数,具体见官方文档
  • evaluator: evaluator所有继承至TypeEvaluator接口,它只有一个evaluate()方法。它用来返回你要进行动画的那个属性在当前时间点所须要的属性值。

咱们能够把动画的过程想象成是一部电影的播放,电影的播放中有进度条,Interpolator就是用来控制电影播放频率,也就是快进快退要多少倍速。而后Evaluator根据Interpolator提供的值计算当前播放电影中的哪个画面,也就是进度条要处于什么位置。

这个函数分三步:

  1. 经过Interpolator计算出动画运行时间的分数
  2. 变量ValueAnimator中的mValues[i].calculateValue(fraction)(也就是 PropertyValuesHolder对象数组)计算当前动画的值
  3. 调用animation的onAnimationUpdate(...)通知animation更新的消息
//PropertyValuesHolder.calculateValue(...)
        void calculateValue(float fraction) {
            mAnimatedValue = mKeyframeSet.getValue(fraction);
        }

        //mKeyframeSet.getValue
        public Object getValue(float fraction) {

        // Special-case optimization for the common case of only two keyframes
        if (mNumKeyframes == 2) {
            if (mInterpolator != null) {
                fraction = mInterpolator.getInterpolation(fraction);
            }
            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        if (fraction <= 0f) {
            final Keyframe nextKeyframe = mKeyframes.get(1);
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = mFirstKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (nextKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
                    nextKeyframe.getValue());
        } else if (fraction >= 1f) {
            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
            final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = prevKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (mLastKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        Keyframe prevKeyframe = mFirstKeyframe;
        for (int i = 1; i < mNumKeyframes; ++i) {
            Keyframe nextKeyframe = mKeyframes.get(i);
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                if (interpolator != null) {
                    fraction = interpolator.getInterpolation(fraction);
                }
                final float prevFraction = prevKeyframe.getFraction();
                float intervalFraction = (fraction - prevFraction) /
                    (nextKeyframe.getFraction() - prevFraction);
                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                        nextKeyframe.getValue());
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't reach here
        return mLastKeyframe.getValue();
    }

咱们先只关注if (mNumKeyframes == 2)这种状况,由于这种状况最多见。还记的咱们使用ObjectAnimator.ofInt(...)传入的int[]数组吗?咱们通常就传入动画开始值和结束值,也就是这里的mNumKeyframes ==2 的状况。这里经过mEvaluator来计算,咱们看看代码(IntEvaluator.evaluate(...)的代码)。

public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }

mEvaluator.evaluate(...)计算后,咱们就返回到ValueAnimator.animateValue(...)中,再回退到ValueAnimator.setCurrentPlayTime(...)。最后回到ValueAnimator.start(boolean playBackwards)。终于解析完了setCurrentPlayTime(...)这个函数,总结一下:这个函数主要用来初始化动画的值,固然这个初始化比咱们想象中的复杂多了,它主要经过PropertyValuesHolder、Evaluator、Interpolator来进行值的初始化。PropertyValueHolder又经过KeyframeSet来存储须要的值。

咱们回到文章开头介绍的ValueAnimator.start(boolean playBackwards)

private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            setCurrentPlayTime(0);
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

在setCurrentPlayTime(0)后,紧接着就经过notifyStartListeners()通知animation启动的消息。最后经过animationHandler.start()去执行。animationHandler是一个AnimationHandler类型的对象,它实现了runable接口。

//AnimationHandler.start()
        public void start() {
            scheduleAnimation();
        }
        //AnimationHandler.scheduleAnimation()
        private void scheduleAnimation() {
            if (!mAnimationScheduled) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            }
        }

        // Called by the Choreographer.
        @Override
        public void run() {
            mAnimationScheduled = false;
            doAnimationFrame(mChoreographer.getFrameTime());
        }

mHandler.start()最终就是经过mChoreographer.发送给UI系统。这个过程比较复杂,这里不介绍。咱们仅仅须要知道,动画中的一帧经过这种方式发送给UI系统后,在UI系统执行完一帧后,又会回调AnimationHandler.run()。那么其实这个过程就至关于,AnimationHandler.start()开始第一次动画的执行→UI系统执行AnimationHandler.run()→UI系统执行完后,回调相关函数→再执行AnimationHandler.run().能够理解为AnimationHandler.run()会一直调用自身屡次(固然这是由UI系统驱动的),直至动画结束。

这个过程比较复杂,若是你感兴趣,能够关注个人下一篇博客。

相关文章
相关标签/搜索