深入分析属性动画的实现原理

这篇文章的起因,是因为之前在学习属性动画时,自己查了很多资料,也看了很多大佬关于属性动画原理分析的文章,其中发现几个问题:

  1. 很多优秀的文章分析都是基于API版本25之前的源码,而在API25之后的属性动画的实现方式做了较大的改动,其中最主要的部分就是AnimationHandler;
  2. 网络上也有些关于API25之后源码分析的文章,我个人觉得但大多数都不够全面,也不够深入;

基于这两个原因我想自己分享一下自己在学习属性动画时候的内容,而且在写的过程中也是一种巩固和提升。
先介绍动画库中几个核心类(以下内容都是基于API28版本下)

  • ValueAnimator:Animator的子类,实现了动画的整个处理逻辑,也是熟悉动画最为核心的类
  • ObjectAnimator:对象属性动画的操作类,继承自ValueAnimator,通过该类使用动画的形式操作对象的属性
  • TimeInterpolator:时间插值器,它的作用是根据时间流逝的百分比来计算当前属性值改变的百分比,系统预置的有线性插值器、加速减速插值器、减速插值器等。
  • TypeEvaluator:TypeEvaluator翻译为类型估值算法,它的作用是根据当前属性改变的百分比来计算改变后的属性值
  • Property:属性对象、主要定义了属性的set和get方法
  • PropertyValuesHolder:持有目标属性Property、setter和getter方法、以及关键帧集合的类。
  • KeyframeSet:存储一个动画的关键帧集

按照惯例以通过具体的例子作为分析入口,例子的内容很简单给view设置背景从红色到蓝色变化的动画,代码如下:

public void startAnimator(View view){

    ObjectAnimator animator = ObjectAnimator.ofInt(view, "backgroundColor", 0XFFF8080, 0xFF8080FF);
    animator.setEvaluator(new ArgbEvaluator());//设置估值器
    animator.setRepeatMode(ValueAnimator.REVERSE);//方向循环
    animator.setRepeatCount(ValueAnimator.INFINITE);//无限重复
    animator.setDuration(3000);
    animator.start();

}

先从它的入口开始,即ObjectAnimator.ofInt入手:

public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setIntValues(values);//设置属性值
    return anim;
}

private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);//设置目标
    setPropertyName(propertyName);//设置目标属性名称 也就是backgroundColor
}

在ObjectAnimato的ofInt函数中会构建属性动画对象,并设置动画的目标对象、目标属性名称还有属性值。这个属性values是个可变参数——如果是一个参数,那么该参数为目标值;如果是两个参数,一个是起始值,另一个是目标值。而如何设置属性值的关键就在anim.setIntValues(values)函数:

@Override
public void setIntValues(int... values) {
     //这时候的mValues还是为空
    if (mValues == null || mValues.length == 0) {
        //mProperty也是为空,我们并没有用到Property
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofInt(mProperty, values));
        } else {
            setValues(PropertyValuesHolder.ofInt(mPropertyName, values));//这里就是才是我们需要分析的入口
        }
    } else {
        super.setIntValues(values);
    }
}

在anim.setIntValues函数里出现了个核心类——PropertyValuesHolder。它的作用就是保存属性名称和该属性的setter、getter方法,以及它的目标值。下面贴出该类在实例中分析所需要的相关代码:

public class PropertyValuesHolder implements Cloneable {
    //属性名称
    String mPropertyName;
    //属性对象
    protected Property mProperty;
    //属性的setter方法
    Method mSetter = null;
    //属性的getter方法
    private Method mGetter = null;
    //属性类型 
    Class mValueType;
    //动画的关键帧,即动画在规定的时间内动画帧的集合,它保存了每一帧该属性对应的值
    Keyframes mKeyframes = null;
    public static PropertyValuesHolder ofInt(String propertyName, int... values) {
       //1:构建IntPropertyValuesHolder对象
        return new IntPropertyValuesHolder(propertyName, values);
    }
    static class IntPropertyValuesHolder extends PropertyValuesHolder {
        Keyframes.IntKeyframes mIntKeyframes;//IntKeyframeSet
        //2:构造函数
        public IntPropertyValuesHolder(String propertyName, int... values) {
            super(propertyName);//设置属性名称
            //设置属性值
            setIntValues(values);//设置属性值
        }
       //3:设置动画的目标值
        @Override
        public void setIntValues(int... values) {
            super.setIntValues(values);//调用父类的setIntValues
            mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
        }
        //计算当前的动画值

