Android View | 属性动画原理解析

2020-08-10  本文已影响0人  南子李
1. 属性动画的使用

属性动画有 ValueAnimator 和 ObjectAnimator,其中 ObjectAnimator 继承自 ValueAnimator,属性动画的常见使用如下:

        //ValueAnimator 的使用
        ValueAnimator moveValueAnimator = ValueAnimator.ofInt(0, 800);
        moveValueAnimator.setDuration(10000);
        //添加动画计算监听
        moveValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                //调用View.layout 方法,改变 tvMove 的位置达到动画效果
                tvObject.layout(tvMove.getLeft(), curValue,
                        tvMove.getRight(), curValue + tvMove.getHeight());
            }
        });
        moveValueAnimator.start();

        //ObjectAnimator 的使用
        ObjectAnimator.ofFloat(tvObject, "X", 200).start();

上面代码中,ValueAnimator 实现了在10s内将 tvObject 位置向下移动了800的动画效果。使用 ValueAnimatior 实现了值在10s内从0~800的动画,监听值的动画进度,每调用一次 onAnimationUpdate() 方法通过调用 tvObject.layout() 传入需要变更的位置信息对 tvObject 的位置进行变更。而 ObjectAnimator 则实现了将 tvObject 移动到横坐标为200的位置。

2. 源码解析

考虑一下,调用了 start() 方法之后属性动画是如何改变属性值的呢?属性动画对每一帧又是如何绘制的呢?那么从 ObjectAnimator 的 start() 方法为入口来研究属性动画的实现原理。

    @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

首先调用 AnimationHandlerautoCancelBasedOn() 方法停止动画,这里可以保证当我们多次调用同一个 ObjectAnimator 的 start() 方法时停止并清除掉上一次调用 start()时正在进行的动画效果,确保动画同步。如果是 debug 模式则遍历 mValues 打印出每一各属性动画的属性名、属性的开始值结束值,最后调用了父类 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;

        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        ...
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
  1. 首先判断 Looper.myLooper()是否为空,为空则抛出异常,可见属性动画效果的实现是在Looper 线程(UI线程)进行的;
  2. playBackwards表示动画是否要反向播放,如果是反向播放根据动画播放重复次数是否为 INFINITE(无限循环) 设置当前的播放进度 mSeekFraction
  3. 如果没有设置延时启动动画,调用 startAnimation() 方法,startAnimation() 方法里设置了动画的相关参数并调用了 initAnimation() 方法。
    void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

initAnimation() 方法很简单,遍历 mValues 并调用 PropertyValuesHolder 的 init() 方法。注意:initAnimation() 方法会在处理动画第一帧时调用,接下来我们进入到 PropertyValuesHolder 的 init() 方法去看看具体做了什么。

    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
            mKeyframes.setEvaluator(mEvaluator);
        }
    }

PropertyValuesHolder 的 init() 中方法中为属性设置了 TypeEvaluator,TypeEvaluator 用于计算属性在每一帧的属性值。走到这一步我们发现从调用 ObjectAnimator 的 start() 方法开始,都是在做属性动画的初始化工作,包括设置属性动画的属性类型、播放进度等。
那么我们如何去设置属性的值呢?回到 VlaueAnimator#start() 方法中,调用了 initAnimation() 方法后,接着判断了mSeekFraction 值是否为-1,其默认值为-1,如果是第一次调用则会调用 setCurrentPlayTime(),否则调用 setCurrentFraction(),源码中 setCurrentPlayTime() 同样也调用了 setCurrentFraction() 方法,setCurrentFraction() 方法主要计算了当前的属性进度最后调用了 animateValue() 方法,我们来 animateValue() 的代码:

    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);
            }
        }
    }

好像也没做什么,只是根据当前属性变化的进度计算了一下对应的属性值,再回到 VlaueAnimator#start() 方法中,遗漏了一个方法的调用 addAnimationCallback(0);

    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }

getAnimationHandler()返回了 AnimationHandler 的单例并调用了 addAnimationFrameCallback() 方法。

    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }

因为是第一次调用,mAnimationCallbacks.size() 肯定是0,这里调用了 getProvider().postFrameCallback(mFrameCallback),getProvider()返回的是 AnimationFrameCallbackProvider ,AnimationFrameCallbackProvider是一个接口,它的实现类 MyFrameCallbackProvider 中实现了 postFrameCallback() 方法:

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            mChoreographer.postFrameCallback(callback);
        }

进到 Choreographer 的 postFrameCallback()看看:

    public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);
    }

    public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
        ...
        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
    }

   private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ...
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

基本从 addAnimationCallback() 到 postFrameCallbackDelayed() 我们就大概知道,start() 中调用 addAnimationCallback() 方法是实现屏幕刷新的,Choreographer 中的 FrameHandler(是一个Handler类,用于接收处理 callback 的消息)会调用 doFrame() 处理每一帧刷新和绘制,具体屏幕刷新机制可以参考这里 。回到 addAnimationFrameCallback() ,调用 getProvider().postFrameCallback(mFrameCallback) 时传入的 mFrameCallback 是什么呢?

    private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

