Source code of ValueAnimator and related interpretation of interpolator estimator

Preface

This article from the beginning to the end of animation, a process to understand the source of animation.
Read the relationship between classes in the source code. The relationship between the interpolator and the estimator is explained.

problem

Question 1: animation involves multiple animation data storage structures at the same time.
Question 2: how does the process go from the beginning to the end of the code.
Question 3: what is the relationship between interpolator and estimator.

Basic use

mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, 0x80000000);//Start reading 1
mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
    public void onAnimationUpdate(ValueAnimator animation) {
       final int c = (Integer) animation.getAnimatedValue();
          mColor.setColor(c);
    }
});
mColorAnim.setDuration(2000);
mColorAnim.start(); //Start reading 2

Source code interpretation

ValueAnimator.ofObject

public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setObjectValues(values); //Key 1
        anim.setEvaluator(evaluator); //Key 2
        return anim;
    }

This method creates a ValueAnimator object and sets the three parameter values we passed in

First look at setObjectValues

PropertyValuesHolder[] mValues;
...
public void setObjectValues(Object... values) {
            // mValues has not been initialized
  if (mValues == null || mValues.length == 0) {
    setValues(PropertyValuesHolder.ofObject("", null, values)); //Key point 1
    }
        ....
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

Let's take a look at what PropertyValuesHolder.ofObject("", null, values) returns first

PropertyValuesHolder class
PropertyValuesHolder{ 
// Corresponding to three parameters' ', null, values
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,Object... values) {
        PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
        pvh.setObjectValues(values);//Key point 1
        pvh.setEvaluator(evaluator);
        return pvh;
    }
public void setObjectValues(Object... values) {
        mValueType = values[0].getClass();//Gets the type of value passed in.
        mKeyframes = KeyframeSet.ofObject(values);//Key point 2
        if (mEvaluator != null) { //The incoming is empty, so there is no setting
            mKeyframes.setEvaluator(mEvaluator);
        }
    }
}
--------------------------------------------------------------------------------------------------------
KeyframeSet class
KeyframeSet{
public static KeyframeSet ofObject(Object... values) {
        int numKeyframes = values.length;
        ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
            keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
        } else {  //The first value we passed in was (0, 0x8000000), where numKeyframes=2, so our keyframes[2]
            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);//Key 3
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new KeyframeSet(keyframes);
    }
}
--------------------------------------------------------------------------------------------------------
ObjectKeyframe class
ObjectKeyframe extends Keyframe {
Object mValue;
public static Keyframe ofObject(float fraction, Object value) {
        return new ObjectKeyframe(fraction, value);//Key 4
    }
}
//Key 5
ObjectKeyframe(float fraction, Object value)  Keyframe{
  mFraction = fraction;
  mValue = value;
  mHasValue = (value != null);
  mValueType = mHasValue ? value.getClass() : Object.class;
}

As you can see above, PropertyValuesHolder.ofObject("", null, values) returns a PropertyValuesHolder object, which contains our Keyframes.

Keyframe s is an array. According to the ValueAnimator.ofObject(new ArgbEvaluator(), 0, 0x80000000) you passed in, the number of 0, 0x80000000 you passed in corresponds to the number of Keyframes you created. (if you pass in one, two Keyframes will be generated.)

Keyframe is used to store mFraction score information, mValue value information mValueType type information
image.png

Return the original anim.setEvaluator(evaluator); / / key 2

    public void setEvaluator(TypeEvaluator value) {
        if (value != null && mValues != null && mValues.length > 0) {
            mValues[0].setEvaluator(value);
        }
    }
------------------------------------------------------------------------------------------------------------------
PropertyValuesHolder class
PropertyValuesHolder {
 public void setEvaluator(TypeEvaluator evaluator) {
        mEvaluator = evaluator;
        mKeyframes.setEvaluator(evaluator);
    }
}

Store the Evaluator in PropertyValuesHolder and KeyframeSet

The function of each class can be seen from the figure below
1: .png

mColorAnim.start(); / / start reading 2

public void start() {
  start(false);
}
----------------------------------------------------------------------
private void start(boolean playBackwards) {
mReversing = playBackwards;
mStarted = true;
mPaused = false;
mRunning = false;
....Mark bit recording, etc
addAnimationCallback(0);//Key 1 
if (mStartDelay == 0...){
 startAnimation();//Key 2
if (mSeekFraction == -1) {
setCurrentPlayTime(0);}}}

Key 1: wait for key 2, then look back
Key 2: first look at the startAnimation() of the callback;

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

 void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();//The key 2 mvalues are of type PropertyValuesHolder
            }
            mInitialized = true;
        }
    }
