属性动画简单分析

Posted chunqiuwei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了属性动画简单分析相关的知识,希望对你有一定的参考价值。

在《属性动画简单解析(一)》分析了属性动画ObjectAnimation的初始化流程:
1)通过ObjectAnimation的ofXXX方法,设置propertyName和values。
2)将propertyName和values封装成PropertyValueHolder对象:每个PropertyValueHolder对象持有values组成的帧序列对象KeyFrameSet对象;
3)将步骤2创建的PropertyValueHolder对象用ObjectAnimation的mValues 数组保存起来;并用map缓存,最终可以用如下图片来表示初始化的最终完成结果:
这里写图片描述
一切就绪后,我们就可以调用ObjectAnimation对象的start方法来启动动画了!
需要注意的是本片博客继续沿袭第一篇博客的说明,本篇所讲的KeyFrame实际上是ObjectKeyframe
结合《属性动画简单说明前篇》这篇博文和上图,不难理解属性动画的执行逻辑如下:start方法开启之后,根据当前时间计算此时的fraction属于KeyFrameSet中的哪一个KeyFrame(currentFeyFrame),然后,然后根据将当前fraction 、preKeyFrame.value和nextKeyFrame.value三者交给TypeEvaluator的计算出一个值,然后将该值通过反射调用Objectde target方法,然后循环遍历下一时刻的fraction,并重复TypeEvaluator的计算过程即可,注意fraction的范围仍然是[0,1]。

还是用代码说话吧,从ObjectAnimator的start方法开始:该start方法直接调用了父类ValueAnimator的start(),VualeAnimator调用了start(boolean playBackwards) 方法;下面基本上就是对源码的分析了,源码分析结束后会会总结成一张图出来方便理解,当然如果对源码没兴趣的话,也可以直接看下文的图来理解之。

private void start(boolean playBackwards) {

        //将当前Animator对象放入一个集合中
        sPendingAnimations.get().add(this);
        //此处省略部分代码
        if (mStartDelay == 0) {         
            setCurrentPlayTime(getCurrentPlayTime());
              //此处省略部分代码:
        }
        //此处省略部分代码
    //发送一个消息
    animationHandler.sendEmptyMessage(ANIMATION_START);
}

start方法很简单,显示调用了setCurrentPlayTime然后发送一个handler,那么继续追踪setCurrentPlayTime方法:

public void setCurrentPlayTime(long playTime) {
        //此处省略部分代码
        initAnimation();
       //此处省略部分代码
        animationFrame(currentTime);
    }

上面代码调用了initAnimation和animationFrame方法,那么就来按顺序看看他们都干了些什么事儿,注意因为分析的是ObjectAnimator这个对象,所以initAnimation应该看ObjectAnimator类里面的方法,而不是ValueAnimator的方法:

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

initAnimation方法其实做了两个逻辑:
1)调用为setupSetterAndGetter方法为ObjectAnimator中mValues数组中的每一个PropertyValuesHolder赋值,参考上图确切的来说就是为PropertyValuesHolder对象所持有的mKeyframeSet中每一个KeyFrame赋值,如果KeyFrame在初始化的时候没有初始值就将目标对象的get方法为其设置初始值。具体代码如下:

void setupSetterAndGetter(Object target) {
            try {
                Object testValue = mProperty.get(target);
                //遍历PropertyValuesHolder所持有的mKeyframeSet
                for (Keyframe kf : mKeyframeSet.mKeyframes) {                 //如果没有设置初始值的话就设置初始值
                    if (!kf.hasValue()) {
                        kf.setValue(mProperty.get(target));
                    }
                }
                return;
            } catch (ClassCastException e) {
            }
        }
       //此处省略部分代码
    }

