Android属性动画原理

1,034 阅读2分钟

前言

属性动画是Android API 11加入的新特性。属性动画可以作用于任意对象属性(具有get/set方法的属性),不仅仅可以作用于View对象。从这个特性来看,属性动画的应用场景就会变得多种多样。

属性动画应用非常简单,通过ObjectAnimator、ValueAnimator、AnimatorSet就可以实现动画或者动画组合。根据属性动画的应用,我们带着问题去分析原理。我们知道使用的动画会在一定的时间内执行完毕,那么属性动画是如何更新值的?带着这个问题我们在看源码。

属性动画原理

val animator: ValueAnimator = ValueAnimator.ofInt(0, 100);
animator.start()

上面的代码是一个非常简单的属性动画的实现,代码的作用是在300ms(属性动画默认时间)将值从0增加到100。

private void start(boolean playBackwards) {
    //...
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    addAnimationCallback(0);
    //...
    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    //...
}

我们从start()方法入手,可以看到会调用start(boolean playBackwards)方法。在这个方法里,可以看到有个startAnimation()方法,继续分析。

private void startAnimation() {
        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }

startAnimation()方法中逻辑很简单,先看一个initAnimation()方法,这个方法做了一些初始化工作。

void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

可以看到这里初始化了mValues[i].init()。这个mValues变量是PropertyValuesHolder数组。它持有了属性动画对应的属性,包括属性setter、getter方法等信息,主要作用是更新属性的值。

接下去继续分析notifyStartListeners()方法,这里是回调AnimatorListener的onAnimationStart()方法。

到这里可以发现,startAnimation()里面没有执行动画的逻辑,只是做了一些初始化工作和接口通知。回过头在来看start()方法,可以看到还有一个addAnimationCallback(0)方法。

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

这里代码将会在AnimationHandler类中执行。接着跟代码。

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

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

这里可以看到一个熟悉的角色mChoreographer,这个变量就是Choreographer。也就是Android中提供屏幕刷新与Android页面更新的重要角色。大家在学习屏幕刷新机制时都会看到Choreographer的身影。

这里Choreographer将回调注册到了自己的工作队列中(具体内部实现大家可以看屏幕刷新相关的文章)。那么接下来的工作流程就是在屏幕刷新时会通过Choreographer来回调动画接口。

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

这个接口就是注册到Choreographer的回调。这里先看第二步的逻辑,如果mAnimationCallbacks.size() >0,就继续注册接口。这也很好理解,这个说明还有动画需要执行,所以继续注册回调到Choreographer中。

接下来看第一步的逻辑。跟代码进去,中间可能省略部分方法,具体细节大家可以自行阅读源码。

这一步将回调进ValueAnimator中调用了doAnimationFrame()方法。

public final boolean doAnimationFrame(long frameTime) {
    //...
	final long currentTime = Math.max(frameTime, mStartTime);
    boolean finished = animateBasedOnTime(currentTime);

    if (finished) {
        endAnimation();
    }
    return finished;
}

这里方法就处理了每一帧的事情,接下来的过程在animateBasedOnTime()方法中实现。

 boolean animateBasedOnTime(long currentTime) {
     // ...
     mOverallFraction = clampFraction(fraction);
     float currentIterationFraction = getCurrentIterationFraction(
         mOverallFraction, mReversing);
     animateValue(currentIterationFraction);
 }

这个方法最终的逻辑在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);
            }
        }
    }

这个方法可以看到先计算的动画执行结果的值。这里是通过PropertyValuesHolder中计算的。

void calculateValue(float fraction) {
        Object value = mKeyframes.getValue(fraction);
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

这里可以看到是通过Keyframes计算的,计算的过程也依赖了插值器的影响。Keyframes是一个接口,继承它的结果又分为IntKeyframes和FloatKeyframes,这里就相当于支持了Int和Float类型的计算。具体实现可以看IntKeyframeSet和FloatKeyframeSet.

总结

经过上面的分析,属性动画的大致工作原理也就比较清晰了。

  1. 在调用start()方法或,属性动画会执行一些初始化工作,并通知动画流程开始;
  2. 然后通过AnimationHandler将动画回调注册进Choreographer的工作队列中,一次监听屏幕刷新,直至动画结束(这里是每帧处理一次,当回调列表数量大于0时就继续注册监听)。