SoFunction
Updated on 2025-03-11

Android in-depth analysis of attribute animation source code

1. First look at the code implementation of an animation

ObjectAnimator alpha = (view, "alpha", 1, 0,1);
(500);
();

The code is very simple. The above three lines of code can enable an animation of transparency changes. So how is the Android system implemented? Enter source code analysis.

1) Look at the first line of code:

ObjectAnimator alpha = (view, "alpha", 1, 0,1);

Create an ObjectAnimator object and set the values ​​array to the anim object.

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        (values);
        return anim;
}

ObjectAnimator constructor. Assign the passed View object and propertyName to the member variable.

private ObjectAnimator(Object target, String propertyName) {
        // Assign the passed View object to the member variable mTarget        setTarget(target);
        // Assign propertyName to the member variable mPropertyName        setPropertyName(propertyName);
}

Note why this mTarget uses a soft reference?

That's to prevent memory leaks in the Activity. Because the Activity has been exited, but the animation may not have been executed yet. If the View cannot be released at this time, it will cause the Activity memory leak.

private WeakReference<Object> mTarget;
public void setTarget(@Nullable Object target) {
    final Object oldTarget = getTarget();
    if (oldTarget != target) {
        if (isStarted()) {
            cancel();
        }
        // Assign the incoming View object to mTarget        mTarget = target == null ? null : new WeakReference<Object>(target);
        mInitialized = false;
    }
}

Let's see what the second line of code does? (values);

For the first time, mValues==null, mProperty==null, so this line of code will be executed. setValues((mPropertyName, values)).

public void setFloatValues(float... values) {
    if (mValues == null ||  == 0) {
        if (mProperty != null) {
            setValues((mProperty, values));
        } else {
            setValues((mPropertyName, values));
        }
    } else {
        (values);
    }
}

setValue assigns the obtained PropertyValuesHolder array to the member variable PropertyValuesHolder[] mValues;

Let’s look at (mPropertyName, values));

First call the super constructor and assign propertyName to the mPropertyName of the parent class.

 public FloatPropertyValuesHolder(String propertyName, float... values) {
    super(propertyName);
    setFloatValues(values);
}

Then call setFloatValues(values);

public void setFloatValues(float... values) {
    (values);
    //Firmly convert mKeyframes to mFloatKeyframes    mFloatKeyframes = () mKeyframes;
}
//Create the parent class method and create a KeyframeSet object, assigning it to mKeyframespublic void setFloatValues(float... values) {
    mValueType = ;
    mKeyframes = (values);
}

(values); This line of code creates a collection of keyframes.

public static KeyframeSet ofFloat(float... values) {
    boolean badValue = false;
    int numKeyframes = ;
    //Create an array of FloatKeyFrames of value length    FloatKeyframe keyframes[] = new FloatKeyframe[(numKeyframes,2)];
    // If numKeyframes==1, there is actually no View and no animation.  If the length of the values ​​transmitted is 1, an error will be reported.    if (numKeyframes == 1) {
        keyframes[0] = (FloatKeyframe) (0f);
        keyframes[1] = (FloatKeyframe) (1f, values[0]);
        if ((values[0])) {
            badValue = true;
        }
    } else {
        //The following code is the key. Keyframe ofFloat(float fraction, float value) is to create keyframes.        //fraction English word means part, and as a parameter, it means: from the animation revelation position to the current position, the percentage of the entire animation occupied.        //value is the attribute value corresponding to a certain part.        // For example, the value passed in is 1.0f 2.0f 3.0f 4.0f, 5.0f.  The entire animation has 5 values.  Because 1.0 is the initial value, it takes 4 steps to complete the entire animation.         //From 1-2, 2-3, 3-4, 4-5; 4 parts.         //The 0th position is the starting position, so the part he is located is 0.  The first position is one quarter, and the second position is two quarters...         //The i-th position, the entire animation part is i/(i-1).  The attribute value of the animation corresponding to this position is value[i]        //So the purpose of this keyframes[] array is to save the percentage of the key positions of the animation and the attribute values ​​corresponding to the key positions.        keyframes[0] = (FloatKeyframe) (0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (FloatKeyframe) ((float) i / (numKeyframes - 1), values[i]);
            if ((values[i])) {
                badValue = true;
            }
        }
    }
    return new FloatKeyframeSet(keyframes);
}