        @Override

        void calculateValue(float fraction) {

            mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);

        }

    }

}

PropertyValuesHolder算是一个辅助类,统一管理属性名称和属性值。例如我们这里调用PropertyValuesHolder.ofInt()函数,会生成正在的属性类对象是IntPropertyValuesHolder,并给其设置了属性名称、属性值。而这里的关键其实是注释3中的setIntValues函数,它是先调用其父类的setIntValues方法,再把mKeyframes设置给mIntKeyframes。那我们就一起看看其父类的setIntValues函数做了什么:

public void setIntValues(int... values) {

    mValueType = int.class;

    //获取到动画的关键帧

    mKeyframes = KeyframeSet.ofInt(values);

}

这里又调用KeyframeSet的ofInt函数,继续跟踪下去:

public static KeyframeSet ofInt(int... values) {

    int numKeyframes = values.length;

    IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];

    if (numKeyframes == 1) {//设置1个目标值

        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);

        keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);

    } else {//设置大于1个目标值

        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);//起始值

        for (int i = 1; i < numKeyframes; ++i) {//遍历单独设置每个关键帧

            keyframes[i] =

                    (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);

        }

    }

    return new IntKeyframeSet(keyframes);

}

可以看到关键帧就是在ofInt函数中计算的。首先values是个可变参数,如果设置的是一个值也就是numKeyframes==1,那么这个值就是最终值,起始值默认为0。如果设置的目标值个数大于1,按照比例设置各个关键帧的值:


关键帧

这些关键帧都会存储在IntKeyframeSet对象中,再回到IntPropertyValuesHolder的setIntValues方法中mIntKeyframes属性也设置好了值。分析到这关键帧都设置完成了,我们需要回到ObjectAnimator的start方法,开始启动动画的分析:

@Override
public void start() {
   AnimationHandler.getInstance().autoCancelBasedOn(this);
   //代码...
   super.start();
}

调用父类的start方法也就是ValueAnimator的start方法:

private void start(boolean playBackwards) {
    //判断ui线程的looper是否是否为空    
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mSelfPulse = !mSuppressSelfPulseRequested;
    // Special case: reversing from seek-to-0 should act as if not seeked at all.
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            // Calculate the fraction of the current iteration.
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            //无限循环次数的计算
            mSeekFraction = 1 - fraction;
        } else {
            //有限循环次数的计算
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }

    /*步骤1:初始化值*/
    //标识动画是否已启动
    mStarted = true;
    //标识动画是否处于暂停状态
    mPaused = false;
    mRunning = false;
    //跟踪请求结束动画的标志。
    mAnimationEndRequested = false;
    mLastFrameTime = -1;
   mFirstFrameTime = -1;
    mStartTime = -1;
    //步骤2:**关键点** 
    addAnimationCallback(0);
    //步骤3:如果没有延迟启动,初始化动画设置一些监听,并设置time为0的当前帧数的目标值
    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        startAnimation();
        if (mSeekFraction == -1) {
            //设置一些监听,并设置time为0的当前帧数的目标值
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

先总体归纳一下在start方法中做了几件事情:

  1. 初始化动画的各种标志位;
  2. 最主要的的关键点:注册callBack回调,获取动画的下一帧回调;
  3. 如果没有延迟启动,初始化动画设置一些监听,并设置time为0的当前帧数的目标值

然后我们单独分析各个步骤,步骤一:没什么特别的初始化了一些值,例如mStarted表示动画是否启动、mPaused标识动画是否处于暂停状态、mReversing表示是否要reverse等。步骤二是最重要的一步先不分析,重要的事情永远留在最后。步骤三中有startAnimation和setCurrentPlayTime(0)两个关键的方法。通常情况从命名上我们都以为startAnimation这是开启动画的关键。在开始看源码的的时候我也这么认为,可分析了半天发现其实不然,但也做了很多关键的是事情。那我们先看看startAnimation方法到底做了什么处理:

private void startAnimation() {
    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                System.identityHashCode(this));
    }
    mAnimationEndRequested = false;
    initAnimation();//初始化动画
    mRunning = true;
    if (mSeekFraction >= 0) {
        mOverallFraction = mSeekFraction;
    } else {
        mOverallFraction = 0f;
    }
    if (mListeners != null) {
        notifyStartListeners();
    }
}

