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

跟上之前的两篇文章

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

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

继续看AnimatorSet源码的大概过程。

AnimatorSet 提供了一种把多个动画放到一起,按照某种特定的顺序来播放,比如一个接一个的播放或者多个动画一起播放。

AnimatorSet简单使用随便举一个最简单的例子

        //AnimatorSet
        AnimatorSet animSet = new AnimatorSet();
        ObjectAnimator objectAlpha = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f, 1f);
        ObjectAnimator objectScaleX = ObjectAnimator.ofFloat(this, "scaleX", 1f, 0, 1f);
        ObjectAnimator objectScaleY = ObjectAnimator.ofFloat(this, "scaleY", 1f, 0, 1f);
        animSet.setDuration(3000);
        animSet.setInterpolator(new LinearInterpolator());
//      animSet.playSequentially(objectAlpha, objectScaleX, objectScaleY);
        animSet.playTogether(objectAlpha, objectScaleX, objectScaleY);
        animSet.start();

还是按照使用的规则开始分析AnimatorSet。对应AnimatorSet的源码准备从三个方面来分析。

1. AnimatorSet.playSequentially() 函数里面干了些啥。(动画顺序播放)

2. AnimatorSet.playTogether() 函数里面干了些啥。(动画同时播放)

3. AnimatorSet.start() 函数里面干了些啥。

一,AnimatorSet.playSequentially() 函数里面干了些啥

如果调用了这个函数代表动画按照顺序播放,我们带着好奇跟到源码里面去看看,到底是怎么让他顺序播放的。

AnimatorSet类的playSequentially 函数。

    public void playSequentially(Animator... items) {
        if (items != null) {
            mNeedsSort = true;
            if (items.length == 1) {
                play(items[0]);
            } else {
                mReversible = false;
                for (int i = 0; i < items.length - 1; ++i) {
                    play(items[i]).before(items[i+1]);
                }
            }
        }
    }

参数的长度是可变的,参数是具体的动画。

4-6行,如果只是传入了一个动画,直接调用了play函数,参数就是这个动画。那就得看play函数了。

    public Builder play(Animator anim) {
        if (anim != null) {
            mNeedsSort = true;
            return new Builder(anim);
        }
        return null;
    }

继续Builder的构造函数。AnimaterSet的内部类Builder类

        Builder(Animator anim) {
            mCurrentNode = mNodeMap.get(anim);
            if (mCurrentNode == null) {
                mCurrentNode = new Node(anim);
                mNodeMap.put(anim, mCurrentNode);
                mNodes.add(mCurrentNode);
            }
        }

new了一个Node,记录了Builder 的当前节点mCurrentNode,并且把这个节点加入到了mNodeMap中(key是动画,value是new出来的节点)。同时把这个节点加入到了mNodes里面。这里要注意mCurrentNode 是属于Builder类的,mNodeMap和mNodes是属于AnimatorSet类的。

继续AnimatorSet类的playSequentially 函数。6-11行,当传入的动画的个数大于1的时候。循环调用 play(items[i]).before(items[i+1]); play函数上面分析了,由于这里是for循环我们只分析一次的就好了,我们刚才看了play函数返回的是一个Build对象注意这里我们记录了mCurrentNode,然后继续调用这个Builder对象的before函数,跟踪进去。

        public Builder before(Animator anim) {
            mReversible = false;
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
            node.addDependency(dependency);
            return this;
        }

2-8行,这个应该不用解释,就是new一个Node同样把这个Node加入到AnimatorSet的mNodeMap和mNodes里面去。

9行 生成了一个Dependency对象,注意这里传入的是mCurrentNode是刚才play函数里面生成的,并且模式是Dependency.AFTER的模式(顺序播放的模式)。