Until then, the first line of code has been executed.

(view, "alpha", 1, 0,1)

Assign the view to the ObjectAnimator member variable.

Assigning propertyName to PropertyValuesHolder will reflect its set method through the property name to modify the property value.

Create a KeyframeSet, a collection of keyframes. Convert the value array into the corresponding keyframe set, calculate the attribute value corresponding to the current time through the execution time of the animation, and then call the set attribute method of the view to achieve the purpose of forming an animation.

The code for this piece will be seen later.

2). Look at the second line of code ();

The parent class of ObjectAnimator is ValueAnimator. The methods called in start() will jump around in the subclass and parent class, which also increases the difficulty of reading.

First look at the ValueAnimator#start(boolean playBackwards) method

addAnimationCallback: Register the callback function with Choreographer. We know that Choreographer can accept Vsync signals, 16.66ms once, which is also the time for the screen to refresh once. In this way, when the screen is refreshed, you can register the callback function with Choreographer to update the animation.

 private void start(boolean playBackwards) {
        //Animators must run in a thread where the Looper cannot be empty, because the animation needs to involve Choreographer.        if (() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mStartTime = -1;
        //This is a callback function.  This piece is called back by Choreographer and will be analyzed later.        addAnimationCallback(0);
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            //Start the animation.            startAnimation();
        }
    }

Let’s look at the startAnimation method() first, and initAnimation() will be called initAnimation() in this method;

In this case, the subclass ObjectAnimator will be called first, and then the initAnimation method of the ValueAnimator of the parent class will be called.

First look at the initAnimation() of the subclass. This method reflects the set property method of the view based on propertyName.

void initAnimation() {
     if (!mInitialized) {
        //Get the target first, that is, the view object.         final Object target = getTarget();
         if (target != null) {
         // PropertyValuesHolder[] mValues; This value is the collection of PropertyValuesHolder.             final int numValues = ;
             for (int i = 0; i < numValues; ++i) {
                //The property value is passed in the PropertyValuesHolder. The following line of code reflects the set method of the view based on the property value.                //Use the set method, the change of the property value of the view can be dynamically changed.                 mValues[i].setupSetterAndGetter(target);
             }
         }
         //Calling the initAnimation() method of the parent class         ();
     }
}

Let’s look at the initAnimation method of the parent class ValueAnimator. The init() method of PropertyValuesHolder is called.

In the init method, an estimator is set to the KeyframeSet keyframe collection. This is used to calculate the attribute value. You will see the specific calculation method later.

void initAnimation() {
    if (!mInitialized) {
        int numValues = ;
        for (int i = 0; i < numValues; ++i) {
        //Call PropertyValuesHolder#init method mValues[i].init();        }
        mInitialized = true;
    }
}
 void init() {
    if (mEvaluator == null) {
        //Get an estimator        mEvaluator = (mValueType == ) ? sIntEvaluator :
                (mValueType == ) ? sFloatEvaluator : null;
    }
    if (mEvaluator != null) {
        //Set an estimator into the KeyframeSet, which is used to calculate the attribute value of the animation at a certain moment.        (mEvaluator);
    }
}
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
public class FloatEvaluator implements TypeEvaluator<Number> {
    //This function returns the result of linearly interpolating the start and end values
    This method returns a linear result between the beginning and end of the animation。It's actually a one-time equation,To calculate the current position of the animation。
    //result = x0 + t * (v1 - v0)
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = ();
        return startFloat + fraction * (() - startFloat);
    }
}

At this point, the code of initAnimation has been executed. The main work can be summarized into two points:

1. Call the setupSetterAndGetter method of PropertyValuesHolder and get the setter method of the View through reflection.

2. Set an estimator to the KeyframeSet to calculate the attribute value of a certain moment of the animation.

3) Next, let’s look at ValueAnimator#addAnimationCallback

This method is to set a callback function to Choreographer, which will callback every 16.66ms to refresh the animation.