在startAnimation方法中最重要的一步就是initAnimation初始化动画,这里需要注意的一点是:这里调用的是ObjectAnimator.initAnimation。我自己在分析的过程中,就因为这个事情困了我好长时间。因为在这里的startAnimation()方法所属在ValueAnimator类中,我们通过鼠标点击,必然直接进入的是ValueAnimator的initAnimation函数,而这里真正的调用对象是ObjectAnimator,所以我们需要的是调用对象的initAnimation函数:

// class==>ObjectAnimator
void initAnimation() {
    if (!mInitialized) {
        // mValueType may change due to setter/getter setup; do this before calling super.init(), 
        // which uses mValueType to set up the default type evaluator.
        final Object target = getTarget();
        if (target != null) {
            final int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(target);//1:设置属性的setter和getter方法
           }
        }
        super.initAnimation();//2:调用父类的initAnimation函数
    }
}

在上面注释1中会调用PropertyValuesHolder的setupSetterAndGetter方法初始化属性的setter和getter方法,然后再调用ValueAnimator的initAnimation方法。
那我们先看一下如何设置setter和getter方法:

void setupSetterAndGetter(Object target) {
    //省略 property为不为空的代码 
    if (mProperty == null) {//property为空的情况下 在实例是我们需要分析的
        Class targetClass = target.getClass();
        if (mSetter == null) {
            setupSetter(targetClass);//初始化属性的setter 方法
        }
        List<Keyframe> keyframes = mKeyframes.getKeyframes();// 获得前面设置的mKeyframes关键帧
        int keyframeCount = keyframes == null ? 0 : keyframes.size();
        for (int i = 0; i < keyframeCount; i++) {
            Keyframe kf = keyframes.get(i);
            if (!kf.hasValue() || kf.valueWasSetOnStart()) {
                if (mGetter == null) {
                   setupGetter(targetClass);//初始化属性的getter 方法
                   if (mGetter == null) {
                        // Already logged the error - just return to avoid NPE
                        return;
                    }
                }
                try {
                    Object value = convertBack(mGetter.invoke(target));
                    kf.setValue(value);
                    kf.setValueWasSetOnStart(true);
                } catch (InvocationTargetException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                } catch (IllegalAccessException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                }
            }
        }
    }
}

这里以分析setter方法为例,如果mSetter方法为空就通过setupSetter函数初始化目标属性的setter方法,继续跟踪下去:

@Override
void setupSetter(Class targetClass) {
    if (mProperty != null) {
        return;
    }
    // Check new static hashmap<propName, int> for setter method
    synchronized(sJNISetterPropertyMap) {
        HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);//先从sJNISetterPropertyMap缓存中获取
        boolean wasInMap = false;
        if (propertyMap != null) {
            wasInMap = propertyMap.containsKey(mPropertyName);
            if (wasInMap) {
                Long jniSetter = propertyMap.get(mPropertyName);
                if (jniSetter != null) {
                    mJniSetter = jniSetter;
                }
            }
        }
        //map如果缓存中没有
        if (!wasInMap) {
            //  创建属性set方法名
            String methodName = getMethodName("set", mPropertyName);
            try {
                //调用JNI方法获取 set方法是否存在
                mJniSetter = nGetIntMethod(targetClass, methodName);//调用的是Native方法
            } catch (NoSuchMethodError e) {
                // Couldn't find it via JNI - try reflection next. Probably means the method
                // doesn't exist, or the type is wrong. An error will be logged later if
                // reflection fails as well.
            }
            //保存到缓存避免多次创建
            if (propertyMap == null) {
                propertyMap = new HashMap<String, Long>();
                sJNISetterPropertyMap.put(targetClass, propertyMap);
            }
            propertyMap.put(mPropertyName, mJniSetter);
        }
    }
    if (mJniSetter == 0) {
        // Couldn't find method through fast JNI approach - just use reflection
        super.setupSetter(targetClass);
    }
}
static String getMethodName(String prefix, String propertyName) {
    if (propertyName == null || propertyName.length() == 0) {
        // shouldn't get here
        return prefix;
    }
    //属性名的第一个字母大写
    char firstLetter = Character.toUpperCase(propertyName.charAt(0));
    //从第一个位置substring
    String theRest = propertyName.substring(1);
    //拼接属性set方法 最后得到setBackground
    return prefix + firstLetter + theRest;
}
//native 方法
native static private long nGetIntMethod(Class targetClass, String methodName);

以setter的创建过程为例:

  1. 先从sJNISetterPropertyMap缓存中根据目标对象的classlei x获取目标对象的属性propertyMap
  2. 如果propertyMap中有目标属性且属性的set方法也存在,就直接获取jniSetter
  3. 如果不存在则通过调用getMethodName方法,拼接属性的setter方法(set+属性名第一个字母大写+属性名后面的字母)
  4. 通过调用native方法获取 jniSetter

分析到这就设置完了属性的setter和getter方法,我们再次回到start()方法中,这时候开始super.initAnimation()的调用:

// class==>ValueAnimator
void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].init();//PropertyValuesHolder的init方法
        }
        mInitialized = true;//
    }
}

PropertyValuesHolder的init初始化,并把mInitialized设置true:

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

在init方法主要就是给mKeyframes关键帧设置估值器,如实例这种的ArgbEvaluator。

到这里ValueAnimator的startAnimation()方法都已经全部分析完了,接下来就是分析setCurrentPlayTime(0)方法:

public void setCurrentPlayTime(long playTime) {
    //playTime==0
    float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;//fraction==0
    setCurrentFraction(fraction);
}

public void setCurrentFraction(float fraction) {
    initAnimation();
    fraction = clampFraction(fraction);//fraction为0
    mStartTimeCommitted = true; // do not allow start time to be compensated for jank
    if (isPulsingInternal()) {//是否启动动画循环 显然不是
        long seekTime = (long) (getScaledDuration() * fraction);
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        // Only modify the start time when the animation is running. Seek fraction will ensure
        // non-running animations skip to the correct start time.
        mStartTime = currentTime - seekTime;
    } else {
        // If the animation loop hasn't started, or during start delay, the startTime will be
        // adjusted once the delay has passed based on seek fraction.
        mSeekFraction = fraction;    
    mOverallFraction = fraction;
    final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
    animateValue(currentIterationFraction);//把经过的每一帧转换为动画值
}
//
private float clampFraction(float fraction) {
    if (fraction < 0) {
       fraction = 0;
    } else if (mRepeatCount != INFINITE) {//实例中设置的mRepeatCount为INFINITE
        fraction = Math.min(fraction, mRepeatCount + 1);
    }
    return fraction;//最后结果还是0
}

这里涉及到各种计算,把fraction转化为currentIterationFraction,我就不做详细的分析来。fraction为0时,其实就是动画刚开始time为0,算得的currentIterationFraction也为0。再调用animateValue函数把这时的0帧数转化动画值也就是起始值。同样这里调用的也是ObjectAnimation的animateValue函数:

//ObjectAnimation
void animateValue(float fraction) {
    final Object target = getTarget();
    if (mTarget != null && target == null) {
        // We lost the target reference, cancel and clean up. Note: we allow null target if the
        /// target has never been set.
        cancel();
        return;
    }
    super.animateValue(fraction);//计算属性值
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(target);//更新属性值
    }
}

