This article has authorized the WeChat public address. guolin_blog ( Guo Lin) Exclusive release

I want to sort it out this time View Animation is also called mending animation(ScaleAnimation, AlphaAnimation,
TranslationAnimation...) Process analysis of these animations. Content doesn't analyze how animation works, such as Matrix
What are the principles of this kind, Because I haven't figured it out yet. This article mainly analyzes when callingView.startAnimation() after, What is the running process of animation from the beginning to the end?

Question link

The best way to read the source code is to go with problems, This is more purposeful and targeted, It can prevent deviation and corner drilling when reading the source code, So let's start with a few questions.

Animation
The expansion of animation is very high, The system just encapsulates a few basic animations for us: translation, rotate, transparency, Scaling and so on, Interested can see the source code of these animations, They are all inherited from
Animation class, And then it's doneapplyTransformation() Method, In this way Transformation and Matrix
Realize all kinds of cool animation, therefore, If you want to make cool animation, These still need to be understood.

I haven't figured it out yet, Limited ability, So it is a priority to analyze the running process of animation.

First look at it. Animation Basic usage of animation:


We're going to use one View Animation time, Usually first new An animation, Then configure various parameters, Finally, call the animation to the role. View Of
startAnimation(), Pass in the animation instance as a parameter, Then you can see the effect of animation running.

that, The problem is coming.:

Q1: I don't know if you've thought about it, When called View.startAnimation() after, Is the animation going to be executed soon?

Q2: If the animation duration 300ms, When called View.startAniamtion() after, Another interface refresh operation was initiated, Then the refresh of the interface is in 300ms
After that, the animation is executed, Or in the process of animation execution, the interface refresh operation is executed?

We all know,applyTransformation() This is where the animation works, When this method is called back, the parameter will pass in the current animation progress(0.0 ———
1.0). It's like drawing a curve in math, The more points you give, the smoother the curve you draw, Also, when this method is called back more than once, The smoother the animation.

Like a man from 0 Zoom in to 1280 Of View Zoom in, If this process, this method only calls back 3 Second time, So the span will be very large each time, such as 0 —— 600 ——
1280, So the animation will look very abrupt; Contrary, If the method calls back dozens of times during this process, So each span may be only 100, So the animation looks smooth.

I believe we've all been here applyTransformation() Log in to see the current animation progress, Sometimes there are dozens of logs, Sometimes there are dozens.

So here comes our question:

Q3:applyTransformation() What determines the number of callbacks for this method?

Okay, This article is mainly about these three questions, If you understand these three questions, I will know how to analyze when I come across cartoon carton, We've located where we lost the frame, It's not far from solving the problem after finding the problem of lost frames.

Source code analysis

ps: The source code of this analysis is all based on android-25
Edition. The following source codes are all screenshots, At the top of each diagram is the class name+ Method name, When we want to go through it by ourselves, If you don't know which class the method belongs to, you can view it at the top of each diagram.

View.startAnimation()

At the beginning of source code analysis, it may not be clear where to start, Suggestions can come from where we use it startAnimation():


There are not many codes. Four methods called, So follow in one by one, First is setStartTime() :


So it's just assigning values to some variables, No logic to run animation, Keep looking. setAnimation():


View There's a Animation Member variable of type, So this method is actually to new Of ScaleAnimation Animation and View
It's just tied, There's no logic to run the animation, Keep looking downinvalidateParentCached():


invalidateParentCaches() It's easier, to mPrivateFlags Added a flag bit, Although I don't know why, But you can take a look, because
mPrivateFlags This variable follows View
Related source code often encountered, So if you can figure it out, you can figure it out, But it doesn't seem to have much to do with finding out when animation actually starts, Skip first, Continue to follow upinvalidate():


therefore invalidate() It's called internally ViewGroup Of invalidateChild(), Follow up:


Here's a do{}while() Cycle operation of, On the first cycle parent yes this, Namely ViewGroup itself, So the next step is to call
ViewGroup In itselfinvalidateChildInParent() Method, Then the loop terminates on the condition that patent ==
null, So we can guess that this method should return ViewGroup Of parent, Follow up:


So the key is PFLAG_DRAWN and PFLAG_DRAWING_CACHE_VALID When are these two assigned to
mPrivateFlags, Because as long as there is one of the two signs, The method will return mParent, The specific assignment is not clear, But what's certain is that when the animation is executed, It is satisfied. if
Conditional, That is, this method will return mParent.

A concrete View Of mParent yes ViewGroup,ViewGroup Of mParent Also ViewGoup, So in
do{}while() It's going to keep searching in the cycle mParent, And one View The top of the tree mParent yes ViewRootImpl, So in the end, it will come
ViewRootImpl OfinvalidateChildInParent() Gone inside.

