View animation Animation operation principle analysis

This article has been authorized to publish exclusively on the WeChat public account guolin_blog (Guo Lin)

This time, I want to sort out the process analysis of View animation, that is, tweening animation (ScaleAnimation, AlphaAnimation, TranslationAnimation...). The content does not analyze the presentation principle of animation, such as Matrix, because I haven't understood it yet. This article mainly analyzes what is the running process of animation from start to end View.startAnimation()after ?

Q & A session

It is best to look at the source code with questions, which is more purposeful and pertinent, and can prevent deviation and horns when reading the source code, so let's ask a few questions first.

Animation animation is highly extensible. The system simply encapsulates several basic animations for us: translation, rotation, transparency, scaling, etc. If you are interested, you can take a look at the source code of these animations, they are all inherited From the Animation class, and then implement the applyTransformation() method. In this method, various cool animations are realized through Transformation and Matrix. Therefore, if you want to make cool animation effects, you still need to understand them. .

At present, I haven't understood it yet, and my ability is limited, so I give priority to analyzing a running process of animation.

First look at the basic usage of Animation animation:
Basic usage

When we want to use a View animation, we usually first create a new animation, then configure various parameters, and finally call the startAnimation() of the View that the animation will affect, pass the animation instance as a parameter, and then you can see The effect of the animation is running.

So, here comes the question:

Q1: I don't know if you have thought about it, when View.startAnimation() is called, will the animation be executed immediately?

Q2: If the animation lasts for 300ms, when View.startAniamtion() is called, and another interface refresh operation is initiated, then the interface refresh is executed after 300ms, that is, after the animation is completed, or during the animation execution process. Is the interface refresh operation executed?

We all know that the applyTransformation() method is where the animation takes effect. When this method is called back, the parameters will be passed in the progress of the current animation (0.0 --- 1.0). Just like drawing a curve in mathematics, the more points are given, the smoother the curve will be. Also, the more times this method is called back, the smoother the animation will be.

For example, a View zooming animation from 0 to 1280, if the method is only called back 3 times in this process, then the span of each time will be very large, such as 0 - 600 - 1280, then the animation effect will look like It's abrupt; on the contrary, if the method is called back dozens of times during the process, the span may only be 100 each time, so the animation effect will look smooth.

I believe that everyone has also typed logs in applyTransformation() to view the current animation progress. Sometimes there are dozens of logs, and sometimes there are dozens of them.

So here comes our question:

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

Well, this article is mainly to explain these three problems. If you understand these three problems, you will know how to analyze and locate the place where the frames are lost when you encounter animation freezes in the future. After finding the problem of the lost frame, you can leave it. Solving the problem is not far away.

Source code analysis

ps: The source code analyzed in this article is all based on the android-25 version. The following source code is in the form of screenshots. The top of each picture is the class name + method name. When you want to go through it yourself, if you don’t know which class the method belongs to, you can view it on the top of each picture.

View.startAnimation ()

If you are just starting out with source code analysis, you may not know where to start. It is recommended to start from where we use it startAnimation():
startAnimation.png

There is not much code, four methods are called, then follow up one by one to see, first setStartTime():
setStartTime.png

So here are just assignments to some variables, and there is no logic to run the animation, continue to see setAnimation():
setAnimation.png

There is a member variable of type Animation in the View, so this method is actually just binding our new ScaleAnimation animation to the View, and there is no logic to run the animation, continue to see invalidateParentCached():
invalidateParentCached.png

invalidateParentCaches()This method is simpler. It adds a flag to mPrivateFlags. Although it is not clear what it does, you can keep an eye on it first, because mPrivateFlags is a variable that is often encountered when reading the source code related to View, so you can understand it if you can. I figured it out, but it doesn't seem to have much to do with what we want to find out when the animation starts to execute. Skip it first and continue to follow up invalidate():
invalidateInternal.png

So the ViewGroup is actually called invalidate()internally invalidateChild(), and then follow up to see:
invalidateChild.png

There is a loop operation of do{}while(), the parent is this in the first loop, which is the ViewGroup itself, so the next step is to call the invalidateChildInParent()method , and then the loop termination condition is patent == null, so you can guess This method should return the parent of the ViewGroup, follow up and see:
invalidateChildInparent.png

So the key is when PFLAG_DRAWN and PFLAG_DRAWING_CACHE_VALID are assigned to mPrivateFlags, because as long as there is one of the two flags, the method will return mParent, the specific assignment is not clear, but what is certain is the animation execution , it satisfies the if condition, that is, this method will return mParent.

The mParent of a specific View is ViewGroup, and the mParent of ViewGroup is also ViewGoup, so in the do{}while() loop, it will keep looking for mParent, and the mParent at the top of a View tree is ViewRootImpl, so it will eventually come. in invalidateChildInParent()ViewRootImpl .