在animateValue方法中先是调用了super.animateValue(fraction)方法计算属性值,再调用PropertyValuesHolder的setAnimatedValue方法更新属性值,所以我们还得要回到ValueAnimation的animateValue函数中:

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

分析到这终于看见TimeInterpolator时间插值器的身影,而时间插值器的作用是根据时间流逝的百分比来计算当前属性值改变的百分比。获取到当前属性的改变百分比后,在调用IntPropertyValuesHolder的calculateValue计算当前的动画值

@Override
void calculateValue(float fraction) {
    mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);//IntKeyframeSet的getIntValue方法
}
IntKeyframeSet的getIntValue方法内容:
@Override
public int getIntValue(float fraction) {
    if (fraction <= 0f) {//1:当前属性改变的百分比小于等于0 也就是减小
        final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
        final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
        int prevValue = prevKeyframe.getIntValue();
        int nextValue = nextKeyframe.getIntValue();
        float prevFraction = prevKeyframe.getFraction();
        float nextFraction = nextKeyframe.getFraction();
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        float intervalFraction = (fraction - prevFraction) / (nextFraction -prevFraction);
        return mEvaluator == null ?
                prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                        intValue();

    } else if (fraction >= 1f) {//2:属性改变的百分比大于等于1
        final IntKeyframe prevKeyframe = (IntKeyframe)mKeyframes.get(mNumKeyframes - 2);
        final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
        int prevValue = prevKeyframe.getIntValue();
        int nextValue = nextKeyframe.getIntValue();
        float prevFraction = prevKeyframe.getFraction();
        float nextFraction = nextKeyframe.getFraction();
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);

        }

        float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
        return mEvaluator == null ?
                prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
    }
    //3:属性改变的百分比小于1大于0
    // mKeyframes存储了关键帧
    IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);//起始的帧数
    for (int i = 1; i < mNumKeyframes; ++i) {
        IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);//
        if (fraction < nextKeyframe.getFraction()) {
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
            int prevValue = prevKeyframe.getIntValue();
            int nextValue = nextKeyframe.getIntValue();
            // Apply interpolator on the proportional duration.
            if (interpolator != null) {
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            return mEvaluator == null ?
                    prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't get here
    return ((Number)mKeyframes.get(mNumKeyframes -1).getValue()).intValue();
}

在getIntValue方法中,计算当前的动画值分为三种情况:

  • 1:当前属性改变的百分比小于等于0, 也就是减小;
  • 2:属性改变的百分比大于等于1;
  • 3:属性改变的百分比小于1大于0;

我这里就以第三种情况为例,分析它的处理过程。首先是否还记得mKeyframes,在文章的前面我们分析关键帧时,通过百分比的方式存放在mKeyframes中。 在这里最开始获取mKeyframes.get(0)为上一帧prevKeyframe,通过循环去下一帧,例如mKeyframes.get(1)为nextKeyframe,通过 当前帧数变化的百分比fraction与nextKeyframe中存放的百分比比较:

  1. 如果当前的百分比下一帧的百分比,说面现在动画属性值还在上一帧到下一帧的范围内。然后通过估值器mEvaluator计算出当前的属性值,如果估值器存在的话,否则直接百分比的方式返回prevValue + (int)(intervalFraction * (nextValue - prevValue));
  2. 如果当前的百分比大于一帧的百分比, 那么上一帧prevKeyframe就等于下一帧nextKeyframe,然后循环去一下针比较;

计算完当前动画属性的值后,我们需要把属性值设置到目标对象上。这时候回到ObjectAnimation.animateValue()方法中,执型IntPropertyValuesHolder.setAnimatedValue(target)函数设置属性值:

@Override
void setAnimatedValue(Object target) {
    if (mIntProperty != null) { 
        mIntProperty.setValue(target, mIntAnimatedValue);
        return;
    }
    if (mProperty != null) {
        mProperty.set(target, mIntAnimatedValue);
        return;
    }
    if (mJniSetter != 0) {
        nCallIntMethod(target, mJniSetter, mIntAnimatedValue);//
        return;
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = mIntAnimatedValue;
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}
native static private void nCallIntMethod(Object target, long methodID, int arg);

现在是不是已经有点恍然大悟了,在前面我们已经分析了setter方法的初始化,mJniSetter通过调用native方法获得。这里再通过

native方法nCallIntMethod(Object target, long methodID, int arg)设置属性值。如果按照例子中的实际情况,其实是通过jni的方式调用了view的setBackgroundColor(color)方法,改变属性值view开始重绘。

到这里我们已经设置了动画的起始值,离成就差一步了,喝口茶♨️,休息一下。

一盏茶的功夫继续回来。我们的都知道动画简单的说就是让单个画面有序的联动起来,在前面已经给动画设置了起始值,想要画面动起来就得不停的一帧一帧的设置属性值,那么怎么实现的呢?


我们需要回到开始分析的start()方法中的,之前我说——步骤二是最重要的一步先不分析,重要的事情永远留在最后,那现到了揭开这神秘面纱的时刻,一探addAnimationCallback(0)方法的究竟:

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return
    }
    AnimationHandler handler = AnimationHandler.getInstance();
    handler.addAnimationFrameCallback(this, delay);
}
Animation的addAnimationFrameCallback:
public class AnimationHandler {
    private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime
                     =new ArrayMap<>();//存放延迟发送AnimationFrameCallback
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks 
                     =new ArrayList<>(); //存放发送的AnimationFrameCallback
     public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {   
            getProvider().postFrameCallback(mFrameCallback);//MyFrameCallbackProvider.postFrameCallback        
        }
        if (!mAnimationCallbacks.contains(callback)) {. //
            mAnimationCallbacks.add(callback);
        }
        if (delay > 0) {
             mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }
    //获得MyFrameCallbackProvider对象
    private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
}