---------------------------------------------------
PropertyValuesHolder class
PropertyValuesHolder{
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
void init() {
        if (mEvaluator == null) {
            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                    (mValueType == Float.class) ? sFloatEvaluator :
                    null;
        }
        if (mEvaluator != null) {
            mKeyframes.setEvaluator(mEvaluator);
        }
    }
}

Here are some initialization of the start state. Then the M evaluator estimator will set up a estimator for you according to whether you have passed in or not. If not, it will set up a estimator according to the value type you have passed in before. Only Integer and Float type will help you set up the estimator automatically.

Look back at the above key 1: addAnimationCallback(0);

ValueAnimator class
ValueAnimator{
 private void addAnimationCallback(long delay) {
      ....
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }
}
--------------------------------------------------------------------------------------------------------
AnimationHandler class
public class AnimationHandler {
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);//Key 1 getProvider returns MyFrameCallbackProvider
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback); 
        }
       ......    }}

    private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
------------------------------------------------------------------------------------------------------------------------
MyFrameCallbackProvider class
MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
 final Choreographer mChoreographer = Choreographer.getInstance();
 @Override
  public void postFrameCallback(Choreographer.FrameCallback callback) {
            mChoreographer.postFrameCallback(callback);//Key 2
        }
}
------------------------------------------------------------------------------------------------------------------------
Choreographer class
Choreographer{
 public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);//Key 3
    }

 public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
          ...........
        postCallbackDelayedInternal(CALLBACK_ANIMATION,callback, FRAME_CALLBACK_TOKEN, delayMillis);//Key 4
    }
 private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
             ......
            synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis; // 0 + now 
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);//Key 5
            if (dueTime <= now) {
                scheduleFrameLocked(now); //Key 6
            } else {
            ......}}}

 private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) { // USE_VSYNC = true
              ....
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();//Key 7
                }  }
              ......}}  }
    @UnsupportedAppUsage
 private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();//Key 8
    }
-------------------------------------------------------------------------------------------------------------
DisplayEventReceiver class
DisplayEventReceiver{
   @FastNative
    private static native void nativeScheduleVsync(long receiverPtr);//Key 10
public void scheduleVsync() {
            ............
            nativeScheduleVsync(mReceiverPtr);//Key 9
    }
}
}

In the end, this is a Native method..... Because this involves the problem of interface rendering. When we receive a new Vsync signal, it will call back the doFrame(long frameTimeNanos) function of the FrameCallback interface.

We passed in the callback method mFrameCallback from the first key 1getProvider().postFrameCallback(mFrameCallback)
Then to the key 5 mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); action is our incoming mFrameCallback

The code is as follows

public void addCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
            ....
        }
//Create a CallbackRecord 
private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
        CallbackRecord callback = mCallbackPool;
        if (callback == null) {
            callback = new CallbackRecord();
        } else {
            mCallbackPool = callback.next;
            callback.next = null;
        }
        callback.dueTime = dueTime;
        callback.action = action;
        callback.token = token;
        return callback;
    }
 private static final class CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;

        @UnsupportedAppUsage
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);//Key point 1.1
            } else {
                ((Runnable)action).run();
            }
        }
    }

(framecallback) action. Doframe (frametimenanos); / / key 1.1 token = = frame ﹐ callback ﹐ token.

At this time, it will call back to the callback method mFrameCallback of getProvider().postFrameCallback(mFrameCallback) in the initial AnimationHandler class

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

private void doAnimationFrame(long frameTime) {
        long currentTime = SystemClock.uptimeMillis();
        final int size = mAnimationCallbacks.size(); //Add a callback in the first code block, at least one.
        for (int i = 0; i < size; i++) {
            final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
            ......
            if (isCallbackDue(callback, currentTime)) {
                callback.doAnimationFrame(frameTime); //Key 2 / / if you look back, you can see that the frameTime type is VaueAnimator
               ....................
            }
        }
      .....
    }
}
------------------------------------------------------------------------------------------------------------------------
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
public final boolean doAnimationFrame(long frameTime) {
      if (mStartTime < 0) {
            // The logic of the first frame
            mStartTime = mReversing
                    ? frameTime
                    : frameTime + (long) (mStartDelay * resolveDurationScale());
        }

        // Status bits for processing pause/resume
        if (mPaused) {
            mPauseTime = frameTime;
            removeAnimationCallback();//The status of the pause cancels the callback
            return false;
        } else if (mResumed) {
            mResumed = false;
          ......
        }
         .....
mLastFrameTime = frameTime;
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);//Key point 3

        if (finished) {
            endAnimation();
        }
        return finished;
    }