As for why the top of the View tree of an interface is ViewRootImpl, this is related to the Activity startup process. We all know that when we setContentView() in onCreate, we add the layout file we wrote to a ViewGroup with DecorView as the root layout, that is to say, DevorView is the root layout of the View tree, so why say View The top of the tree is actually ViewRootImpl?

This is because after the onResume()execution , WindowManager will execute addView(), and then create a ViewRootImpl object in it, then bind the DecorView with the ViewRootImpl object, and set the mParent of the DecorView to ViewRootImpl, and ViewRootImpl implements the ViewParent interface , so while ViewRootImpl doesn't inherit View or ViewGroup, it is indeed the parent of DecorView. This part of the content should belong to the relevant principles of the Activity startup process, so this article only gives the conclusion, and does not analyze it in depth. If you are interested, you can search for it yourself.

So we continue to go back to the place to find the animation execution, we followed to invalidateChildInParent()ViewRootImpl to see what it does:ViewRootImpl#invalidateChildInParent.png

First of all, all its return values ​​are null, so the previous do{}while() loop will definitely stop after it is executed here. Then the parameter dirty is passed layer by layer in the original View invalidateInternal(). It is certain that it is not empty or isEmpty, so continue to follow the invalidateRectOnScreen()method to see:invalidateRectOnScreen.png

Just follow it here, the scheduleTraversals()function is to performTraversals()encapsulate it into a Runnable, and then throw it into the queue to be executed in Choreographer. These Runnables to be executed will be executed when the latest 16.6 ms screen refresh signal arrives. performTraversals()It is the initiator of the three major operations of View: measurement, layout, and drawing.

No matter which View in the View tree initiates a layout request or a drawing request, it will eventually go to scheduleTraversals() in ViewRootImpl, and then pass the performTraversals() of ViewRootImpl when the latest screen refresh signal arrives, starting from the root layout DecorView in order Traverse the View tree to perform three major operations: measurement, layout, and drawing. This is why it is always required that the page layout level should not be too deep, because each page refresh will go to the ViewRootImpl first, and then traverse to the specific changed View to perform the corresponding layout or drawing operation.

These contents should belong to the Android screen refresh mechanism. Here I will only give the conclusion. I will post a blog post for the specific analysis in a few days.

Therefore, in the process of following View.startAnimation()up can also see that when executing animation, the redraw request operation of View will be called internally invalidate(), so it will eventually go to ViewRootImpl scheduleTraversals(), and then refresh the signal on the next screen. Time to traverse the View tree to refresh the screen.

Therefore, the conclusions that can be drawn here are:

When View.startAniamtion() is called, the animation is not executed immediately. This method just does some variable initialization operations, then binds View and Animation, then calls the redraw request operation, and internally searches for mParent. Finally, go to the scheduleTraversals of ViewRootImpl to initiate a request to traverse the View tree. This request will be executed when the latest screen refresh signal arrives. PerformTraversals is called to traverse the View tree from the root layout DecorView.

where the animation really executes

So, at this point, we can guess that the actual execution of the animation should be in the process of traversing the View tree initiated by ViewRootImpl. Measurement, layout, drawing, the three basic operations that View displays on the screen performTraversals()are , and as the parent at the top of the View tree, to control the three basic operations of this Veiw tree, only through layers traverse. Therefore, the execution of the three basic operations of measurement, layout, and drawing will be a traversal operation.

When I followed these three processes, I finally found that when I followed the drawing process, I saw the code related to animation, so we skipped the other two processes and looked directly at the drawing process:

Drawing process.png

I didn't draw this picture. I found it on the Internet. The beginning of the drawing process is initiated by ViewRootImpl, and then the View tree is traversed from DecorView. The implementation of traversal is in the View#draw() method. We can look at the annotation for this method:
draw.png

This method mainly does the above six things. Generally speaking, if the current View needs to be drawn, it will call its own onDraw(), and then if there is a child View, it will call dispatchDraw()to notify the child View of the drawing event. The ViewGroup is rewritten dispatchDraw(), called drawChild(), drawChild()and the child View is called draw(Canvas, ViewGroup, long), and this method will call the draw(Canvas)method , so this achieves the effect of traversal. The whole process is like the one pictured above.

In this process, when draw(Canvas, ViewGroup, long)I , I found code related to animation:
draw2.png

Remember that View.startAnimation(Animation)when assigned the incoming Animation to mCurrentAnimation.
getAnimation.png

So the Animation passed in at that time is now used, so the place where the animation is actually executed should be in the applyLegacyAnimation()method (the method was named drawAnimation in the android-22 version and before)
applyLegacyAnimation.png

Now determine where the animation really starts to execute. I have seen onAnimationStart()it also seen the initialization of the animation and the call to the Animation getTransformation. This method is the core of the animation, and then follow up to see:getTransformation.png

This method does several things:

  1. Record the time of the first frame of the animation
  2. Calculate the progress of the animation based on the time between the current time and the time of the first frame of the animation and how long the animation should last
  3. Control the animation progress between 0 and 1. If it exceeds 1, it means that the animation has ended. You can reassign it to 1.
  4. Calculate the actual progress of the animation based on the interpolator
  5. Call applyTransformation() to apply the animation effect

