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按一定的顺序启动动画。
流水账记完了,下一遍估计就是插值器 估值器 和一些例子。
更多相关文章
- android opencv 前置摄像头
- Android(安卓)开机图片/文字/动画 修改
- Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析
- Android三种动画详解
- Android(安卓)- 如何将2个或多个应用放到一个进程中去?
- 浅谈android hook技术
- Android原生代码调用H5 Web网页中的Javascript函数方法
- Android(安卓)Animation
- 箭头函数的基础使用