2)调用super.initAnimation为上图中的每个PropertyValuseHodler设置插值器TypeEvaluator
到此为止setCurrentPlayTime中调用的initAnimation方法讲解完毕,那么此时上图的中完成的初始化工作可以简单如下所示:
这里写图片描述
执行完initAnimation后,setCurrentPlayTime继续执行animationFrame方法:
(事先透露一句:该方法正式开始了属性动画的核心:根据当前fraction的值,根据上文设置的插值器,计算目标对象的setXX方法所需参数的值,并放射调用setXX方法来逐步完成属性动画的过程

boolean animationFrame(long currentTime) {
        boolean done = false;
        //省略部分代码
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
            //fraction的计算公式为:(当前时间-开始时间)/动画时间
            float fraction = mDuration > 0 ? (float) (currentTime - mStartTime)
            //省略了部分代码
            animateValue(fraction);
            break;
        }

        return done;
    }

为了便于博文的流程梳理,该方法在这里先不细说,其计算了fraction在最后调用了animateValue(fraction)方法,所以先来看看animateValue方法:

void animateValue(float fraction) {
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            //调用PropertyValuesHolder的setAnimatedValue方法
            //为target赋值
            mValues[i].setAnimatedValue(mTarget);
        }
    }
//super.animateValue方法:
void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            //为每一个PropertyValuesHolder调用calculateValue方法
            mValues[i].calculateValue(fraction);
        }
        //省略部分代码
    }

animateValue方法先调用父类ValueAnimator的animateValue方法,该方法可以说是属性动画的核心算法体现所在,该方法循环上图中每个PropertyValuesHolder,根据fraction为每个PropertyValuesHolder对象调用其calculateValue方法设置此时目标对象Target的set方法当前应该传入的值,该值保存在PropertyValuesHolder对象的mAnimatedValue变量里保存(当然本篇就不另外说明calculateValue的具体计算思路了,具体思路可参考博主另外一篇博文《属性动画简单说明前篇》)。调用完父类的方法之后,PropertyValuesHolder用图来表示的话就是如下所示了(高手貌似都是喜欢用图来说话):
这里写图片描述
分析完了ValueAnimator的方法animateValue之后,继续分析ObjectAnimator的animateValue方法,会发现该方法正好与父类的方法相反,父类的方法是为PropertyValuesHolder的mAnimatedValue变量设置值,那么ObjectAnimator的方法就是为获取mAnimatedValue的值并赋值给目标对象的setXX方法。具体处理方法是循环遍历PropertyValuesHolder对象,调用其setAnimatedValue方法:

void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
               //getAnimatedValue
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
            } catch (IllegalAccessException e) {
            }
        }
    }

setAnimatedValue方法也很简单:
1、getAnimatedValue获取mAnimatedValue保存的值
2、将mAnimatedValue的值通过反射调用目标对象的setXX方法,设置到目标对象中

所以到此为止ObjectAnimator通过OfXX设置的values,经过上述的重重调用计算,最终经过反射调用setXX方法,最终达到了修改目标对象属性的目的。
但是,It’s not over yet!到现在只是讲了一次调用setXX的过程,属性动画又是怎么逐步调用每一帧来让属性“动”起来呢?还记得文章开头说的start方法吗?最后发送了由handle发送了一个Messege方法:animationHandler.sendEmptyMessage(ANIMATION_START);,看看这个方法 是干什么呢?是不是做了下一帧调用的处理呢?
让我们看看这个Message方法都做了神马了不起的事儿!真相在一点一点打开:

先剖开别的不谈,其实阅读到这段代码的时候着实让我郁闷纠结了一段时间,因为ValueAnimator里面的handler只发送了两个消息:ANIMATION_START和ANIMATION_FRAME,其中第一个消息是在start里面发送的,但是ANIMATION_FRAME这个消息是什么时候发送的呢?看源码只有下面case语句里面一句发送了ANIMATION_FRAME消息啊:

case ANIMATION_FRAME:
 sendEmptyMessage(ANIMATION_FRAME);
break;

到底是怎么handleMessage的switch(msg.what)到底是怎么走到case ANIMATION_FRAME这个分支上的呢?其实特么的有点坑爹了,那是因为处理消息的代码框架如下:

