preface

Property animation is a new feature added to Android API 11. Property animation can work on any object property (property with get/set methods), not just View objects. From this point of view, the application scenarios of property animation become diverse.

The application of attribute animation is very simple. ObjectAnimator, ValueAnimator, AnimatorSet can be used to implement animation or animation combination. Based on the application of attribute animation, we analyze the principle with questions. We know that the animation we’re using will finish executing in a certain amount of time, so how does the property animation update the value? With this question we are looking at the source code.

Principle of attribute animation

val animator: ValueAnimator = ValueAnimator.ofInt(0.100);
animator.start()
Copy the code

The code above is a very simple property animation implementation that increases the value from 0 to 100 in 300ms (the default property animation time).

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); }}/ /...
}
Copy the code

Starting with the start() method, you can see that the start(Boolean playBackwards) method is called. In this method, you can see that there is a startAnimation() method.

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

The logic in the startAnimation() method is very simple. First look at the initAnimation() method, which does some initialization.

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

You can see that mValues[I].init() is initialized here. The mValues variable is the PropertyValuesHolder array. It holds the properties corresponding to the property animation, including information about setter and getter methods, and mainly updates the value of the property.

We continue to examine the notifyStartListeners() method, in this case the onAnimationStart() method that calls back to the AnimatorListener.

StartAnimation () has no logic to perform the animation, just some initialization and interface notifications. Looking back at the start() method, you can see that there is also an addAnimationCallback(0) method.

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

This code will be executed in the AnimationHandler class. And then the code.

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

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

Here you can see a familiar role mChoreographer, and this variable is Choreographer. This is an important role in Android that provides screen refreshes and Android page updates. Everyone will see Choreographer when learning the screen refresh mechanism.

Here Choreographer registers callbacks to its own work queue (see the on-screen refresh article for details of the internal implementation). The next workflow is then to move the paint interface back and forth through Choreographer when the screen is refreshed.

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); }}};Copy the code

This interface is the callback registered with Choreographer. If manimationCallbacks.size () >0, continue registering the interface. This also makes sense, this specification still has animations to perform, so continue registering callbacks to Choreographer.

Now look at the logic of step 1. Go in with the code, the middle may omit part of the method, specific details we can read the source code.

This step calls back to the doAnimationFrame() method in ValueAnimator.

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

    if (finished) {
        endAnimation();
    }
    return finished;
}
Copy the code

This method handles each frame, and the rest of the process is implemented in the animateBasedOnTime() method.

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

The final logic of this method is in the animateValue() method.

void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; {+ + I),// Calculates the value of the animation's execution result
            mValues[i].calculateValue(fraction);
        }
        if(mUpdateListeners ! =null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                // Update the animation
                mUpdateListeners.get(i).onAnimationUpdate(this); }}}Copy the code

This method sees the value of the animation execution result computed first. This is computed using PropertyValuesHolder.

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

As you can see here, the calculation is done through Keyframes, and the calculation process also depends on the influence of the interpolator. Keyframes is an interface that results in IntKeyframes and FloatKeyframes, which supports calculations of Int and Float types. See IntKeyframeSet and FloatKeyframeSet for implementations.

conclusion

After the above analysis, the general working principle of attribute animation is relatively clear.

  1. On calling the start() method or the property animation performs some initialization and notifies the animation process to begin;
  2. Animation callbacks are then registered into Choreographer’s work queue via AnimationHandler, listening for screen refreshes once until the animation ends (in this case, once per frame, continuing to register when the number of callbacks in the list is greater than zero).