走进源码之SpringAnimation

作者: Wang_Guan | 来源:发表于2020-04-30 11:06 被阅读0次

    简介

    SpringAnimation 是通过 SpringForce 进行驱动的,SpringForce 定义了弹性的阻尼、刚度以及平衡位置。当 SpringAnimation 启动后,弹性驱动力都会在每一帧更新动画的值以及加速度,动画会一直执行到弹性达到一个平衡点。如果弹性没有设置阻尼的话,那么动画将会一直执行下去,不会停止。

    SpringAnimation 是继承于 DynamicAnimation 的,DynamicAnimation 的另一个直接子类是 FlingAnimation。

    构造方法

     public SpringAnimation(FloatValueHolder floatValueHolder) {
            super(floatValueHolder);
        }
    
        public <K> SpringAnimation(K object, FloatPropertyCompat<K> property) {
            super(object, property);
        }
    
    /**
    *  object 动画作用的属性的对象
    *  property object 的属性,用于动画作用
    *  finalPosition 创建时动画的平衡点
    *  <K> 属性的类型
    */
        public <K> SpringAnimation(K object, FloatPropertyCompat<K> property,
                float finalPosition) {
            super(object, property);
            mSpring = new SpringForce(finalPosition);
        }
    

    SpringAnimation 有三个构造方法,第一个构造方法只有一个参数,参数类型为 FloatValueHolder,其实就是一个提供value 值存储与访问的地方,在下一帧到来时设置新的值,调用者访问存储值。第二个构造函数与第三个构造函数大径相同,唯一不同的是第三个构造函数为我们初始化了 spring 属性,建议大家创建 SpringAniamtion 实例的时候用第三个构造函数,可以避免因忘记设置 spring 或者是 finalPosition 而导致异常发生。点进带两个参数的父类构造方法看看。

    <K> DynamicAnimation(K object, FloatPropertyCompat<K> property) {
            mTarget = object;
            mProperty = property;
            if (mProperty == ROTATION || mProperty == ROTATION_X
                    || mProperty == ROTATION_Y) {
                mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES;
            } else if (mProperty == ALPHA) {
                mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
            } else if (mProperty == SCALE_X || mProperty == SCALE_Y) {
                mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
            } else {
                mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
            }
        }
    

    该方法就是确定动画的对象和动画作用的属性,以及为mMinVisibleChange赋值,mMinVisibleChange为用户最小可见变化的值,这个值会根据不同的属性有不同的设定,以 view 来说,当动画作用的是 view 的 rotation、rotationX 或者是 rotationY 属性时,这个值为MIN_VISIBLE_CHANGE_ROTATION_DEGREES,如果是 alpha 或者是 scale 也会有不同的值,默认初始化后设置为像素类型值MIN_VISIBLE_CHANGE_PIXELS

    start()

        @Override
        public void start() {
            sanityCheck();
            mSpring.setValueThreshold(getValueThreshold());
            super.start();
        }
    

    SpringAnimation 的启动入口,在真正启动之前,做了一个安全检查,并给 spring 设置了相应的阈值,最后才是调用父类的启动方法,真正让动画动起来。

    private void sanityCheck() {
            if (mSpring == null) {
                throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
                        + " position or a spring force needs to be set.");
            }
            double finalPosition = mSpring.getFinalPosition();
            if (finalPosition > mMaxValue) {
                throw new UnsupportedOperationException("Final position of the spring cannot be greater"
                        + " than the max value.");
            } else if (finalPosition < mMinValue) {
                throw new UnsupportedOperationException("Final position of the spring cannot be less"
                        + " than the min value.");
            }
        }
    

    spring 在 SpringAnimation 里面不可或缺,弹性动画的各种计算,主要都是依靠 spring 来实现的。如果给 spring 设置了最大最小值的注意了,设置 finalPosition 的时候一定要在这个区间范围内,不然会报错。默认的最大值为 Float 的最大值,而最小值为最大值的负值。

    查看父类的 start() 方法,终于来到真正执行动画开始的地方。

        public void start() {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new AndroidRuntimeException("Animations may only be started on the main thread");
            }
            if (!mRunning) {
                startAnimationInternal();
            }
        }
    

    由上可知,我们的启动动画必须在主线程操作,多次调用 start() 方法,并不会多次执行 startAnimationInternal() 。接着看 startAnimationInternal() 做了什么。

        private void startAnimationInternal() {
            if (!mRunning) {
                // 设置已启动标志位
                mRunning = true;
                // 是否设置过开始值
                if (!mStartValueIsSet) {
                // 如果没有设置过的话,系统会取对象当前的属性值
                    mValue = getPropertyValue();
                }
                // Sanity check:
                if (mValue > mMaxValue || mValue < mMinValue) {
                    throw new IllegalArgumentException("Starting value need to be in between min"
                            + " value and max value");
                }
                AnimationHandler.getInstance().addAnimationFrameCallback(this, 0);
            }
        }
    

    这里依旧是动画启动前的一些准备工作,我们关注到AnimationHandler.getInstance().addAnimationFrameCallback(this, 0);继续跟进去看。

        // 注册以获取延迟后下一帧的回调
        public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
            if (mAnimationCallbacks.size() == 0) {
                // 如果在这之前没任何回调注册,推动第一帧。
                getProvider().postFrameCallback();
            }
            // 过滤掉重复注册的回调
            if (!mAnimationCallbacks.contains(callback)) {
                mAnimationCallbacks.add(callback);
            }
    
            if (delay > 0) {
                // 带有延时的回调,先存储进待处理集合。
                mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
            }
        }
    

    SystemClock.uptimeMillis() 会返回从开机到此刻的毫秒数,这其中不包括深度休眠的时间。看看第一帧是如何推动的getProvider().postFrameCallback(),这个 getProvider() 是什么?

        AnimationFrameCallbackProvider getProvider() {
            if (mProvider == null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mProvider = new FrameCallbackProvider16(mCallbackDispatcher);
                } else {
                    mProvider = new FrameCallbackProvider14(mCallbackDispatcher);
                }
            }
            return mProvider;
        }
    

    可以看到返回类型是 AnimationFrameCallbackProvider 。

        abstract static class AnimationFrameCallbackProvider {
            final AnimationCallbackDispatcher mDispatcher;
            AnimationFrameCallbackProvider(AnimationCallbackDispatcher dispatcher) {
                mDispatcher = dispatcher;
            }
    
            abstract void postFrameCallback();
        }
    

    AnimationFrameCallbackProvider 是一个抽象类,我们主要看它的实现类,上面可以看到,这里根据 Android API 的不同分别设置两个实现类,API16 及以上用的是 FrameCallbackProvider16 而以下的则用 FrameCallbackProvider14 这两个在实现上有什么不同?接着往下看。

    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
        private static class FrameCallbackProvider16 extends AnimationFrameCallbackProvider {
            // 获取编排器实例
            private final Choreographer mChoreographer = Choreographer.getInstance();
            private final Choreographer.FrameCallback mChoreographerCallback;
    
            FrameCallbackProvider16(AnimationCallbackDispatcher dispatcher) {
                super(dispatcher);
                // 初始化编排器回调
                mChoreographerCallback = new Choreographer.FrameCallback() {
                        @Override
                        public void doFrame(long frameTimeNanos) {
                            mDispatcher.dispatchAnimationFrame();
                        }
                    };
            }
    
            @Override
            void postFrameCallback() {
                // post 第一帧,相当于启动编排器了。
                mChoreographer.postFrameCallback(mChoreographerCallback);
            }
        }
    

    Choreographer 跟创建实例的线程有关系,在哪个线程创建的,它就运行在哪个线程,特别注意的一点是,如果要获取 Choreographer 实例,那这个线程一定要有自己的 looper。其内部的实现,是通过 handler 实现消息循环的,这里我们是在主线程创建的,所以,我们的 Choreographer 是运行在主线程上的。兜兜转转,Choreographer 会走到一下代码:

        private static final class CallbackRecord {
            public CallbackRecord next;
            public long dueTime;
            public Object action; // Runnable or FrameCallback
            public Object token;
    
            public void run(long frameTimeNanos) {
                if (token == FRAME_CALLBACK_TOKEN) {
                    ((FrameCallback)action).doFrame(frameTimeNanos);
                } else {
                    ((Runnable)action).run();
                }
            }
        }
    

    这里,我们是FRAME_CALLBACK_TOKEN类型,所以这里就调用了doFrame(),也就是回调了我们上一步设置的回调最终会执行mDispatcher.dispatchAnimationFrame();,这里的 dispatcher 我们后面一起看,我们接着看 API16 以下的实现:

       private static class FrameCallbackProvider14 extends AnimationFrameCallbackProvider {
    
            private final Runnable mRunnable;
            private final Handler mHandler;
            long mLastFrameTime = -1;
    
            FrameCallbackProvider14(AnimationCallbackDispatcher dispatcher) {
                super(dispatcher);
                mRunnable = new Runnable() {
                    @Override
                    public void run() {
                        mLastFrameTime = SystemClock.uptimeMillis();
                        mDispatcher.dispatchAnimationFrame();
                    }
                };
                mHandler = new Handler(Looper.myLooper());
            }
    
            @Override
            void postFrameCallback() {
                long delay = FRAME_DELAY_MS - (SystemClock.uptimeMillis() - mLastFrameTime);
                delay = Math.max(delay, 0);
                mHandler.postDelayed(mRunnable, delay);
            }
        }
    

    可以看到,这里是通过 handler 和 runnable 来实现的不断轮询的。runnable 调用后调用 dispatcher 的 dispatchAnimationFrame,而这里面又调用postFrameCallback 实现循环。

        class AnimationCallbackDispatcher {
            void dispatchAnimationFrame() {
                // 记录当前帧时间
                mCurrentFrameTime = SystemClock.uptimeMillis();
                AnimationHandler.this.doAnimationFrame(mCurrentFrameTime);
                if (mAnimationCallbacks.size() > 0) {
                    // 如果有监听者的话继续调用以实现循环
                    getProvider().postFrameCallback();
                }
            }
        }
    

    这里看到了,dispatchAnimationFrame 回调了 AnimationHandler 的 doAnimationFrame 方法:

        void doAnimationFrame(long frameTime) {
            long currentTime = SystemClock.uptimeMillis();
            for (int i = 0; i < mAnimationCallbacks.size(); i++) {
                // 遍历所有已经注册的监听者
                final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
                // 因为在调用 removeCallback 的时候,并没有真正的从集合里面删除,而是赋值为 null。
                if (callback == null) {
                    continue;
                }
                //  判断是否当前帧该回调的监听者
                if (isCallbackDue(callback, currentTime)) {
                    callback.doAnimationFrame(frameTime);
                }
            }
            // 真正执行 mAnimationCallbacks 的元素删除。
            cleanUpList();
        }
    

    可以看到,正常无 delay 的情况下,callback.doAnimationFrame() 会被调用。DynamicAnimation 也就是 SpringAnimation 的父类,在 start() 里面注册了这个监听器,该方法每一帧都会回调,所以会在里面进行 value 和 velocity 的计算,并且判断动画是否该结束。

        public boolean doAnimationFrame(long frameTime) {
            if (mLastFrameTime == 0) {
                // First frame.
                mLastFrameTime = frameTime;
                setPropertyValue(mValue);
                return false;
            }
            // 时间差,用于计算 value 和 velocity。
            long deltaT = frameTime - mLastFrameTime;
            mLastFrameTime = frameTime;
            // 判断动画
            boolean finished = updateValueAndVelocity(deltaT);
            // Clamp value & velocity.
            mValue = Math.min(mValue, mMaxValue);
            mValue = Math.max(mValue, mMinValue);
    
            // 更新属性值,并且回调所有监听的 update 的 listeners
            setPropertyValue(mValue);
    
            if (finished) {
                // 平滑结束动画。
                endAnimationInternal(false);
            }
            return finished;
        }
    

    计算 value 和 velocity 都在 updateValueAndVelocity 方法里面,这个方法是一个抽象方法,直接跟到 SpringAnimation 的实现里面。

    @Override
        boolean updateValueAndVelocity(long deltaT) {
            // If user had requested end, then update the value and velocity to end state and consider
            // animation done.
            if (mEndRequested) {
                // 设置结束点结束动画
                if (mPendingPosition != UNSET) {
                    mSpring.setFinalPosition(mPendingPosition);
                    mPendingPosition = UNSET;
                }
                mValue = mSpring.getFinalPosition();
                mVelocity = 0;
                mEndRequested = false;
                return true;
            }
    
            if (mPendingPosition != UNSET) {
                double lastPosition = mSpring.getFinalPosition();
                // Approximate by considering half of the time spring position stayed at the old
                // position, half of the time it's at the new position.
                // massState 用来存储 value 和 velocity。
                MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
                mSpring.setFinalPosition(mPendingPosition);
                mPendingPosition = UNSET;
    
                massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
                mValue = massState.mValue;
                mVelocity = massState.mVelocity;
    
            } else {
                MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
                mValue = massState.mValue;
                mVelocity = massState.mVelocity;
            }
    
            // 确保 value 在正常范围内
            mValue = Math.max(mValue, mMinValue);
            mValue = Math.min(mValue, mMaxValue);
    
            //  是否已经达到平衡状态,可以理解为动画该结束的状态。
            if (isAtEquilibrium(mValue, mVelocity)) {
                mValue = mSpring.getFinalPosition();
                mVelocity = 0f;
                return true;
            }
            return false;
        }
    

    上面给 value 和 velocity 赋值的时候,有个判断因素,那就是 mPendingPosition,mPendingPosition != UNSET 只有在调用 animateToFinalPosition() 且动画处于 running 状态的时候,才会成立。也就是说,当调用 animateToFinalPosition() 驱动动画的时候,就会将时间一分为二,然后最终计算出 value 和 velocity,如果用 start() 驱动动画,则会直接调用 SpringForce 的 updateValues() 函数计算 value 和 velocity。

    DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity,
                long timeElapsed) {
            init();
    
            double deltaT = timeElapsed / 1000d; // unit: seconds
            lastDisplacement -= mFinalPosition;
            double displacement;
            double currentVelocity;
            if (mDampingRatio > 1) {
                // Overdamped
                double coeffA =  lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity)
                        / (mGammaMinus - mGammaPlus);
                double coeffB =  (mGammaMinus * lastDisplacement - lastVelocity)
                        / (mGammaMinus - mGammaPlus);
                displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT)
                        + coeffB * Math.pow(Math.E, mGammaPlus * deltaT);
                currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT)
                        + coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT);
            } else if (mDampingRatio == 1) {
                // Critically damped
                double coeffA = lastDisplacement;
                double coeffB = lastVelocity + mNaturalFreq * lastDisplacement;
                displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT);
                currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT)
                        * (-mNaturalFreq) + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT);
            } else {
                // Underdamped
                double cosCoeff = lastDisplacement;
                double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq
                        * lastDisplacement + lastVelocity);
                displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
                        * (cosCoeff * Math.cos(mDampedFreq * deltaT)
                        + sinCoeff * Math.sin(mDampedFreq * deltaT));
                currentVelocity = displacement * (-mNaturalFreq) * mDampingRatio
                        + Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
                        * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
                        + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
            }
    
            mMassState.mValue = (float) (displacement + mFinalPosition);
            mMassState.mVelocity = (float) currentVelocity;
            return mMassState;
        }
    

    这里和之前阻尼的划分是一样的,都有几个节点,>1的情况、=1的情况以及<1的情况。这段代码看着还蛮头晕的,不过我们知道,它利用了各种公式结合阻尼率和刚性计算出 value 和 velocity 就差不多可以了,吧。🤣🤣🤣

    isAtEquiLibrium() 最终调用的是 SpringForce 里面的 isAtEquilibrium()

    public boolean isAtEquilibrium(float value, float velocity) {
            if (Math.abs(velocity) < mVelocityThreshold
                    && Math.abs(value - getFinalPosition()) < mValueThreshold) {
                return true;
            }
            return false;
        }
    

    如果 velocity 或者是位移差小于阈值的时候系统就认为其达到平衡状态了。这时就该停止动画了。

        private void endAnimationInternal(boolean canceled) {
            mRunning = false;
            // 反注册,不在接收帧回调。
            AnimationHandler.getInstance().removeCallback(this);
            mLastFrameTime = 0;
            mStartValueIsSet = false;
            for (int i = 0; i < mEndListeners.size(); i++) {
                if (mEndListeners.get(i) != null) {
                    // 回调 onAnimationEnd()
                    mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity);
                }
            }
            removeNullEntries(mEndListeners);
        }
    

    调用 cancel() 最终也会执行 endAnimationInternal()。至此,从动画开始到结束的流程就过了一遍了,当然有很多的细节没有提到,可以自己阅读下源码,除了计算那一段,其他的应该都挺好理解的。

    相关文章

      网友评论

        本文标题:走进源码之SpringAnimation

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