As for an interface View Why is the top of the tree ViewRootImpl, This is followed by Activity It's about the startup process. We all know, stay onCreate in
setContentView() When, Is to add our own layout file to the DecorView One for the root layout ViewGroup in, In other words
DevorView actually is View Tree root layout, Then why View The top of the tree is actually ViewRootImpl What about?

This is because onResume() After execution,WindowManager Will execute addView(), And then I'll create a ViewRootImpl
object, Next will DecorView Follow ViewRootImpl Object binding, And will DecorView Of mParent Set up
ViewRootImpl, and ViewRootImpl It is realized. ViewParent Interface, So though ViewRootImpl No inheritance View or
ViewGroup, But it does DecorView Of parent. This part should belong to Activity
Of the start-up process, So this article only gives the conclusion, No further analysis, If you are interested, you can search it by yourself.

So let's go back to where we're looking for animation execution, We're here ViewRootImpl Of invalidateChildInParent() Gone inside, See what it does:


First and foremost, All of its return values are null, So the previous one do{}while() The loop will stop when it's finally executed here. Then parameters dirty At the very beginning
View OfinvalidateInternal() From inside, To be sure, it's not empty, Neither isEmpty, So keep following
invalidateRectOnScreen() Look in the way:


Just follow me here,scheduleTraversals() The effect is to performTraversals() Package to a Runnable inside, Then throw it to
Choreographer In the queue to be executed, These to be implemented Runnable Will be in the latest 16.6 ms Screen refresh signal is executed when it arrives. and
performTraversals() yes View Three operations of: measure, layout, The originator of drawing.

View No matter which one is in the tree View Layout request initiated, Rendering request, All of them will eventually arrive ViewRootImpl Li
scheduleTraversals(), And then pass it when the latest screen refresh signal arrives ViewRootImpl Of performTraversals()
From root layout DecorView Start traversing one by one View
Tree to perform measurement, layout, Three operations of drawing. That's why it is always required that the page layout level should not be too deep, Because every page refresh will arrive first ViewRootImpl
in, And then we can traverse to the specific changed View To perform the corresponding layout or drawing operations.

These should belong to Android Screen refresh mechanism, Here we will only give the conclusion, I will post another blog in a few days.

therefore, We from View.startAnimation() In the process of following up source code analysis, It can also be seen that, Execution animation, In fact, it will be called internally View Redraw request operation for
invalidate() , So it will eventually arrive ViewRootImpl Of scheduleTraversals(), And then traverse when the next screen refresh signal arrives
View Tree refresh screen.

therefore, The conclusion we can get here is:

When called View.startAniamtion() after, The animation was not executed immediately, This method only does some variable initialization, Next will View and
Animation Bind up, Then call redraw request operation. Looking inside mParent, Finally come to ViewRootImpl Of scheduleTraversals
Start a traversal in View Tree request, This request will be executed when the latest screen refresh signal arrives, call performTraversals From root layout DecorView
Start traversal View tree.

Where animation really works

that, Come here, We can guess, In fact, the real execution of animation should be in ViewRootImpl Traversal initiated View In the process of tree. measure, layout, Draw,View
The three basic operations displayed on the screen are ViewRootImpl OfperformTraversals() To control, And as View The top of the tree
parent, To control this one Veiw Three basic operations of tree, You can only traverse through layers. therefore, measure, layout, Drawing the execution of three basic operations will be a traversal operation.

I'm following these three processes, Finally found, When following the drawing process, See the code related to animation, So we skip the other two processes, Look directly at the drawing process:



I didn't draw this picture, I found it on the Internet, The drawing process starts with ViewRootImpl Sponsored, Then from DecorView Start traversal View tree. And the implementation of traversal, Is in
View#draw() In the method. Let's take a look at the comments on this method:


In this method, the above six things are mainly done, In general, if View Need to draw, I'll call my own onDraw(), And then if there's a son View, Would call
dispatchDraw() Notify children of drawing events View.ViewGroup Rewrote dispatchDraw(), Called drawChild(), and
drawChild() Call child View Of draw(Canvas, ViewGroup, long), And this method will call draw(Canvas)
Method, So it's traversal. The whole process is just like the picture above.

In this process, Follow up draw(Canvas, ViewGroup, long) Li Shi, Found code related to animation:


Remember we called View.startAnimation(Animation) It will come in Animation Assign to mCurrentAnimation
Why?.


So it came in Animation , Now it's out, So where animation really works is in applyLegacyAnimation() In the way( The method is
android-22 Versions and earlier are named drawAnimation)


Now let's make sure where the animation really starts, All see onAnimationStart() 了, We also see the initialization of animation, And called Animation Of
getTransformation, This method is the core of animation, Come in and have a look:


Several things have been done in this method:

* Record the time of the first frame of the animation

* Calculate the progress of the animation based on the time between the current time and the first frame of the animation and the duration of the animation

* Control the animation progress to 0-1 Between, Exceed 1 Indicates that the animation is over, Reassign to 1 that will do

* Calculate the actual progress of the animation according to the interpolator

