以下是Android Animation的动画的完整流程。源码参考的是Android5.0。
- View.startAnimation
setAnimation 将mCurrentAnimation设置为当前要执行的animation
invalidateParentCaches将当前view的mParent的flag设为 PFLAG_INVALIDATED
然后调用invalidate,同时参数为true,使当前view的缓存失效。
- View.invalidate
这里会将失效的矩阵传递给ViewParent,ViewParent的直接子类有ViewGroup和ViewRootImpl,通常情况下,view的直接parent是ViewGroup,所以接下来会进入ViewGroup.invalidateChild。
- ViewGroup.invalidateChild
在这个方法中
这里会循环调用parent的invalidateChildInParent方法,在ViewGroup.invalidateChildInParent中,会返回它的mParent,但是最下层的ViewRootImpl.invalidateChildInParent方法返回null,因为它没有父view,所以循环终止。drawAnimation在第一次判断时,并不为true,因为这个PFLAG_DRAW_ANIMATION flag只有当执行了一次绘制后才会被设置,参见后面View.drawAnimation。当动画执行过程中的invalidateChildParent时,drawAnimation会为true,这时下面的while循环就会给ViewGroup设置PFLAG_DRAW_ANIMATION flag,给ViewRootImpl.mIsAnimating设置为true。
- ViewRootImpl.invalidateChildInParent
intersected 这个变量,顾名思义是指矩阵是否相交,用来判断需要失效的矩阵是否和当前ViewRootImpl的矩阵相交,如果相交的话就执行下面的scheduleTraversals.同时如果是在动画执行过程中,mIsAnimating变量也为true,也能走到scheduleTraversals这一步。
- ViewRootImpl.scheduleTraversals
这里主要就是调用了Choreographer的postCallBack,mTraversalRunnable 里面就是调用了View measure,layout,draw的非常经典的performTraversals方法。然后我们再看看Choreographer这个类.
- Choreographer.postCallback
addCallbackLocked这个方法会将当前Runnable按执行时间先后进行排队(所以如果在UI线程中最很多事情的话,肯定会导致动画的掉帧).
- Choreographer.scheduleFrameLocked
USE_VSYNC,判断这个VSync(VSync是Android 4.1以后引入的新的view绘制技术,可以参见http://blog.chinaunix.net/uid-26669815-id-3272173.html)是否打开,如果打开,走VSync的定时刷新逻辑,VSync刷新时间由VSync控制,不受Choreographer.sFrameDelay(默认为10ms,也就是100FPS)延迟控制,如果VSync没有打开走下面的延迟刷新逻辑,会判断上次刷新绘制帧+sFrameDelay的时间和当前请求绘制的时间哪个大,选择大的最后下一帧的刷新时间,但是由于使用Handler处理的,所以不一定会在规定时间点开始执行。不管哪种方式最终都会走到Choreographer.doFrame方法。
- Choreographer.doFrame
将最后一次绘制帧的时间设置为当前的时间,精确到纳秒,同时调用callback.run方法。而之前CALLBACK_TRAVERSAL对应的Runnable是TraversalRunnable,其run方法调用的是ViewRootImpl.doTraversal.
- ViewRootImpl.doTraversal
这个方法很简单,之前有介绍,就是调用了ViewRootImpl.performTraversals
- ViewRootImpl.performTraversals
这个方法就不细说了,重点不在这,这里会进行绘制的分发,ViewRootImpl.performDraw->ViewRootImpl.draw->ViewGroup.dispatchDraw->ViewGroup.drawChild->View.draw(注意是这个:draw(Canvas canvas, ViewGroup parent, long drawingTime))。
- ViewGroup.dispatchDraw
值得注意的是ViewGroup.drawChild和View.draw这两个方法都有boolean返回值,这个返回值是用来标志是否需要继续执行invalidate方法,当动画没有结束,那么肯定返回true。而在方法最后,会判断一个标志位是否被置,如果置了将继续调用invalidate方法。
接下来再看的View的draw方法。
- View.draw(Canvas canvas, ViewGroup parent, long drawingTime)
这里没有太多,主要看View.drawAnimation方法。
- View.drawAnimation
这里会调用Animation.getTransformation方法,这个方法处理Animation的一些生命周期,比如Animation的start,end,repeat和applyTransformation,同时有个返回值,如果动画执行结束,返回false,没有结束就返回true。如果是true的话,就将修改parent的一些flag,比如PFLAG_DRAW_ANIMATION,参见第3步。
这里有几个分支,如果进入第一个分支,执行parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED,这个flag,会在ViewGroup.dispatchDraw方法最后有判断,如果有这个标志,继续执行View.invalidate,参见第11步。而如果进了其他几个分支的话,显而易见,直接调用了parent.invalidate方法,也就继续进入了第2步。所以只要动画没结束,都会继续调用invalidate,直到动画结束。
附1:dispatchDraw流程
附2:如果调用startAnimation,那么只有当该View和屏幕区域有交集,且可见时才会执行动画。否则不会执行,除非定时调用invalidate的方式。
同时根据以上的源码分析,可以解释一些Animation上的一些诡异情况:
- 通过WindowManager.addView方式添加的View是否可以直接执行(是指直接parent是ViewRootImpl的情况)基本动画(外层没有ViewGroup包裹)?
答案是无法执行,因为外层没有ViewGroup,无法走正常的Animation流程。在ViewRootImpl.draw过程中无法通过ViewGroup.dispatchDraw走View.drawAnimation过程。
- 为什么有的View调用了startAnimation动画不能执行?
因为有可能这个view的矩阵和ViewRootImpl的矩阵没有交集,比如View不在屏幕内,在进行第3步时,得到的矩阵交集为0。而第4步则会失败。