case ANIMATION_START:
  doSomthing();
case ANIMATION_FRAME:
 sendEmptyMessage(ANIMATION_FRAME);
break;

那就是ANIMATION_START这个case分支没有break语句,执行完ANIMATION_START分之后会直接执行ANIMATION_FRAME分支;真特么坑爹,一时没留意这单,让我着实纠结了一阵
**这里写图片描述**
因为本篇博文较长,所以下面应该看成是本篇博文的第二大部分
那么正式开始吧,不过如果按照正常顺序来说明handleMessage的执行流程的话,可能会有点绕,因为它的代码有点长而且按照其流程说起来估计会对读者造成困惑,所以在这里我就先说结果,然后带着结果分析源码吧!

阅读源码可以发现:ValueAnimation提供了四个ThreadLocal类型的ArrayList静态集合:
sAnimations:包含着正在执行也即是已经执行但是还没有执行完毕的动画集合,处于此集合中的ValueAnimator或者 ObjectAnimator对象正在执行状态中

sPendingAnimations:如果一个ObjectAnimator对象调用了start()方法,那么就把此对象放入sPendingAnimations中,该集合中的动画尚未开始正式执行,正如其注释所说:该集合的动画对象都是即将执行的动画对象

sDelayedAnims:该集合保存了设置了延迟执行方法的ObjectAnimator对象

sReadyAnims :如果sDelayedAnims里面的某一个延迟执行的动画对象已经到了执行的时间,那么该ObjectAnimation对象就会 从sDelayedAnims删除,并放入sReadyAnims;也即是说sReadyAnims里面的动画都话保存了延迟时间到期后可以开始执行的集合

sEndingAnims:当sAnimations里面的某个正在执行的ObjectAnimation执行完毕后,就把该对象从sAnimations移除并将其放入 sEndingAnims集合中

这几个集合之间数据的移动可以用如下图简单所示:
这里写图片描述

以上就是ObjectAnimator的基本流程,那么到现在各种条件都准备好了,是时候讨论下前面说的那些handleMessage是怎么回事儿了:
因为代码太长,所以先看看case ANIMATION_START分支都干了些神马:

 //获取正在执行的动画集合
 ArrayList<ValueAnimator> animations = sAnimations.get();
 //获取延迟的动画集合
 ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
            switch (msg.what) {
            case ANIMATION_START:
                ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
                if (animations.size() > 0 || delayedAnims.size() > 0) {
                    callAgain = false;
                }
                //如果还有要执行的动画
                while (pendingAnimations.size() > 0) {

                    ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) pendingAnimations
                            .clone();
                    //清空pendingAnimations确保退出while循环
                    pendingAnimations.clear();
                    int count = pendingCopy.size();
                    //遍历pendingAnimations里面每一个ValueAnimator
                    for (int i = 0; i < count; ++i) {
                        ValueAnimator anim = pendingCopy.get(i);
                        if (anim.mStartDelay == 0) {
                            //主要是调用了initAnimation方法并将anim放入sAnimations里面的集合
                            //sAnimations保存了正在执行的动画对象
                            anim.startAnimation();
                        } else {
                            //还没开始执行
                            delayedAnims.add(anim);
                        }
                    }//end for
                }//end while
                //此处没有break

上面的代码也很简单就是将sPendingAnimations集合里面的数据分成两部分,正在执行的数据对象加入sAnimations集合中,没有执行的动画对象放入sDelayedAnims中;