A callback collection is also set up. In the Choreographer callback function, the callback function in the callback collection is called to realize the refresh of attribute animation.

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    //getAnimationHandler is the AnimationHandler created above.    //Use this as the callback of AnimationFrameCallback, it will call back doAnimationFrame(long frameTime)    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
//AnimationHandler#addAnimationFrameCallback
getProvider()What I got isMyFrameCallbackProvider。
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (() == 0) {
        //Add a callback function mFrameCallback to Choreographer        getProvider().postFrameCallback(mFrameCallback);
    }
    //Add the added callback function to a collection of callbacks.    if (!(callback)) {
        (callback);
    }
}

Let's first look at getProvider().postFrameCallback(mFrameCallback); This is to register a callback with Choreographer.

final Choreographer mChoreographer = ();
    //This line of code adds a callback function to the choreographer.    public void postFrameCallback( callback) {
        (callback);
}
Choreographermiddle
public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);
}

The following line of code is to add a callback function with CallBackType CALLBACK_ANIMATION and Token to FRAME_CALLBACK_TOKEN to Choreographer. callback is the mFrameCallback that is transmitted in.

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

The intermediate calling process is omitted. . . The code for this section has been analyzed in the Choreographer source code.

MyFrameCallbackProvider#postFrameCallback is to add a callback function to Choreographer. We know that Choreographer calls these callback functions after receiving the Vsync signal.

 void doFrame(long frameTimeNanos, int frame) {
 doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
 }
Will be transferred here in the end,According to the abovetoken,Convert to different callback functions,Call different methods。
//When drawing the View, the callback of the else branch is called//In the animation, the instance of mFrameCallback is passed in, and the doFrame method will be called public void run(long frameTimeNanos) {
    if (token == FRAME_CALLBACK_TOKEN) {
        ((FrameCallback)action).doFrame(frameTimeNanos);
    } else {
        ((Runnable)action).run();
    }
}
private final  mFrameCallback = new () {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (() > 0) {
            //Register callback with Choreographer again and wait until the next Vsync signal comes,            //For 60Hz screen, the refresh time interval is 16.66ms, which is the time interval of Vsync callback            //That is, the attribute animation will change once in 16.66 milliseconds in 16.66 milliseconds            getProvider().postFrameCallback(this);
        }
    }
};

In Choreographer, each 16.6ms will call back the doFrame method(). In the doAnimationFrame method, the registered callback collection will be called back.

private void doAnimationFrame(long frameTime) {
    long currentTime = ();
    final int size = ();
    for (int i = 0; i < size; i++) {
        final AnimationFrameCallback callback = (i);
        if (callback == null) {
            continue;
        }
        //Transf the mAnimationCallbacks, call the callBack callback function,        //This callback function is the doAnimationFrame of ValueAnimator        if (isCallbackDue(callback, currentTime)) {
            (frameTime);
        }
    }
}