10行 addDepenDency 跟踪进去看看吧

        public void addDependency(Dependency dependency) {
            if (dependencies == null) {
                dependencies = new ArrayList<Dependency>();
                nodeDependencies = new ArrayList<Node>();
            }
            dependencies.add(dependency);
            if (!nodeDependencies.contains(dependency.node)) {
                nodeDependencies.add(dependency.node);
            }
            Node dependencyNode = dependency.node;
            if (dependencyNode.nodeDependents == null) {
                dependencyNode.nodeDependents = new ArrayList<Node>();
            }
            dependencyNode.nodeDependents.add(this);
        }

这里要时刻记住dependency里面放的动画是前一个的动画节点(play函数里面赋值的mCurrentNode)。注意Node的nodeDependents和nodeDependencies两个List。这里可能不是很好讲明白直接举个例子吧。例子是这样的play参数假设是A动画,befor参数假设是B动画。那么play(A).before(B)调用完之后,看下Node的对应关系应该是这样的。这个关系在下面分析play的时候有非常非常的重要。

play函数的时候:

A对应的Node(这个时候还没有对应关系)

before函数的时候:

B对应的Node ->nodeDependencies 里面加了A对应的Node(对应函数的2-6行)

A对应的Node ->nodeDependents 里面加了B对于的Node(对应函数的10-14行)

总结AnimatorSet.playSequentially()做的事情,比如我们AnimatorSet.playSequentially()传入三个动画A,B,C。调用完playSequentially函数之后的状态是怎样的呢。

AnimatorSet里面的主要变量的变化:

mNodeMap加入了三个元素(A,A对应的Node), (B,B对应的Node), (C,C对应的Node)。mNodes里面加入了三个元素A,B,C。

各个动画对应的Node里面元素的变化:playSequentially里面会循环两次

第一次循环

A对应的Node(没有对应关系)

B对应的Node ->nodeDependencies 里面加了A对应的Node

A对应的Node ->nodeDependents 里面加了B对于的Node

第二次循环

C对应的Node->nodeDependencies 里面加了B对应的Node

B对应的Node->nodeDependents 里面加了C对于的Node

两次循环归总起来就是

A的Node:nodeDependencies 没有数据,nodeDependents 里面有B的Node

B的Node:nodeDependencies 里面有A的Node,nodeDependents 里面有C的Node

C的Node:nodeDependencies 里面有B的Node,nodeDependents 没有数据。

A不依赖那个动画,B依赖A,C依赖B。同时这里的依赖关系是Dependency.AFTER, AnimatorSet.playSequentially()的分析就到此结束了。

二,AnimatorSet.playTogether() 函数里面干了些啥

playTogether函数源代码

    public void playTogether(Animator... items) {
        if (items != null) {
            mNeedsSort = true;
            Builder builder = play(items[0]);
            for (int i = 1; i < items.length; ++i) {
                builder.with(items[i]);
            }
        }
    }

play函数看过了,直接看builder.with函数,这里的builder每次都是同一个对象,Builder里面的mCurrentNode一直是同一个是参数第一个动画对应的Node哦。看下with函数里面都干了啥。

        public Builder with(Animator anim) {
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
            node.addDependency(dependency);
            return this;
        }

和前面的before函数是一样的就是换了依赖关系,换成了Dependency.WITH。

同样的为AnimatorSet.playTogether()举个例子。同意参数是三个动画A,B,C, AnimatorSet.playTogether(A,B,C)。最后的结果是怎样的呢。playTogether里先产生了A对应的Node,然后做两次循环。

第一次循环

B的Node->nodeDependencies 里面加了A对应的Node

A对应的Node ->nodeDependents 里面加了B对于的Node

第二次循环(Builder没变,mCurrentNode也没变)

C的Node->nodeDependencies 里面加了A对应的Node

A对应的Node ->nodeDependents 里面加了C对于的Node

归总起来就是

A的Node: nodeDependencies 没有数据,nodeDependents 里面有B的Node,C的Node

B的Node: nodeDependencies 里面有A的Node, nodeDependents 没有数据

C的Node: nodeDependencies 里面有A的Node, nodeDependents 没有数据

B,C都依赖A。

三,AnimatorSet.start() 函数里面干了些啥

