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.ofInt

    public static ValueAnimator ofInt(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        return anim;
    }
    
    public void setIntValues(int... values) {
        if (values == null || values.length == 0) {
            return;
        }
        //传入的参数values,会设置到mValues,如果mValues为没有任何数据,就需要为它设置数据
        if (mValues == null || mValues.length == 0) {
            
            setValues(PropertyValuesHolder.ofInt("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setIntValues(values);
        }
        // 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)(currentTime - mStartTime) / scaledDuration : 1f;
            final float lastFraction = mOverallFraction;
            final boolean newIteration = (int) fraction > (int) lastFraction;
            final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                    (mRepeatCount != INFINITE);
            if (scaledDuration == 0) {
                // 0 duration animator, ignore the repeat count and skip to the end
                done = true;
            } else if (newIteration && !lastIterationFinished) {
                // Time to repeat
                if (mListeners != null) {
                    int numListeners = mListeners.size();
                    for (int i = 0; i < numListeners; ++i) {
                        mListeners.get(i).onAnimationRepeat(this);
                    }
                }
            } else if (lastIterationFinished) {
                done = true;
            }
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }

2.9、 ValueAnimator#animateBasedOnTime

    void animateValue(float fraction) {
        // 参数fraction 是时间流逝的百分比
        // 根据fraction ,使用插值器,计算出当前动画,需要显示进度的百分比
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            //这里最终调到 估值器,根据当前的动画进度百分比,计算出应该属性对应的值
            //本例中,mValues的类型是IntPropertyValuesHolder
            mValues[i].calculateValue(fraction);
        }

        //调用回调接口,例如:上面示例通过addUpdateListener 设置的接口
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
              
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

2.10、 PropertyValuesHolder#calculateValue

    void calculateValue(float fraction) {
        //mKeyframes 在本例中特指 IntKeyframeSet
        Object value = mKeyframes.getValue(fraction);
        //mConverter 可设置,在示例中,和设置插值器等是一样的
        //把最终的属性值,赋值给mAnimatedValue,
        //最终在回调接口onAnimationUpdate中使用 animator.getAnimatedValue("width")获取该值
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

2.11 、 IntKeyframeSet#getIntValue

    @Override
    public int getIntValue(float fraction) {
        if (fraction <= 0f) {
          ... 省略代码...
          //这里是 动画进度 小于0% 的处理方式
            return mEvaluator == null ?
                    prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            intValue();
        } else if (fraction >= 1f) {
          ... 省略代码...
          //这里是 动画进度 大于100% 的处理方式
            return mEvaluator == null ?
                    prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
        }

        //下面是动画正常运行流程,上面省略的代码逻辑和下面基本一致 
        IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
        for (int i = 1; i < mNumKeyframes; ++i) {
            IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
            //遍历mKeyframes, 取出指定IntKeyframe进度百分比,和当前的进度百分比,
            //选出比fraction大的最小的一个
            if (fraction < nextKeyframe.getFraction()) {
              //prevKeyframe.getFraction()  <fraction < nextKeyframe.getFraction()
                //获取插值器
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                //fraction在 prev-next 的进度百分比 
                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                //获取对应进步百分比的值,这个实在ofInt 的时候设置的    
                int prevValue = prevKeyframe.getIntValue();
                int nextValue = nextKeyframe.getIntValue();
                // Apply interpolator on the proportional duration.
                if (interpolator != null) {
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                //如果有估值器,使用估值器计算出,属性对应的值
                return mEvaluator == null ?
                        prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                        ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                                intValue();
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't get here
        return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
    }

最终在回调接口onAnimationUpdate中使用 animator.getAnimatedValue(“width”)获取该值,来改变View 属性的值

到此就算是分析完了,如果觉得有帮助,给个赞吧。

©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页