So, here we have been able to applyTransformation()determine when the callback is called and when the animation actually starts executing. Then Q1 is finally done, and Q2 can basically be sorted out. Because we know that applyTransformation()it is finally executed during the drawing draw()process , then obviously when the screen refresh signal of each frame comes, traversing the View tree is to recalculate the screen data, which is the so-called View refresh, and The animation is only performed incidentally in the process.

Next is Q3. We know that applyTransformation()is animation takes effect. When this method is continuously called back, the parameters will be passed in the progress of the animation, so the rendering effect is that the animation is running according to the progress.

However, after analyzing from the beginning, we found the place where the animation is actually executed, and the place where applyTransformation() was called, but we didn't see any for or while loop in these places, that is, a traversal and drawing operation of the View tree, The animation will only be executed once, right? So how did it get called back so many times?

We know applyTransformation()that getTransformation()is called in , and this method has a boolean return value, let's see what its return logic is:getTransformation2.png

In other words getTransformation(), the return value represents whether the animation is completed. Remember where it was called. getTransformation()Go to applyLegacyAnimation()and see what you did after getting the return value:
applyLegacyAnimation2.png

When the animation has not been executed, the invalidate()method again, and the ViewRootImpl will be notified layer by layer to initiate a traversal request again. When the next frame of the screen refresh signal comes, and then draw by performTraversals()traversing the View tree, the View's draw will be notified. When called, the applyLegacyAnimation()method perform animation-related operations, including calling getTransformation()Calculate animation progress and call applyTransformation()Apply animation.

That is to say, when the animation is very smooth, it is actually executed once every 16.6ms, that is, when each frame arrives applyTransformation(), until the animation is completed. So applyTransformation()this is how it is called many times, and there is no way to set the number of callbacks manually.

That's why this method prints the log more times when the animation duration is longer.

Remember that when the getTransformation()method calculates the animation progress, it is based on the currentTime passed in as a parameter, and this currentTime can be understood as the system time at the moment when the traversal operation is initiated (the actual currentTime is checked and adjusted in Choreographer's doFrame() ) A time, but the difference from the system time at the moment when the traversal operation is initiated is very small, so if you don’t go into details, you can understand it as above, which is easier to understand).

summary

To sum up, let's sort it out a bit:

  1. First of all, when View.startAnimation() is called, the animation is not executed immediately, but is notified to ViewRootImpl through invalidate() layer by layer to initiate a request to traverse the View tree, and this request will wait until the latest frame is received. The traversal view tree drawing operation is initiated only when the signal is generated.

  2. Traversing from DecorView, the drawing process will call the View's draw() method when traversing. When this method is called, if the View has bound animation, it will call applyLegacyAnimation(), which is specially used to handle animation. related logic.

  3. In the method applyLegacyAnimation(), if the animation has not been initialized yet, first call the initialization method initialized() of the animation, and at the same time call onAnimationStart() to notify that the animation has started, and then call getTransformation() to calculate the animation progress according to the current time. Then call applyTransformation() and pass the animation progress to apply the animation.

  4. getTransformation() This method has a return value. If the animation has not ended, it will return true. If the animation has ended or been canceled, it will return false. Therefore, applyLegacyAnimation() will decide whether to notify ViewRootImpl to initiate another traversal request according to the return value of getTransformation(). If the return value is true, it means that the animation is not over, then it will notify ViewRootImpl to initiate a traversal request again. Then when the next frame comes, start traversing the View tree drawing from the DecorView, and repeat the above steps until the animation ends.

  5. One thing to note is that the animation is executed in the drawing process of each frame, so the animation is not executed separately, that is to say, if there are some Views that need to be redrawn in this frame, then the work is also done in this frame. This time in the frame is done during this traversal of the View tree. Only one perfromTraversals() operation is initiated per frame.

The above is all the content of this article, to sort out the operating process principle of View animation Animation, but if you want to understand why the animation is stuck, you also need to understand the refresh mechanism of the Android screen and the message-driven mechanism; these contents will be in the In the past few days, it has been organized into a blog and shared.

Remaining problem

Finally, there are still some unresolved problems left, waiting to continue to explore:

Q1: Everyone knows that the difference between View animation and property animation is that View animation does not modify the property value of the View, such as translation animation. After translation, the View is still in the original position, and the actual position will not follow the animation. Execute and move, so what's the rationale for this?

Q2: Since the View animation will not change the View's property values, does the View need to perform the measurement operation again if it is a zoom animation?


QQ picture 20180316094923.jpgI just opened a public account recently. I want to motivate myself to keep on writing. In the initial stage, I mainly shared some original Android or Android-Tv knowledge. If you are interested, you can click on it. Thank you for your support~~

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325386582&siteId=291194637