老规矩进入start源码。

    @Override
    public void start() {
        mTerminated = false;
        mStarted = true;
        mPaused = false;

        for (Node node : mNodes) {
            node.animation.setAllowRunningAsynchronously(false);
        }

        if (mDuration >= 0) {
            // If the duration was set on this AnimatorSet, pass it along to all child animations
            for (Node node : mNodes) {
                // TODO: don‘t set the duration of the timing-only nodes created by AnimatorSet to
                // insert "play-after" delays
                node.animation.setDuration(mDuration);
            }
        }
        if (mInterpolator != null) {
            for (Node node : mNodes) {
                node.animation.setInterpolator(mInterpolator);
            }
        }
        // First, sort the nodes (if necessary). This will ensure that sortedNodes
        // contains the animation nodes in the correct order.
        sortNodes();

        int numSortedNodes = mSortedNodes.size();
        for (int i = 0; i < numSortedNodes; ++i) {
            Node node = mSortedNodes.get(i);
            // First, clear out the old listeners
            ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
            if (oldListeners != null && oldListeners.size() > 0) {
                final ArrayList<AnimatorListener> clonedListeners = new
                        ArrayList<AnimatorListener>(oldListeners);

                for (AnimatorListener listener : clonedListeners) {
                    if (listener instanceof DependencyListener ||
                            listener instanceof AnimatorSetListener) {
                        node.animation.removeListener(listener);
                    }
                }
            }
        }

        // nodesToStart holds the list of nodes to be started immediately. We don‘t want to
        // start the animations in the loop directly because we first need to set up
        // dependencies on all of the nodes. For example, we don‘t want to start an animation
        // when some other animation also wants to start when the first animation begins.
        final ArrayList<Node> nodesToStart = new ArrayList<Node>();
        for (int i = 0; i < numSortedNodes; ++i) {
            Node node = mSortedNodes.get(i);
            if (mSetListener == null) {
                mSetListener = new AnimatorSetListener(this);
            }
            if (node.dependencies == null || node.dependencies.size() == 0) {
                nodesToStart.add(node);
            } else {
                int numDependencies = node.dependencies.size();
                for (int j = 0; j < numDependencies; ++j) {
                    Dependency dependency = node.dependencies.get(j);
                    dependency.node.animation.addListener(
                            new DependencyListener(this, node, dependency.rule));
                }
                node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
            }
            node.animation.addListener(mSetListener);
        }
        // Now that all dependencies are set up, start the animations that should be started.
        if (mStartDelay <= 0) {
            for (Node node : nodesToStart) {
                node.animation.start();
                mPlayingSet.add(node.animation);
            }
        } else {
            mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
            mDelayAnim.setDuration(mStartDelay);
            mDelayAnim.addListener(new AnimatorListenerAdapter() {
                boolean canceled = false;
                public void onAnimationCancel(Animator anim) {
                    canceled = true;
                }
                public void onAnimationEnd(Animator anim) {
                    if (!canceled) {
                        int numNodes = nodesToStart.size();
                        for (int i = 0; i < numNodes; ++i) {
                            Node node = nodesToStart.get(i);
                            node.animation.start();
                            mPlayingSet.add(node.animation);
                        }
                    }
                    mDelayAnim = null;
                }
            });
            mDelayAnim.start();
        }
        if (mListeners != null) {
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationStart(this);
            }
        }
        if (mNodes.size() == 0 && mStartDelay == 0) {
            // Handle unusual case where empty AnimatorSet is started - should send out
            // end event immediately since the event will not be sent out at all otherwise
            mStarted = false;
            if (mListeners != null) {
                ArrayList<AnimatorListener> tmpListeners =
                        (ArrayList<AnimatorListener>) mListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onAnimationEnd(this);
                }
            }
        }
    }

函数还蛮长的,

11-18行,如果AnimatorSet设置了duration,那么就统一设置所有动画的duration,单个设置的就没有效果了。

19-23行,如果AnimatorSet设置了插值器,那么就统一设置所有动画的插值器,单个的设置就没有效果了。

