ValueAnimator 源码深入分析

Posted 薛瑄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ValueAnimator 源码深入分析相关的知识,希望对你有一定的参考价值。

前言

android 的View动画, Roate Scale Translate Alpha 等,可以组合使用实现一些动画效果,但是这些动画却有一个致命的弱点,它们只是改变了 View 显示的大小,而没有改变 View 的响应区域。详情可查看这篇文章View 的 translationX、 translationY , X、Y 和 Left、Top,Right、Bottom

这时以 ObjectAnimator、ValueAnimator 为代表的属性动画也就应运而生了,就是更改View的属性例如,宽,高,透明度等等。来实现动画效果的。那么他是如何实现的呢?又是如何保证动画从头开始执行呢?

一、 如何使用

先来看个简单的示例

    private void performAnimate(final View target, final int start, final int end) 
        
        //针对width属性 
        PropertyValuesHolder propertyValuesHolder1 = PropertyValuesHolder.ofInt("width", 1, 1, 5);
        //使用PropertyValuesHolder 对象创建ValueAnimator
        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder1);
        //设置每一帧的回调接口
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 


            @Override
            public void onAnimationUpdate(ValueAnimator animator) 
                //获取到属性width 的值,设置给view
                target.getLayoutParams().width = (int) animator.getAnimatedValue("width");
                target.requestLayout();
            
        );

        valueAnimator.setDuration(5000)  //设置持续时间
                     .setInterpolator(new LinearInterpolator())  //设置插值器
                     .start();
    
    

大家应该都很熟悉这段代码了,就不多说,直接开始分析源码

二、源码分析

2.1、PropertyValuesHolder

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

    static class IntPropertyValuesHolder extends PropertyValuesHolder 
    
        public IntPropertyValuesHolder(String propertyName, int... values) 
            super(propertyName);
            setIntValues(values);
        

        @Override
        public void setIntValues(int... values) 
            //调用基类的setIntValues函数
            super.setIntValues(values);
            mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
        

    

调用PropertyValuesHolder的setIntValues

    public void setIntValues(int... values) 
        mValueType = int.class;
        mKeyframes = 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);
    

创建了IntKeyframeSet的数组,分了两种情况,

  • numKeyframes == 1时只会设置keyframes[0]和keyframes[1]分别代表的是动画的起始位置和结束位置;

  • numKeyframes > 1时,则将动画均分成numKeyframes - 1份,也就是有numKeyframes - 1 个 IntKeyframe,每个保存着当前动画进度 和值的信息。最后构建了一个IntKeyframeSet对象

2.2 、 ValueAnimator.ofPropertyValuesHolder

    public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) 
        ValueAnimator anim = new ValueAnimator();
        anim.setValues(values);
        return anim;
    
    public void setValues(PropertyValuesHolder... values) 
        int numValues = values.length;
        mValues = values;
        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
        for (int i = 0; i < numValues; ++i) 
            PropertyValuesHolder valuesHolder = values[i];
            //按照PropertyValuesHolder中属性名 作为key(本例是"width")来存储。在最后.getAnimatedValue("width") 就可以获取到对应的值
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
        

2.3 ValueAnimator#start

一切都设置好后,开始执行动画

    private void start(boolean playBackwards) 
        if (Looper.myLooper() == null) 
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        
        //是否倒序播放
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        //如果是倒序播放,并且指定了开始位置(从头到尾,在这个过程的百分比),那么需要重新计算开始的位置,倒序是从尾到头,需要转换mSeekFraction
        // 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 = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) 
            // 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();
            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);
            
        
    
    private void addAnimationCallback(long delay) 
        if (!mSelfPulse) 
            return;
        
        //获取当前线程的AnimationHandler对象(该对象使用ThreadLocal来描述的)
        //这里传入的回调接口是this,因为ValueAnimator 实现了AnimationFrameCallback 接口,后面会分析到
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    
    

2.4、AnimationHandler# addAnimationFrameCallback

    //callback 就是ValueAnimator ,它实现的AnimationFrameCallback 接口
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) 
        //第一次调用的时候,还没有添加任何回调,所以mAnimationCallbacks 的大小为0
        if (mAnimationCallbacks.size() == 0) 
            //getProvider 函数获取一个AnimationFrameCallbackProvider 类型的对象
            getProvider().postFrameCallback(mFrameCallback);
        
        //把当前回调添加到mAnimationCallbacks,不能重复添加
        if (!mAnimationCallbacks.contains(callback)) 
            mAnimationCallbacks.add(callback);
        

        if (delay > 0) 
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        
    
    private AnimationFrameCallbackProvider getProvider() 
        if (mProvider == null) 
            mProvider = new MyFrameCallbackProvider();
        
        return mProvider;
    

2.5、AnimationHandler# MyFrameCallbackProvider

    private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider 

        //Choreographer 的实例,也是使用ThreadLocal 描述的
        final Choreographer mChoreographer = Choreographer.getInstance();

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) 
            //这里最终调用了Choreographer的函数,
            mChoreographer.postFrameCallback(callback);
        

        @Override
        public void postCommitCallback(Runnable runnable) 
            mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
        

        @Override
        public long getFrameTime() 
            return mChoreographer.getFrameTime();
        

        @Override
        public long getFrameDelay() 
            return Choreographer.getFrameDelay();
        

        @Override
        public void setFrameDelay(long delay) 
            Choreographer.setFrameDelay(delay);
        
    

可以看到 最终的操作,都是交给了Choreographer 处理的,不熟悉 Choreographer的同学,可以查看Choreographer 源码分析,简单来说Choreographer 会根据VSync信号来回调传递进去的接口。可以看到上面postFrameCallback,postCallback都指定了回调接口

