属性动画简单分析(二)

在《属性动画简单解析(一)》分析了属性动画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);
    }
}

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

时间: 2024-10-04 19:02:16

属性动画简单分析(二)的相关文章

浅谈属性动画简单使用之实现卫星菜单(二)

大家对于卫星菜单应该都不陌生了,其实这个菜单如果能合适运用到我们的APP项目中,确实是一个不错的选择,交互性非常好.在写Demo之前我也上网搜了一些关于卫星菜单的实现,感觉好多人实现卫星菜单这种动画,采用的是补间动画,并且代码还不少,通过上一讲我们知道,补间动画不具备与用户交互的特点,就比如卫星菜单展开后,产生很多子菜单有很多的点击事件,会发现产生点击事件的位置不会随着补间动画而产生位置改变而改变,反而点击事件一直保存在初始的位置.当然补间动画也能做,那就需要产生动画后发生位置的改变,并且也需要

浅谈属性动画简单使用之实现爱的贝塞尔曲线浪漫告白效果(三)

谁说程序员不浪漫的啊,每次看到别人在黑程序员心中就有一种无奈,只是他们看到的是程序员不好的一面,今天我将用这个案例告诉那些人,程序猿也是一个很浪漫,很有情调的人.在程序员心中他们只想做最高效的事情,没有什么比效率更重要了.那就开始今天程序猿的告白之旅. 我们都知道属性动画有个强大的地方,它实现让某个控件按照我们指定的运动轨迹来运动.也就是说它可以按照一个抛物线来运动,也可以按照一个线性的线来运动,还可以按照我们今天所讲的贝塞尔曲线的轨迹来运动.为什么他可以按照某一个轨迹来运动呢??首先我们来分析

Android属性动画简单剖析

运行效果图: 先看布局文件吧,activity_main.xml: 1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 and

ImageLoader的简单分析(二)

在<ImageLoader的简单分析>这篇博客中对IImageLoader三大组件的创建过程以及三者之间的关系做了说明,同时文章的最后也简单的说明了一下ImageLoader是怎么通过displayImage方法来获取缓存来显示图片的,本文就对ImageLoader的这个知识点做较为详细的说明. ImageLoader对缓存的使用还是很灵活的:支持同步或者异步加载图片资源,对内存缓存和文件缓存可以选择使用其一或者二者都是用.在构建DisplayImageOptions对象的时候可以通过cach

jQuery中样式和属性模块简单分析

1.行内样式操作 目标:扩展框架实现行内样式的增删改查 1.1 创建 css 方法 目标:实现单个样式或者多个样式的操作 1.1.1 css方法 -获取样式 注意:使用 style 属性只能获取行内样式 解释:获取类或者外部样式文件中设置的样式要使用 W3C规范:window.getComputedStyle(dom) IE中 :dom.currentStyle itcast.fn.extend({ css: function(name, value) { return window.getCo

使用属性动画简单实现view飞入效果

比较简单的效果,可以用来菜单飞入之类,作为记录吧, package com.test.animation; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import com.nineoldandroids.animation.AnimatorSet;

使用属性动画(Animator)来完成动画

一直以来,完成动画的时候都使用的是AniamtionSet,把所有的动画放在一个集合中,这就是传统的动画,但是这存在有很大的弊端,例如下边一段代码,布局文件分别由一个IamgeView和一个Button组成,对Button和ImageVew添加如下监听事件: 1 @Override 2 public void onClick(View view) { 3 // TODO Auto-generated method stub 4 switch (view.getId()) { 5 //对Image

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

跟上之前的两篇文章 Android属性动画ValueAnimator源码简单分析 Android属性动画ObjectAnimator源码简单分析 继续看AnimatorSet源码的大概过程. AnimatorSet 提供了一种把多个动画放到一起,按照某种特定的顺序来播放,比如一个接一个的播放或者多个动画一起播放. AnimatorSet简单使用随便举一个最简单的例子 //AnimatorSet AnimatorSet animSet = new AnimatorSet(); ObjectAnim

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

Android开发的过程中经常要用到属性动画,经常都是网上扒下来看下怎么用,但是经常不知道为什么要这么用,手一哆嗦一不小心就点到源码里面去了.我们就来看看Android属性动画ValueAnimator类源码的简单实现,从而对ValueAnimator类有个大概的了解. 在Android开发过程中做动画效果的时候用到ValueAnimator的时候最简单的方法我们是这么干的 // ValueAnimator ValueAnimator valueAnimator = ValueAnimator.