26行,sortNodes(); 这个函数我们不看了,就是根据对应关系去拍下,最后排序的结果会放到mSortedNodes中去。

29-44行,如果有的动画设置了DependencyListener或者AnimatorSetListener,把对应的监听移除掉,因为这两个对应的Listener在AnimatorSet里面要用来决定动画的播放顺序的。不让单个的动画去设置。

55-68行,去遍历整个的动画列表,56-58行,如果动画的Node没有dependencies,那么把这个动画加入到nodesToStart的list里面去。

58-66行,如果动画有依赖的动画,遍历这个动画依赖的动画(dependencies)。给这些依赖的动画添加DependencyListener的监听。这个是顺序或者同时播放的关键。顺序播放靠监听DependencyListener的onAnimationEnd启动下一个动画,同时播放则是靠监听DependencyListener的onAnimationStart来启动下一个动画,这个好理解吧,同时播放启动的时候同时起来,顺序播放结束的时候启动下一个。这个我们等下再分析。继续往下看start的函数。

70-75行,AnimatorSet没有设置延时 启动nodesToStart里面所有的动画。

75-96行,AnimatorSet设置了延时 加入了一个临时的动画,duration就设置成了延时的时间,当这个动画结束的时候启动nodesToStart里面所有的动画。

剩下的代码应该也好理解,对AnimatorSet的Listener的处理(主要哦是这里说的是设置给AnimatorSet的监听哦)。

接下里继续上面没分析的一个点DependencyListener,这个里面才是动画顺序,同时播放的关键点。

看AnimatorSet里面的内部类DependencyListener 这个类里面的onAnimationEnd和onAnimationStart两个函数。一个是管顺序播放的一个是管同时播放的这个好理解吧。

        public void onAnimationEnd(Animator animation) {
            if (mRule == Dependency.AFTER) {
                startIfReady(animation);
            }
        }
        public void onAnimationStart(Animator animation) {
            if (mRule == Dependency.WITH) {
                startIfReady(animation);
            }
        }

都调用了startIfReady函数。

        private void startIfReady(Animator dependencyAnimation) {
            if (mAnimatorSet.mTerminated) {
                // if the parent AnimatorSet was canceled, then don‘t start any dependent anims
                return;
            }
            Dependency dependencyToRemove = null;
            int numDependencies = mNode.tmpDependencies.size();
            for (int i = 0; i < numDependencies; ++i) {
                Dependency dependency = mNode.tmpDependencies.get(i);
                if (dependency.rule == mRule &&
                        dependency.node.animation == dependencyAnimation) {
                    // rule fired - remove the dependency and listener and check to
                    // see whether it‘s time to start the animation
                    dependencyToRemove = dependency;
                    dependencyAnimation.removeListener(this);
                    break;
                }
            }
            mNode.tmpDependencies.remove(dependencyToRemove);
            if (mNode.tmpDependencies.size() == 0) {
                // all dependencies satisfied: start the animation
                mNode.animation.start();
                mAnimatorSet.mPlayingSet.add(mNode.animation);
            }
        }

这里估计看代码的时候会比较乱的,我们就用例子来说明,还是举刚才的AnimatorSet.playSequentially()传入三个动画A,B,C。依赖关系是

A的Node:nodeDependencies 没有数据,nodeDependents 里面有B的Node

B的Node:nodeDependencies 里面有A的Node,nodeDependents 里面有C的Node

C的Node:nodeDependencies 里面有B的Node,nodeDependents 没有数据。

在start函数里面,0-0行,遍历到A动画的时候,因为A的Node没有nodeDependencies,所以A被加入到了nodesToStart的list里面去了,遍历到B动画的时候,B有nodeDependencies,然后给A动画加了DependencyListener的Listener注意哦这个是给A动画加了DependencyListener哦。在添加DependencyListener的时候把B的Node传递过去了。因为我们的规则是Dependency.AFTER,所以在A动画结束的时候会到DependencyListener的onAnimationEnd函数里面去,就走到了startIfReady函数了。这里就要启动B的动画了,看看是怎么启动的。