在AnimationHandler类有两个集合,mDelayedCallbackStartTime用来存放延迟发送的AnimationFrameCallback,mAnimationCallbacks不管是否延迟都会存放在这。关于mDelayedCallbackStartTime这处代码的逻辑,有一点没弄明白。在我分析的API28版本的源码中,不管是否是延迟动画,因为在调用addAnimationCallback(long delay)方法时,delay永远都是0,也就是说延迟动画并没有放在mDelayedCallbackStartTime这个延迟集合中。可能是版本的原因,网上很多文章在分析延迟动画时,都是根据mDelayedCallbackStartTime中存放的callback和动画的延迟时间,来处理相关的逻辑,这个版本中延迟动画的处理并不是这样。

接着分析addAnimationFrameCallback方法,如果mAnimationCallbacks还没有callback就通过MyFrameCallbackProvider.postFrameCallback(callback)发送一个消息:

private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
    //Android系统的编舞者Choreographer
    final Choreographer mChoreographer = Choreographer.getInstance();
    @Override
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        mChoreographer.postFrameCallback(callback);
    }
    //代码...
}

出现了一个新的内容Choreographer,这是一个很重要也很复杂的内容,一两句没法解释清楚。在这里我们只需要知道,系统会通过Choreographer的postFrameCallback方法最终会向底层注册屏幕刷新信号的监听,并回调FrameCallback的doFrame方法。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());//处理动画的逻辑
        if (mAnimationCallbacks.size() > 0) {//如果mAnimationCallbacks还有继续向底层注册监听
            getProvider().postFrameCallback(this);
        }
    }
};