继续分析case ANIMATION_FRAME的代码:

                //获取延迟到期的动画集合
                ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();
                //获取执行结束的动画集合
                ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();

                //把延迟处理的动画放入sReadyAnims集合中
                int numDelayedAnims = delayedAnims.size();
                for (int i = 0; i < numDelayedAnims; ++i) {
                    ValueAnimator anim = delayedAnims.get(i);
                    //如果延迟执行的动画可以执行了
                    if (anim.delayedAnimationFrame(currentTime)) {
                         //放入readyAnims准备执行
                        readyAnims.add(anim);
                    }
                }//end for

                //开始执行readyAnims集合里面的动画
                int numReadyAnims = readyAnims.size();
                if (numReadyAnims > 0) {
                    for (int i = 0; i < numReadyAnims; ++i) {
                        ValueAnimator anim = readyAnims.get(i);
                        //主要是调用了initAnimation方法并将anim放入sAnimations里面的集合
                        //sAnimations保存了正在执行的动画对象
                        anim.startAnimation();
                        anim.mRunning = true;
                        //从延迟队列里面删除对应的记录
                        delayedAnims.remove(anim);
                    }//end for
                    //清空准备好的序列
                    readyAnims.clear();
                }

                //变量正在执行的序列
                int numAnims = animations.size();
                int i = 0;
                while (i < numAnims) {
                    ValueAnimator anim = animations.get(i);
                    //如果当前动画已经执行完毕
                    if (anim.animationFrame(currentTime)) {
                        //把执行完的对象放入endingAnims对象中
                        endingAnims.add(anim);
                    }
                    if (animations.size() == numAnims) {
                        ++i;
                    } else {
                        --numAnims;
                        endingAnims.remove(anim);
                    }
                }//end while
                //省略部分代码

                //如果仍然有尚未执行完的动画,继续发送ANIMATION_FRAME消息继续执行
                if (callAgain
                        && (!animations.isEmpty() || !delayedAnims.isEmpty())) {
                    sendEmptyMessageDelayed(
                            ANIMATION_FRAME,
                            Math.max(
                                    0,
                                    sFrameDelay
                                            - (AnimationUtils
                                                    .currentAnimationTimeMillis() - currentTime)));
                }
                break;

上面的代码也很简单,结合上面的流程图不难发现也就是ObjectAnimation对象在这些集合里面转移的过程,就不在赘述;但是该case语句分支有两个核心点:
1)怎么判断当前ObjectAnimation已经结束呢?
2)如果还有正在执行的动画或者延迟动画集合非空,继续发送延迟消息ANIMATION_FRAME。

其实判断ObjectAnimation是否已经结束的方法前面已经说过,就是animationFrame方法,因为会定时发送ANIMATION_FRAME消息,animationFrame方法也会得到执行,如此往复这样一个ObjectAnimation对象的完整流程就可以完成了。
在animationFrame里面有如下代码来判断一个ObjectAnimation动画是否完成:
1)如果fraction>=1切mRepeatCount已经达到了指定次数。代码如下

if (fraction >= 1f) {
    if (mCurrentIteration < mRepeatCount
            || mRepeatCount == INFINITE) {
        // Time to repeat
        if (mListeners != null) {
            int numListeners = mListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mListeners.get(i).onAnimationRepeat(this);
            }
        }
        if (mRepeatMode == REVERSE) {
            mPlayingBackwards = mPlayingBackwards ? false : true;
        }
        mCurrentIteration += (int) fraction;
        fraction = fraction % 1f;
        mStartTime += mDuration;
    } else {
      done = true;//标明动画已经结束
      fraction = Math.min(fraction, 1.0f);
    }
}

到此为止,属性动画的执行流程已经描述完毕,篇幅较长,如果有不当的地方欢迎批评指正,共同学习和进步。

以上是关于属性动画简单分析的主要内容,如果未能解决你的问题,请参考以下文章

Android属性动画ValueAnimator源码简单分析

自己定义View时,用到Paint Canvas的一些温故,简单的帧动画(动画一 ,&quot;掏粪男孩Gif&quot;顺便再提提onWindowFocusChanged)(代码片段

Android动画TimeInterpolator(插值器)和TypeEvaluator(估值器)分析

Unity编程Unity动画系统

VSCode自定义代码片段——CSS动画

VSCode自定义代码片段7——CSS动画