8-18行,遍历B的Node的nodeDependencies,找到A,把A的DependencyListener移除掉,A的DependencyListener没有用了。

20-24行,启动B的动画,这样在A动画播放完之后B的动画也启动起来了。

整个就结束了。

总结来说就是确定动画间的依赖关系,然后通过动画的DependencyListener按一定的顺序启动动画。

流水账记完了,下一遍估计就是插值器 估值器 和一些例子。

时间: 2024-10-06 00:43:14

Android属性动画AnimatorSet源码简单分析的相关文章

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

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

kafka 0.8.1 新producer 源码简单分析

1 背景 最近由于项目需要,需要使用kafka的producer.但是对于c++,kafka官方并没有很好的支持. 在kafka官网上可以找到0.8.x的客户端.可以使用的客户端有C版本客户端,此客户端虽然目前看来还较为活跃,但是代码问题还是较多的,而且对于c++的支持并不是很好. 还有c++版本,虽然该客户端是按照c++的思路设计,但是最近更新时间为2013年12月19日,已经很久没有更新了. 从官方了解到,kafka作者对于现有的producer和consumer的设计是不太满意的.他们打算

Javac源码简单分析之Javac简单介绍

一.简单介绍 javac 是java语言编程编译器.javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件. 二.源码获取 OpenJDK6源码:http://download.java.net/openjdk/jdk6/ Javac的源码就在OpenJDK源码里面. 或者在CSDN下载:http://download.csdn.net/detail/p_3er/7383741 三.Javac的包 Javac的公共入口点是com.sun.tools.javac

netty 源码简单分析一

周末简单看了下netty5的源码,只看懂了个大概,记录下成果,方便下次再看的时候回忆. 上服务端代码: public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.grou

Javac源码简单分析之解析和填充符号表

一.说明 符号表是由一组符号地址和符号信息构成的表格.符号表中所登记的信息在编译的不同阶段都要用到,在语义分析(后面的步骤)中,符号表所登记的内容将用于语义检查和产生中间代码,在目标代码生成阶段,党对符号名进行地址分配时,符号表是地址分配的依据. 二.主要的类与方法 解析和填充符号表这个过程主要由com.sun.tools.javac.comp.Entry及com.sun.tools.javac.comp.MemberEnter两个类来实现的. com.sun.tools.javac.comp.

FFmpeg源码简单分析:结构体成员管理系统-AVOption

===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFmpeg源码结构图 - 编码 [通用] FFmpeg 源码简单分析:av_register_all() FFmpeg 源码简单分析:avcodec_register_all() FFmpeg 源码简单分析:内存的分配和释放(av_malloc().av_free()等) FFmpeg 源码简单分析:常

FFmpeg源码简单分析:libswscale的sws_scale()

===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFmpeg源码结构图 - 编码 [通用] FFmpeg 源码简单分析:av_register_all() FFmpeg 源码简单分析:avcodec_register_all() FFmpeg 源码简单分析:内存的分配和释放(av_malloc().av_free()等) FFmpeg 源码简单分析:常

Android SVG动画PathView源码解析与使用教程(API 14)

使用的是一个第三方库android-pathview主要是一个自定义View--PathView,跟所有自定义View一样,重写了三个构造方法.并且最终调用三个参数的构造方法,在里面获取自定义属性. /** * Default constructor. * * @param context The Context of the application. */ public PathView(Context context) { this(context, null); } /** * Defau

Android 5.0 Settings源码简要分析

概述: 先声明:本人工作快两年了,仍是菜鸟级别的,惭愧啊!以前遇到好多知识点都没有记录下来,感觉挺可惜的,现在有机会接触Android 源码.我们一个Android组的搞Setting,我觉得是得写得东西,毕竟才接触,现在只能看一段时间代码,就先记录下一些收获吧,说多了就是泪~本文主要针对L平台上Settings模块正常启动流程做一个简要分析,并试着分析一下Settings下面某选项的实现. Setting 简介 在之前的KK平台上Settings模块的第一个Activity名字为Setting