doAnimationFrame is a callback function of AnimationFrameCallback, implemented by ValueAnimator.

 public final boolean doAnimationFrame(long frameTime) {
       //frameTime This time is the time transmitted from Choreographer.       //Record as the time of the last animation refresh        mLastFrameTime = frameTime;
        final long currentTime = (frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        return finished;
 }
 public final boolean doAnimationFrame(long frameTime) {
       //frameTime This time is the time transmitted from Choreographer.       //Record as the time of the last animation refresh        mLastFrameTime = frameTime;
        final long currentTime = (frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        return finished;
 }
boolean animateBasedOnTime(long currentTime) {
     boolean done = false;
     if (mRunning) {
        //Get the total time         final long scaledDuration = getScaledDuration();
         //Calculate how much the animation currently performs.  (currentTime - mStartTime) The execution time of the animation         //Divide by the total scaledDuration time, get the part that has been executed. If it is a duplicate animation, this value may be greater than 1.         final float fraction = scaledDuration > 0 ?
                 (float)(currentTime - mStartTime) / scaledDuration : 1f;
        //The following is to correct the fraction through calculation, subtract the repeated execution part, and get the real part to execute in one animation.         mOverallFraction = clampFraction(fraction);
         float currentIterationFraction = getCurrentIterationFraction(
                 mOverallFraction, mReversing);
         animateValue(currentIterationFraction);
     }
     return done;
 }

Note animateValue, this method is implemented in both parent class ValueAnimator and subclass ObjectAnimator.

So here we first call the method of the subclass ObjectAnimator.

//This method is a subclass method calledvoid animateValue(float fraction) {
     final Object target = getTarget();
     if (mTarget != null && target == null) {
         cancel();
         return;
     }
     //Call the parent class's method first     (fraction);
     //Go back to subclass     int numValues = ;
     for (int i = 0; i < numValues; ++i) {
        //Set the changed property value for the View         mValues[i].setAnimatedValue(target);
     }
 }

Let’s look at the method first. This method is to calculate the attribute value after the animation changes.

 void animateValue(float fraction) {
     //Modify through the interpolation.  If no interpolation is set, the fraction changes are at a constant speed.     //After the calculation of the interpolation, the changes in fraction will show the effect of acceleration and deceleration.     fraction = (fraction);
     mCurrentFraction = fraction;
     int numValues = ;
     for (int i = 0; i < numValues; ++i) {
         //PropertyValuesHolder[] mValues, because a View can have multiple attribute animations, this is stored in an array.         mValues[i].calculateValue(fraction);
     }
 }
AccelerateDecelerateInterpolator Interpolation
public float getInterpolation(float input) {
    return (float)(((input + 1) * ) / 2.0f) + 0.5f;
}
void calculateValue(float fraction) {
    //mKeyframes is the keyframe collection created earlier KeyframeSet    Object value = (fraction);
   // Assign the obtained value to mAnimatedValue    mAnimatedValue = mConverter == null ? value : (value);
}

The following method is to truly calculate the changed property value. Calculated by the valuator mEvaluator.

public Object getValue(float fraction) {
    //Record the first keyframe as the previous keyframe    Keyframe prevKeyframe = mFirstKeyframe;
    for (int i = 1; i < mNumKeyframes; ++i) {
        //Get the next keyframe        Keyframe nextKeyframe = (i);
        if (fraction < ()) {
            final TimeInterpolator interpolator = ();
            //Get the previous keyframe, the corresponding part            final float prevFraction = ();
            //fraction - prevFraction What is the current part to be executed from the previous keyframe.            //() - prevFraction, how many frames are there            //Divide the two and get the proportion of the current part in this frame            float intervalFraction = (fraction - prevFraction) /
                (() - prevFraction);
            if (interpolator != null) {
                //Modify the size of this part through the interpolation                intervalFraction = (intervalFraction);
            }
            //Use the estimator to calculate how much the attribute value needs to change to            //This valuator is the FloatEvaluator or IntEvaluator assigned above            return (intervalFraction, (),
                    ());
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't reach here
    //It should not be executed here. The for loop above should return the size of the current animation and property change.    return ();
}

Calculate the property value of the view through the estimator.

public Float evaluate(float fraction, Number startValue, Number endValue) {
    float startFloat = ();
    //Calculate the current attribute value through a one-in-one equation.    return startFloat + fraction * (() - startFloat);
}

At this point, the attribute value after the animation needs to change has been calculated.

passmValues[i].setAnimatedValue(target);Used to modify the value size of the View property.

 void setAnimatedValue(Object target) {
       //The setter method of view has been obtained through reflection      if (mSetter != null) {
          try {
               //Get the attribute value size,              mTmpValueArray[0] = getAnimatedValue();
             //Change the size of the view attribute value through reflection              (target, mTmpValueArray);
          } catch (InvocationTargetException e) {
              ("PropertyValuesHolder", ());
          } catch (IllegalAccessException e) {
              ("PropertyValuesHolder", ());
          }
      }
  }
Object getAnimatedValue() {
    return mAnimatedValue;
}

At this point, the entire execution process of android attribute animation has been analyzed.

The following points can be summarized:

It is the parent class, and ObjectAnimator is a subclass. It encapsulates a target, which is a view object.

, has attribute name and attribute value. The setter method of reflecting the view through the genus name to dynamically modify the attribute value.

, is a keyframe collection, encapsulates the value of the definition animation as a value array, and each value is recorded as a keyframe FloatKeyframe.

4. Through the interpolation, the speed of the property changes can be changed, and the size of the property value can be calculated through the estimator.

5. Register a callback for Choreographer, and it will change the size of the view attribute value every 16.66ms callback. Change is calculated by fraction, and then the changed attribute value size is obtained through calculation.

In this way, dynamically change the size of the view attribute value, and a animation is formed continuously.

This is the article about Android's in-depth analysis of attribute animation source code. For more related Android attribute animation content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!