从上面代码中可以明显看到,mFrameCallback 其实就是一个 Choreographer.FrameCallback 的接口对象,Choreographer 中接收到需要刷新屏幕的通知时就会调用该接口方法 doFrame(),mFrameCallback 实现了其中的方法 doFrame(),方法中先是调用 doAnimationFrame() 方法,接着判断 mAnimationCallbacks 是否为空,即是否还有 AnimationFrameCallback 动画帧的回调,如果有则继续调用 Choreographer的postFrameCallback() 循环上述步骤,直到所有动画帧执行完毕。这里应该很明显了 doAnimationFrame() 方法就是去处理每一帧动画的回调的,我们来看看它的实现:

    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;
            }
            if (isCallbackDue(callback, currentTime)) {
                callback.doAnimationFrame(frameTime);
                if (mCommitCallbacks.contains(callback)) {
                    getProvider().postCommitCallback(new Runnable() {
                        @Override
                        public void run() {
                            commitAnimationFrame(callback, getProvider().getFrameTime());
                        }
                    });
                }
            }
        }
        cleanUpList();
    }

doAnimationFrame() 中去遍历我们的回调集合 mAnimationCallbacks,接着调用了接口 AnimationFrameCallback 的方法 doAnimationFrame() ,还调用了 commitAnimationFrame() 方法,该方法主要用于处理由于遍历耗时而导致动画开始时的第一帧未绘制的问题,最后调用 cleanUpList() 清空动画帧回调集合。从方法命名也能看出 doAnimationFrame() 是在处理每一帧的动画的,具体来看 doAnimationFrame() 的实现,该方法在哪里实现的呢?我们要去找在哪里传入了 AnimationFrameCallback 对象。

回到最开始 VlaueAnimator 的 start() 方法中,有这么一行代码 addAnimationCallback(),传入了 this,这里的 this 就是我们要找的 AnimationHandler.AnimationFrameCallback,这说明 ValueAnimtor 实现了该接口的方法 doAnimationFrame(),来看具体实现:

    public final boolean doAnimationFrame(long frameTime) {
        ...
        // Handle pause/resume
        if (mPaused) {
            ...
            removeAnimationCallback();
        } else if (mResumed) {
            ...
        }

        if (!mRunning) {
            if (mStartTime > frameTime && mSeekFraction == -1) {
                return false;
            } else {
                mRunning = true;
                startAnimation();
            }        
        }
        ..
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        if (finished) {
            endAnimation();
        }
        return finished;
    }

1)如果是停止状态,调用 removeAnimationCallback(),将当前 AnimationFrameCallback 从 mAnimationCallbacks 中移除,移除操作的实现是将其置空,这点可以从 AnimationHandler#removeCallback() 中 mAnimationCallbacks.set(id, null) 看出;
2)如果是第一次绘制调用 startAnimation() 开启动画;
3)如果已经完成了当前设置的属性动画,调用 endAnimation() 重置相关参数并清空所有的 AnimationFrameCallback。

整个流程下来,还有个疑问我们是怎么给属性设置值的?,具体来看 PropertyValuesHolder 的 setupValue() 方法,该方法中调用 keyFrame.setValue() 用于设置关键帧对应的属性值,这个属性值怎么来的呢,我们来看代码:

    private void setupValue(Object target, Keyframe kf) {
        if (mProperty != null) {
            Object value = convertBack(mProperty.get(target));
            kf.setValue(value);
        } else {
            ...
                if (mGetter == null) {
                    Class targetClass = target.getClass();
                    setupGetter(targetClass);
                    if (mGetter == null) {
                        return;
                    }
                }
                Object value = convertBack(mGetter.invoke(target));
                kf.setValue(value);
            ...
        }
    }

当前属性值是通过属性的 get() 方法拿到的,而 get() 方法则是根据属性名通过反射去调用的,调用结果就是返回当前的属性值。那么 set() 方法呢?我们从 setAnimatedValue() 方法中了解到,和 get() 方法一样,都是通过反射调用的。

    void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

其中 getAnimatedValue() 方法返回当前计算的属性值,通过反射调用set() 方法设置属性当前的属性值。

1)属性动画运行在 Looper 线程(即 UI 线程);
2)属性动画的 set() 和 get() 方法都是根据属性名反射调用的;
3)属性动画的属性必须要有 set() 和 get() 方法,set() 方法用于动画每一帧的绘制,而 get() 方法用于返回当前属性值,如果我们再设置属性动画时没有指定属性的初始值,系统就会调用 get() 方法去获取,所以 get() 方法也是必须的。
4)属性动画的实现就是不断调用属性的 set() 方法去绘制每一帧,直到达到属性的结束值,绘制停止动画也就停止了。

回答开头提到的两个问题:
  1. 属性动画是如何改变属性值的?
    通过 TypeEvaluator 计算的来的当前的属性值,通过反射调用属性的 set() 方法设置属性值。
  2. 属性动画是如何绘制每一帧的?
    AnimationHandler 用于管理动画,其中集合 mAnimationCallbacks 是一个存储 AnimationFrameCallback 的集合,AnimationHandler 接收到屏幕刷新的通知时(通过Choreographer.FrameCallback接收)会依次执行每一个AnimationFrameCallback 的 doAnimationFrame() 方法,在 doAnimationFrame() 实现了每一帧动画的开始、绘制、结束整个流程。
疑问:动画是在 UI线程运行的,动画是需要耗时的,是否和不能在 UI线程执行耗时操作相矛盾?
上一篇下一篇

猜你喜欢

热点阅读