通俗点说,在Android中一般情况下1秒有60帧,1帧大概16ms。我们一帧一帧的绘制动画,代码中最开始我们向底层注册屏幕刷新的监听,当接受到屏幕刷新的信号的时候,就会回调FrameCallback的doFrame。然后在doFrame处理动画逻辑,如果动画没有结束或者暂停,处理完这帧的动画逻辑,继续向底层注册监听,以此往复。
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);//在valueAnimator中实现了AnimationFrameCallback接口
                if (mCommitCallbacks.contains(callback)) {
                    getProvider().postCommitCallback(new Runnable() {
                        @Override
                        public void run() {
                            commitAnimationFrame(callback, getProvider().getFrameTime());
                        }
                    });
                }
            }
        }
        cleanUpList();
    }

上面代码中在doFrame方法doAnimationFrame中,会调用callback.doAnimationFrame,而在ObjectAnimator中实现了AnimationFrameCallback接口:

public final boolean doAnimationFrame(long frameTime) {
    if (mStartTime < 0) {//mStartTime初始值为-1
        // First frame. If there is start delay, start delay count down will happen *after* this frame.
        //如果设置了延迟开始 ,第一帧将在延迟之后开始
        mStartTime = mReversing
                ? frameTime
                : frameTime + (long) (mStartDelay * resolveDurationScale());
    }
    // Handle pause/resume
    if (mPaused) {
        mPauseTime = frameTime;
        removeAnimationCallback();
        return false;
    } else if (mResumed) {
        mResumed = false;
        if (mPauseTime > 0) {
            // Offset by the duration that the animation was paused
            mStartTime += (frameTime - mPauseTime);
        }
    }
    if (!mRunning) {
        if (mStartTime > frameTime && mSeekFraction == -1) {
            //如果动画设置了延迟 直接返回
            // This is when no seek fraction is set during start delay. If developers change the
            // seek fraction during the delay, animation will start from the seeked position
            // right away.
            return false;
        } else {
            // If mRunning is not set by now, that means non-zero start delay,
            // no seeking, not reversing. At this point, start delay has passed.
            mRunning = true;
            startAnimation();
        }
    }
    if (mLastFrameTime < 0) {
        if (mSeekFraction >= 0) {
            long seekTime = (long) (getScaledDuration() * mSeekFraction);
            mStartTime = frameTime - seekTime;
            mSeekFraction = -1;
        }
        mStartTimeCommitted = false; // allow start time to be compensated for jank
    }
    mLastFrameTime = frameTime;
    boolean finished = animateBasedOnTime(currentTime);//处理动画关键帧
    if (finished) {//如果动画完成
        endAnimation();//结束动画移除 AnimationFrameCallback
    }
    return finished;
}

在这个方法中对延迟动画做了,如果设置了延迟,那么动画开始时间mStartTime将等于frameTime加上延迟时间,只有等待延迟时间已过才会处理后面的逻辑,也就是往下执行animateBasedOnTime:

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);
        if (scaledDuration == 0) {
            // 0 duration animator, ignore the repeat count and skip to the end
            done = true;
        } else if (newIteration && !lastIterationFinished) {
            // Time to repeat
            if (mListeners != null) {
                int numListeners = mListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    mListeners.get(i).onAnimationRepeat(this);
                }
            }
        } else if (lastIterationFinished) {
            done = true;
        }

        //fraction 转化为mOverallFraction
        mOverallFraction = clampFraction(fraction);
        //算得最后的时间流逝的百分比
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        // 关键点
        animateValue(currentIterationFraction);
    }
    return done;
}

计算时间的流逝占总动画的百分比,再调用ObjectAnimator的animateValue方法:

//ObjectAnimation
void animateValue(float fraction) {
    final Object target = getTarget();
    if (mTarget != null && target == null) {
        // We lost the target reference, cancel and clean up. Note: we allow null target if the
        /// target has never been set.
        cancel();
        return;
    }
    super.animateValue(fraction);//计算属性值
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(target);//更新属性值
    }
}

在前面动画刚开始的时候已经分析过这个方法,先计算当前时段的属性值然后在更新属性的值,就这么在动画的时间内范围内一帧一帧的设置更新属性,画面就动起来了,终于大功告成。

····

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342