2.6、AnimationHandler# mFrameCallback

那这个回调接口,到底是什么呢?它都执行了什么呢

   // 看到它的类型,知道它是被Choreographer 回调的
    private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() 
        @Override
        public void doFrame(long frameTimeNanos) 
            //执行动画的回调,对该时刻的对应的那帧动画 进行处理
            doAnimationFrame(getProvider().getFrameTime());
            //注意此时的mAnimationCallbacks 是大于0的,是在2.4小节的代码中被添加进去的
            if (mAnimationCallbacks.size() > 0) 
                //再次调用mChoreographer.postFrameCallback,回调是当前对象,所以收到下一个VSync信号 ,还会执行这个回调doFrame
                //不断循环,直到动画结束,可以猜想到,动画结束,mAnimationCallbacks的大小肯定是0
                getProvider().postFrameCallback(this);
            
        
    ;
    private void doAnimationFrame(long frameTime) 
        //获取当前时间
        long currentTime = SystemClock.uptimeMillis();
        final int size = mAnimationCallbacks.size();
        for (int i = 0; i < size; i++) 
            //依次取出,回调接口
            final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
            if (callback == null) 
                continue;
            
            //是否到了 执行callback的时间,(主要针对延迟执行的回调) 
            if (isCallbackDue(callback, currentTime)) 
                //执行回调接口
                callback.doAnimationFrame(frameTime);
                //下面这段代码,现在已经没有用了,但是理解它解决的问题,还是有意义的
                //现在该问题是如何解决的呢?我初步分析 是在2.7中设置startTime
                if (mCommitCallbacks.contains(callback)) 
                    getProvider().postCommitCallback(new Runnable() 
                        @Override
                        public void run() 
                            commitAnimationFrame(callback, getProvider().getFrameTime());
                        
                    );
                
            
        
        cleanUpList();
    

下面我们先来简单介绍一下postCommitCallback 是在解决什么问题。之后再回来分析callback.doAnimationFrame(frameTime);

画是使用插值器,根据时间的流逝来计算比例。在动画开始执行,如果第一帧的渲染时间过长,会导致不是从第一帧开始播放,为了避免这个问题,需要在绘制完后,重新设置动画开始时间。

commitAnimationFrame 最终执行到,下面这个回调(接口AnimationFrameCallback的)

ValueAnimator.java
    public void commitAnimationFrame(long frameTime) 
        if (!mStartTimeCommitted) 
            mStartTimeCommitted = true;
            long adjustment = frameTime - mLastFrameTime;
            if (adjustment > 0) 
                //调整动画的开始时间
                mStartTime += adjustment;
            
        
    

2.7、ValueAnimator#doAnimationFrame

    public final boolean doAnimationFrame(long frameTime) 
        
        if (mStartTime < 0) 
            //mStartTime 小于0 表示这是第一次进入,因为初始值是-1,
            //也就是第一帧动画,为了防止在绘制过程太长,动画还没真正开始执行,这里重新设置动画开始时间,保证动画是从第一帧执行的
            // First frame. If there is start delay, start delay count down will happen *after* this
            // frame.
            //根据是否倒序播放,设置开始时间
            mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
        

        // Handle pause/resume
        if (mPaused) 
           //动画暂停了,就记录暂停的时间,待下次重新执行时,需要把暂停时流逝的时间减去
            mPauseTime = frameTime;
            //这里删除动画的回调,也就说2.6小节,不会继续执行getProvider().postFrameCallback(this)
            removeAnimationCallback();
            return false;
         else if (mResumed) 
            mResumed = false;
            if (mPauseTime > 0) 
                // Offset by the duration that the animation was paused
                mStartTime += (frameTime - mPauseTime);
            
        

        if (!mRunning) 
            // If not running, that means the animation is in the start delay phase of a forward
            // running animation. In the case of reversing, we want to run start delay in the end.
            if (mStartTime > frameTime && mSeekFraction == -1) 
                // This is when no seek fraction is set during start delay. If developers change the
                // seek fraction during the delay, animation will start from the seeked position
                // right away.
                return false;
             else 
                // If mRunning is not set by now, that means non-zero start delay,
                // no seeking, not reversing. At this point, start delay has passed.
                mRunning = true;
                startAnimation();
            
        
		//mLastFrameTime 初始值是-1,
        if (mLastFrameTime < 0) 
            if (mSeekFraction >= 0) 
               //根据初始播放的百分比,计算动画开始时间
                long seekTime = (long) (getScaledDuration() * mSeekFraction);
                //把开始时间往前移动,从而达到直接在指定位置开始播放动画
                mStartTime = frameTime - seekTime;
                mSeekFraction = -1;
            
            //允许修正开始时间(2.6小节的 commitAnimationFrame 函数会用到这个字段)
            mStartTimeCommitted = false; // allow start time to be compensated for jank
        
        mLastFrameTime = frameTime;
        // 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);
        boolean finished = animateBasedOnTime(currentTime);

        if (finished) 
            endAnimation();
        
        return finished;
    

2.8、 ValueAnimator#animateBasedOnTime

    boolean animateBasedOnTime(long currentTime) 
        boolean done = false;
        if (mRunning) 
            final long scaledDuration = getScaledDuration();
            final float fraction = scaledDuration > 0 ?
                    (float)以上是关于ValueAnimator 源码深入分析的主要内容,如果未能解决你的问题,请参考以下文章

ValueAnimator源码解析-基于Android API30

Android属性动画ValueAnimator源码简单分析

Java小节,spring源码分析

SSH开发模式——Struts2(第二小节)

Flutter 命令本质之 Flutter tools 机制源码深入分析

《深入理解SPARK:核心思想与源码分析》——SparkContext的初始化(中)