* call applyTransformation() Apply animation
therefore, We've been able to make sure applyTransformation() When was the callback, When does animation really start. that Q1 It's done,Q2
I can sort it out. Because we know,applyTransformation() In the process of drawing draw()
Implemented in the process, So obviously when the screen refresh signal of each frame comes, ergodic View Trees are used to recalculate screen data, It's called View
Refresh, And animation is only executed in the process.

The next step is Q3 了, We know applyTransformation()
Where animation works, When this method is called back again and again, Parameters will pass in the progress of the animation, So the rendering effect is that the animation is running according to the progress.

however, Let's start from scratch, Found where the animation really works, Eureka applyTransformation() Where called, But none of these places have been seen for
perhaps while Cycle! Once View Traversal drawing operation of tree, Animation can only be executed once? So how did it get called back so many times?

We know applyTransformation() Is in getTransformation() Called in, And this method has a boolean
Return value, Let's see what its return logic is:


In other words getTransformation() The return value of represents whether the animation is complete, Remember where it was called getTransformation() bar, go
applyLegacyAnimation() Let's see what we did after we got this return value:


When the animation is not finished, Will call again invalidate() Method, Notice to ViewRootImpl
Initiate a traverse request again, When the next screen refresh signal comes, Re passperformTraversals() ergodic View Tree rendering time, The View Of draw
When a notification is called, Will call againapplyLegacyAnimation() Method to perform animation related operations, Including calling getTransformation()
Calculate animation progress, callapplyTransformation() 应用动画.

也就是说,动画很流畅的情况下,其实是每隔 16.6ms 即每一帧到来的时候,执行一次 applyTransformation(),直到动画完成.所以这个
applyTransformation() 被回调多次是这么来的,而且这个回调次数并没有办法人为进行设定.

这就是为什么当动画持续时长越长时,这个方法打出的日志越多次的原因.

还记得 getTransformation() 方法在计算动画进度时是根据参数传进来的 currentTime 的么,而这个 currentTime
可以理解成是发起遍历操作这个时刻的系统时间(实际 currentTime 是在 Choreographer 的 doFrame()
里经过校验调整之后的一个时间,但离发起遍历操作这个时刻的系统时间相差很小,所以不深究的话,可以像上面那样理解,比较容易明白).

小结

综上,我们稍微整理一下:

*
首先,当调用了 View.startAnimation() 时动画并没有马上就执行,而是通过 invalidate() 层层通知到 ViewRootImpl
发起一次遍历 View 树的请求,而这次请求会等到接收到最近一帧到了的信号时才去发起遍历 View 树绘制操作.

*
从 DecorView 开始遍历,绘制流程在遍历时会调用到 View 的 draw() 方法,当该方法被调用时,如果 View
有绑定动画,那么会去调用applyLegacyAnimation(),这个方法是专门用来处理动画相关逻辑的.

*
在 applyLegacyAnimation() 这个方法里,如果动画还没有执行过初始化,先调用动画的初始化方法 initialized(),同时调用
onAnimationStart() 通知动画开始了,然后调用 getTransformation() 来根据当前时间计算动画进度,紧接着调用
applyTransformation() 并传入动画进度来应用动画.

*
getTransformation() 这个方法有返回值,如果动画还没结束会返回 true,动画已经结束或者被取消了返回 false.所以
applyLegacyAnimation() 会根据 getTransformation() 的返回值来决定是否通知 ViewRootImpl
再发起一次遍历请求,返回值是 true 表示动画没结束,那么就去通知 ViewRootImpl 再次发起一次遍历请求.然后当下一帧到来时,再从
DecorView 开始遍历 View 树绘制,重复上面的步骤,这样直到动画结束.

*
有一点需要注意,动画是在每一帧的绘制流程里被执行,所以动画并不是单独执行的,也就是说,如果这一帧里有一些 View
需要重绘,那么这些工作同样是在这一帧里的这次遍历 View 树的过程中完成的.每一帧只会发起一次 perfromTraversals() 操作.

以上,就是本篇所有的内容,将 View 动画 Animation 的运行流程原理梳理清楚,但要搞清楚为什么动画会出现卡顿现象的话,还需要理解 Android
屏幕的刷新机制以及消息驱动机制;这些内容将在最近几天内整理成博客分享出来.

遗留问题

最后仍然遗留一些尚未解决的问题,等待继续探索:

Q1:大伙都清楚,View 动画区别于属性动画的就是 View 动画并不会对这个 View 的属性值做修改,比如平移动画,平移之后 View
还是在原来的位置上,实际位置并不会随动画的执行而移动,那么这点的原理是什么?

Q2:既然 View 动画不会改变 View 的属性值,那么如果是缩放动画时,View 需要重新执行测量操作么?


最近刚开通了公众号,想激励自己坚持写作下去,初期主要分享原创的Android或Android-Tv方面的小知识,感兴趣的可以点一波关注,谢谢支持~~