走进源码之SpringAnimation

简介

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()。至此,从动画开始到结束的流程就过了一遍了,当然有很多的细节没有提到,可以自己阅读下源码,除了计算那一段,其他的应该都挺好理解的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335