//Calculate the corresponding value, and deal with animation repetition and other related logic here
boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            final long scaledDuration = getScaledDuration();
            final float fraction = scaledDuration > 0 ?
                    (float)(currentTime - mStartTime) / scaledDuration : 1f;
            final float lastFraction = mOverallFraction;
            final boolean newIteration = (int) fraction > (int) lastFraction;
            final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                    (mRepeatCount != INFINITE);
          ......
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);//Key point 4
        }
        return done;
    }

    void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);//The corresponding interpolator calculates the fractional value
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);//Input the score of interpolator, and then give it to the estimator to calculate the final value
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }
}

mUpdateListeners.get(i).onAnimationUpdate(this); when we go here, we can call back to the final value when the API is used. The above is the source logic interpretation of ValueAnimator we found.

On interpolator and estimator

void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);//Key 1 corresponds to the interpolator to calculate the fractional value
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);//Key 2: input the score of interpolator, and then give it to the estimator to calculate the final value
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

From the above logic, we can see that fraction = mminterpolator. Getinterpolation (fraction); / / when the corresponding interpolator calculates the fraction value, first calculate the fraction value of the 0-1 interval of the interpolator, and then pass the value to mValues[i].calculateValue(fraction); for calculation
Key 1: the code is as follows

private static final TimeInterpolator sDefaultInterpolator =new AccelerateDecelerateInterpolator();
mInterpolator = sDefaultInterpolator 
fraction = mInterpolator.getInterpolation(fraction);//The corresponding interpolator calculates the fractional value
public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolatorFactory {
......
    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }
......
}

Now let's take a look at Evaluate's
Key 2: the code is as follows

mValues[i].calculateValue(fraction); //Input the score of interpolator, and then give it to the estimator to calculate the final value
---------------------------------------------------------------------
PropertyValuesHolder class
PropertyValuesHolder{
void calculateValue(float fraction) {
        Object value = mKeyframes.getValue(fraction);
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }
}

What is the type of mKeyframes??

In the above source code interpretation of ValueAnimator.ofObject, you can see that in setobjectvalues - > propertyvaluesholder.ofobject ("", null, values) - > setobjectvalues, you can see that mKeyframes is of type KeyframeSet.

public class KeyframeSet implements Keyframes {
public Object getValue(float fraction) {
        // Special-case optimization for the common case of only two keyframes
        if (mNumKeyframes == 2) {
            if (mInterpolator != null) {
                fraction = mInterpolator.getInterpolation(fraction);
            }
            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
       .......
        Keyframe prevKeyframe = mFirstKeyframe;
        for (int i = 1; i < mNumKeyframes; ++i) {
            Keyframe nextKeyframe = mKeyframes.get(i);
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                final float prevFraction = prevKeyframe.getFraction();
                float intervalFraction = (fraction - prevFraction) /
                    (nextKeyframe.getFraction() - prevFraction);
                // Apply interpolator on the proportional duration.
                if (interpolator != null) {
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                        nextKeyframe.getValue());
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't reach here
        return mLastKeyframe.getValue();
    }
}

From the above logic, you can see that maevaluator.evaluate (frame, mfirstkeyframe. Getvalue()); anyway, calculate various corresponding values and give them to the estimator to calculate the final value and return it, and assign it to the mmanimatedvalue in the previous code block.
From this point we can see
When we call the API interface

mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, 0x80000000);//Start reading 1
mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
    public void onAnimationUpdate(ValueAnimator animation) {
       final int c = (Integer) animation.getAnimatedValue();
          mColor.setColor(c);
    }
});
mColorAnim.setDuration(2000);
mColorAnim.start(); //Start reading 2

animation.getAnimatedValue(); here we get the final value calculated by our estimator.

Summary: interpolator is to calculate the score, i.e. the score in the middle of 0-1, and estimator is the attribute value we use last. The estimator is an attribute value that we need at this moment by multiplying our attribute value with the value of the interpolator

This code is relatively rough through the beginning to the end of the animation, and then determine some assistance classes of the animation. Many details are not mentioned, such as the problem of Vsyn callback screen rendering in the middle. This is a lot of things. If you say it one by one in the code, it's too long. Next time I look at that screen rendering article, I'll write another blog post.

Published 38 original articles, won praise 1, visited 4848
Private letter follow

Tags: Attribute

Posted on Mon, 16 Mar 2020 04:24:25 -0700 by UrbanCondor