preface
As we know, Android graphics drawing is mainly based on the View class. The drawing of each View needs to go through onMeasure, onLayout and onDraw, which correspond to the measurement size, layout and drawing respectively.
In order to simplify thread development and reduce the difficulty of application development, the Android system puts these three processes in the main thread of application (UI thread) to ensure the thread safety of drawing system.
These three processes drive call updates through a timer called Choreographer, which is invoked every 16ms by the signal vsync, somewhat similar to the mechanism of early TV refreshes. Choreographer’s doFrame approach calls each View’s onMeasure, onLayout, and onDraw methods recursively through treestructured viewgroups. In the end, each View will be drawn (SurfaceFlinger’s classes will also be used to synthesize the View, which is a very complicated process).
At the same time, each View saves many flag values, which are used to judge whether the View needs to be re-measured, Layout and Draw. In this way, methods of views that do not change and do not need to be redrawn will not be called, thus improving the efficiency of drawing.
Android for the convenience of developers animation development, animation provides several ways to achieve. One of the most commonly used is the attribute animation class (ObjectAnimator), which changes a series of attributes of the View at a certain curve rate by timing, and finally produces the effect of the View animation. The more common attribute animation can dynamically change the size, color, transparency and position of the View, which is more efficient to achieve, and is also the official recommended animation form.
In order to further improve the efficiency of animation, it is necessary to prevent multiple calls to onMeasure, onLayout and onDraw to redraw the View itself. Android also introduces the concept of a Layer.
By saving the View on a layer, for animations like pan, rotate, flex, etc., you only need to change the layer as a whole, without redrawing the View itself. The Layer Layer is divided into Software Layer and Harderware Layer. They can be done by setLayerType(layerType, paint) of the View class; Method to set. The soft drawing layer stores the View as a bitmap, which takes up regular memory; The hard drawing layer stores the View as a Texture, which takes up storage in the GPU. Note that since saving the View on layers takes up memory, you need to reset it to LAYER_ TYPE_ NONE after the animation ends to free memory.
Since ordinary views are in the main thread, Android needs to handle various user click events in the main thread in addition to drawing. In many cases, additional user processing logic, polling for message events, and so on will need to be run in the main thread. If the main thread is too busy to process and respond to user input in a timely manner, the user experience deteriorates dramatically. In more serious cases, ANR (Application Not Responding) is also fired when the main thread latency reaches 5s. In this way, when the drawing and animation of the interface are complicated and the calculation is large, it is no longer suitable to use View to draw.
Android has a SurfaceView mechanism for this scenario. The SurfaceView is able to draw graphics from non-UI threads, freeing up the UI thread. SurfaceView SurfaceView SurfaceView SurfaceView SurfaceView SurfaceView SurfaceView
public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
public void surfaceDestroyed(SurfaceHolder holder);
Copy the code
-
SurfaceCreated is called when the SurfaceView is created, and is typically used to create and start the drawing thread.
-
The surfaceDestroyed method is called when the SurfaceView is destroyed. It sets the flag to stop the drawing thread.
In the drawing child thread, it is usually a while loop, which determines whether to exit the child thread by judging the marker bit. Use the sleep function to periodically tune up the drawing logic. Through mHolder. LockCanvas () to get the canvas, painted after call mHolder. UnlockCanvasAndPost (canvas); Come on the screen. Note that locks are required in the drawing thread and surfaceDestroyed. Otherwise, the SurfaceView will be destroyed, but the drawing child thread still holds a reference to the Canvas, causing crash. Here is a commonly used framework:
private final Object mSurfaceLock = new Object(); private DrawThread mThread; @Override public void surfaceCreated(SurfaceHolder holder) { mThread = new DrawThread(holder); mThread.setRun(true); mThread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, Public void surfaceDestroyed(SurfaceHolder) {synchronized (mSurfaceLock) {// doDraw may crash mthread.setrun (false); } } private class DrawThread extends Thread { private SurfaceHolder mHolder; private boolean mIsRun = false; public DrawThread(SurfaceHolder holder) { super(TAG); mHolder = holder; } @Override public void run() { while(true) { synchronized (mSurfaceLock) { if (! mIsRun) { return; } Canvas canvas = mHolder.lockCanvas(); if (canvas ! = null) { doDraw(canvas); / / here do something really draw mHolder unlockCanvasAndPost (canvas); } } Thread.sleep(SLEEP_TIME); } } public void setRun(boolean isRun) { this.mIsRun = isRun; }}Copy the code
Android provides a Canvas class for drawing graphics. It is understood that this class is a Canvas and provides methods for drawing different graphics on the Canvas. It provides a series of apis for drawing various shapes, such as rectangles, circles, ellipses, etc. The corresponding API is of the form drawXXX.
The drawing of irregular graph is special. It is the same as the drawing formula of regular graph. It may be composed of arbitrary lines. Canvas provides a class called Path for drawing irregular shapes. A Path can record a variety of tracks, which can be a combination of points, lines, and shapes. Use the drawPath method to draw any graph.
With the Canvas class, after providing tools to draw various graphics, it is necessary to specify the color and style of the brush and other properties to effectively draw. Android provides a class called Paint to abstract a brush. Paint allows you to specify the color to draw, whether to fill, and how to handle intersection properties.
Animation to achieve
Since this is actual combat, of course there must be an example. Here to TOS recorder in the realization of waveform dynamic effect as an example. First look at the design of lion boy shoes to the visual design:
Here’s how it looks in action:
See so high on the animation, have to praise the design of lion boy shoes, but at the same time also deeply knead sweat – how to achieve this animation knead.
Take a quick look at the visual image above. It feels like multiple sinusoids. Each sinusoid appears to be high in the middle and low on the sides, and should have a symmetric attenuation coefficient. At the same time, there are two groups of sinusoids that are symmetric up and down, and the middle of the symmetrical sinusoids is filled with gradient color. And then looking at the dynamic effect, it looks like the irregular sinusoidal curve is moving forward at a constant rate.
It seems that in order to make this animation work, we had to pick up the poor math we had already given back to the teacher. Here is the formula for the sine curve:
? Y = Asin(ωx+φ) + k?
A is the amplitude, the height of the crest and trough, the distance along the Y-axis; Omega is the angular velocity, and the frequency is 2 PI f, which controls the width of the waveform; φ is the initial phase, which can determine the initial X-axis position of sinusoidal curve. K is the offset, which controls the offset along the y axis
In order to more intuitive, graphical display, the formula here strongly recommend a site: www.desmos.com/calculator, it can convert input formula into the coordinate chart. This is exactly what we need. For example, sine (0.75πx – 0.5π) looks like this:
You also need to multiply by a symmetric decay function compared to the one in the design diagram above. We chose the following attenuation function 425/(4+x4) :
Multiply the sin(0.75πx – 0.5π) by the attenuation function 425 over (4+x4), and then multiply by 0.5. The final result is the following:
It looks like this curve is already very similar to the curve in the visual diagram, just a few more curves with similar algorithms, but different phases. The diagram below:
Look, using our mathematical knowledge of quan, Bu, wang, and ji, we seem to have created waveforms similar to those in visual sketches.
Next, use Path in the SurfaceView, use the above formula to calculate the points, and then draw a line to connect them. This gives us the following practical effect (the background has been turned white for easy display) :
I’ve drawn the curve, and then all I have to do is fill in the gradient. This is where visual restoration is difficult to achieve.
For gradient fill, Android provides a class LinearGradient. It needs to provide the coordinates of the start and end points, as well as the color values of the start and end points:
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
TileMode tile);
Copy the code
TileMode includes CLAMP, REPEAT and MIRROR modes. It specifies the pattern of color repetition if the region is filled beyond the distance between the start and end points. CLAMP refers to colors that use the end edge, REPEAT refers to repeated gradients, and MIRROR refers to MIRROR repetition.
As predicted from the LinearGradient constructor, when filling the gradient, you must specify the exact starting and ending points. Otherwise, if the gradient distance is larger than the filled area, the gradient will be incomplete, while if the gradient distance is smaller than the filled area, there will be multiple gradients or incomplete filling. As shown below:
On the left is the top and bottom of the rectangle with the exact starting and ending points of the gradient. In the middle of the figure, the gradient is set to start at the top and end at the middle of the rectangle. The one on the right has a gradient starting and ending larger than the top and bottom of the rectangle. The code is as follows:
LinearGradient gradient = new LinearGradient(100, mHeight_2 - 200, 100, mHeight_2 + 200,
line_1_start_color, region_1_end_color, Shader.TileMode.REPEAT);
mPaint.setShader(gradient);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(100, mHeight_2 - 200, 300, mHeight_2 + 200, mPaint);
gradient = new LinearGradient(400, mHeight_2 - 200, 400, mHeight_2,
line_1_start_color, region_1_end_color, Shader.TileMode.REPEAT);
mPaint.setShader(gradient);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(400, mHeight_2 - 200, 600, mHeight_2 + 200, mPaint);
gradient = new LinearGradient(700, mHeight_2 - 400, 700, mHeight_2 + 400,
line_1_start_color, region_1_end_color, Shader.TileMode.REPEAT);
mPaint.setShader(gradient);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(700, mHeight_2 - 200, 900, mHeight_2 + 200, mPaint);
Copy the code
For regular shapes like rectangles, you can easily set the start and end of the gradient color. But what about the sinusoids in the figure above? Do you want to connect each point of a set of sinusoids up and down, using a gradient? The computation would be enormous! What other good ways are there?
The Xfermode mechanism for blending images is provided in Paint. It can control the blending and overlapping modes of drawing graphics and previously existing graphics. One of the more useful is the PorterDuffXfermode class. It has a variety of blending modes, as shown below:
Here, the original picture of canvas can be understood as the background, namely DST; The newly drawn image can be interpreted as the foreground, SRC. With this graphics mixing technology, the display of the intersection of various graphics can be completed.
Can we imagine a more imaginative way of intersecting the gradient rectangles with the waveforms already drawn in the image above and drawing out where they intersect? Where they intersect seems to be exactly what we need.
In this way, we simply fill in the waveforms, draw a rectangle with peaks and troughs in the closed region where each set of sinusoids intersects, and then color this rectangle into gradient color. Make the intersection of the rectangle with the waveform and select SrcIn mode, which shows the color of only the part of the intersecting rectangle. This plan looks feasible, try it first. The following figure is the superposition diagram without Xfermode, as can be seen from the figure, the area between the two sinusoids is exactly what we need!
Here is the image after SrcIn mode blending:
Something magical happened, and the visuals were restored.
Let’s draw another set of sinusoids. Note that since Dst in Xfermode refers to the original background, the mixing of the two sets of sinusoids affects each other. That is, when the second group calls the SrcIn mode to blend, the first group’s graph is cut. As shown below:
Therefore, two sets of sinusoids must be drawn separately on different Canvas layers during drawing. Fortunately, Android provides this functionality with different Canvas layers for off-screen cache drawing. We can draw a set of graphs, call canvas.saveLayer to store it in an off-screen cache, and then draw another set of curves. Finally, call Canvas.restoreToCount (sc); Method to restore the Canvas and mix the two screens. The final effect is as follows:
Here’s a summary of the order of drawing:
-
Figure out where the curve needs to be drawn
-
Fill out the sine
-
At the intersection of each set of sinusoids, a rectangle filled with gradient lines is drawn based on the peaks and troughs of the waves. And set the graphics blend mode to SrcIn
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); Copy the code
-
Stroke the sine line
-
Store the Canvas off-screen, and then draw the next set of curves
The static drawing is done. The next step is to get it moving. According to the framework given above, the doDraw method is executed periodically in the drawing thread. We simply move the waveform forward one distance at a time in the doDraw method to achieve the effect of moving the waveform forward. Specifically corresponding to the value of φ in the sine formula, each time only need to modify the value on the basis of the original value can change the position of the waveform in the X axis. Each time doDraw is executed, the initial phase of the graph is recalculated according to the following calculation method:
this.mPhase = (float) ((this.mPhase + Math.PI * mSpeed) % (2 * Math.PI));
Copy the code
When calculating the height of the waveform, you can also multiply by the volume. That is, the value of A in the sine formula can be the maximum height drawn by Volume 425/(4+x4). Thus the amplitude of the waveform can be positively correlated with the volume. The waveform can fluctuate with the volume.
Optimization of animation
Although the above has been achieved waveform animation. But it is so sample, naive to think that the work is over.
Now the resolution of mobile phones is becoming larger and larger, generally 1080p resolution. As the resolution increases, the amount of computation required to draw a graph increases (with more pixels). As a result, in some low-end phones, or in some pseudo-high-end phones (like an S4), the CPU’s computing power is insufficient, resulting in animation stuttering. Therefore, for self-drawing animation, it may also need to constantly optimize the code and algorithm to improve the efficiency of drawing and minimize the amount of calculation.
The ultimate goal of self-drawing animation optimization is to reduce computation and CPU burden. In order to achieve this goal, the author summarizes the following methods, if you have more and better methods, welcome to share:
1. Lower the resolution
In the actual animation drawing process, if the calculation of (x,y) value for each pixel, it will cause a lot of calculation. But this kind of intensive computing is often unnecessary. For animation, the human naked eye is a certain tolerance, in a certain range of distortion is not detectable, especially the kind of flash thing is more so. In this way, in the implementation, you can draw up a graph density much smaller than the actual resolution, this graph density to calculate the Y value. Then we will map our own graph density to the true resolution in scale. For example, when we drew the sinusoid up here, we could have just counted 100 points. The 60 points are then placed proportionally on the X axis of 1024 points. At a stroke, we reduced the computation by nearly a factor of 10. This is a bit like rasterizing an image.
Because of the low-density drawing, connecting these low-density points with straight lines will create a jagged phenomenon, which will also affect the experience. But fear not, Android already offers anti-aliasing features. This can be set in the Paint class:
mPaint.setAntiAlias(true);
Copy the code
Using Android’s optimized anti-aliasing features will definitely be more efficient than our per-point rendering.
It is the most direct and easiest optimization method to find a balance between the drawing density and the final effect by dynamically adjusting the custom drawing density (that is, without affecting the final visual effect, while minimizing the amount of computation).
2. Reduce real-time computation
We know that in the past computing resources were fairly limited on embedded devices, and running code often needed to be optimized, sometimes even at the assembly level. Although processors in mobile phones have become more powerful, they still require careful coding to handle large amounts of computation at short intervals, such as animations. The average animation refresh cycle is 16ms, which means that animation calculations need to be done as little as possible.
Anything that reduces the amount of real-time computation should be done. So how do you do as little real-time computing as possible? An important way of thinking is to exchange space for time. In general, when we do self-drawing animation, we need to do a lot of intermediate operations. These operations may produce the same result each time the drawing time comes. This also means that it is possible that we have made repeated calculations that require redundancy. We can store the results of these intermediate operations in memory. So the next time you need it, you don’t need to recalculate, you just need to take it out and use it. More commonly used table lookup method even use this space for time method to improve speed.
Specifically for this example, when calculating the attenuation coefficient 425/(4+x4), the calculation result is the same for each fixed point on the X-axis. So we just store the y value for each point in an array, and we get it directly from that array each time. This saves CPU a lot of computation on power and division operations. Similarly, since the sine function is periodic, all we need to do is calculate the value of a fixed N points in that period and store it in an array. And every time you want to compute the sine of theta, you just take the approximation from what you’ve already computed. Of course, we don’t need to do this optimization to calculate sin, because the Math library provided by the Android system must have been optimized to calculate sin using similar principles.
Cpus are typically quick at adding, subtracting and multiplying, but slow at floating-point division, requiring multiple instruction cycles. So we should also try to reduce the amount of arithmetic, especially floating-point division. It is generally common to convert floating-point operations to integer operations, so that the speed increase will be more obvious. But integer arithmetic also means a loss of precision, which often results in jagged graphics. Before, some colleagues encountered that even if the anti-aliasing method provided by Android system was adopted, the graphics drawn still had a strong aliasing feeling, which might be the accuracy problem in numerical calculation, such as incorrect integer calculation or incorrect rounding. In order to ensure accuracy and use integral type for calculation, it is often possible to uniformly multiply the parameters to be calculated by an accuracy (for example, multiply by 100 or 1000, depending on the accuracy range required), and then divide the result by the accuracy. There is also the issue of integer overflow.
3. Reduce the number of memory allocations
Android uses JAVA’s garbage collection (GC) model for memory allocation and release. When the allocated memory is no longer used, the system will automatically clear it for us. This is a great convenience for our application development, since we no longer need to pay too much attention to memory allocation and reclamation, thus reducing the risk of memory usage. However, the automatic reclamation of memory also means that additional resources are consumed by the system. The general GC process consumes the system’s MS level calculation time. In a normal scenario, developers don’t have to worry too much about memory details. However, in the development of self-drawing animation, the allocation of memory cannot be ignored.
Since the animation is usually driven by a 16ms timer, this means that the logic of the animation is called over and over again in a short period of time. Creating too many temporary variables on the heap in logical code can cause memory usage to rise steadily over a short period of time, leading to frequent GC behavior on the system. This will undoubtedly drag down the efficiency of animation, making animation become stagnant.
To reduce unnecessary memory allocation, we need to analyze the memory allocation behavior first. Android Studio provides a handy, intuitive tool for analyzing Android memory usage. In order to visualize the effect of memory allocation, some large temporary variables are deliberately created in the program. We then use the Memory Monitor tool to get the following graph:
And you see frequent prints in the log
D/dalvikvm: GC_FOR_ALLOC freed 3777K, 18% free 30426K/36952K, paused 33ms, total 34msCopy the code
The zigzag of each rise and fall in the figure means that a GC has occurred, and then multiple memory has been allocated, and the process continues over and over again. You can see from the log that the system is making frequent GCS, and each GC pauses the system for 33ms, which of course affects the animation. Of course, this is the extreme case of the test. In general, if memory is used more consistently, the probability of triggering GC will be greatly reduced, and the bump jagged rate in the above figure will be lower.
The above memory usage situation, also known as memory jitter, is not only seen during periodic calls, but also occurs when memory is allocated and freed in the for loop. It affects not only self-drawn animation, other scenes also need to avoid as far as possible.
From the figure above, it can be seen intuitively that there is allocation and release of memory within a certain period of time, and whether the use of memory is stable or not. However, when a problem occurs, we also need to use the tool Allocation Tracker to track the cause of the problem and finally solve it. The Allocation Tracker tool can help you track the Allocation and release of memory objects, and obtain the source of memory objects. For example, in the above example, we can trace it over a period of time and get the following picture:
As you can see from the figure, most of the memory allocation comes from Thread 18, which is the drawing Thread of our animation. As you can see from the figure, the main memory allocations are as follows:
-
We deliberately created a temporary large array
-
Comes from the getColor function, which comes from a call to getResources().getColor() and needs to get the color resource from the system resource. Multiple Variables to StringBuilder are created in this method
-
Create a temporary variable for Xfermode from mPaint. SetXfermode (new PorterDuffXfermode(porterduff.mode.src_in)); This call
-
Creates a gradient value
LinearGradient gradient = new LinearGradient(getXPos(startX), startY, getXPos(startX), endY, gradientStartColor, gradientEndColor, Shader.TileMode.REPEAT);Copy the code
For the second and third, these variables do not need to be created repeatedly each time the loop executes. Because every time they’re used, they’re fixed. Consider changing them from temporary variables to member variables that are initialized at the same time as the animation is initialized. Just call it when you need it.
For a memory allocation like class 4, since the shape of the waveform in each animation is different, the gradient must be recreated and set. So you can’t use it as a member variable here. This is the one that has to be assigned. Fortunately, this object is not big, the impact is very small.
For those large numbers of objects that cannot be avoided and must be allocated each time, we can also use the object pool model to allocate objects. Object pool to solve the problem of frequent creation and destruction, but it needs to be noted that after the end of use, the object pool needs to be released manually.
The optimized memory allocation will be much smoother. For example, in the example above. After removing the large number of intentionally created arrays above and optimizing the 2 and 3 points, the memory allocation is shown below:
You can see that memory doesn’t change significantly over a short period of time. And it hasn’t fired a GC for a long time
4. Reduce the number of Path creation times
This involves the optimization of drawing special regular graphics. The creation of a Path also involves allocating and freeing memory, which consumes resources. In addition, the more complex the Path is, the more time-consuming the Canvas will be in drawing. So what we need to do is optimize the Path creation process as much as possible to simplify the computation. There are not a lot of unified standard methods in this area, but more rely on experience, and the above mentioned three points of optimization method flexible application.
First, the Path class itself provides an interface for data structure reuse. In addition to providing the reset method, it also provides the rewind method. This way, each time the animation loop is called, it can be reused without freeing previously allocated memory. This avoids repeated freeing and allocating of memory. This is especially true in this case, where the Path is drawn with the same number of points each time.
Adopting a low-density drawing method can also reduce the number of line segments in Path, which reduces the number of Path construction. In the same way, when drawing Path, Canvas can also speed up the drawing speed because Path becomes simpler.
In particular, for the waveform examples in this article. The renderings given in the visual diagram, except that the area in the middle of the sinusoid is filled with gradients. You also need to stroke the sine itself. And the top and bottom sinusoids in a set of sinusoids have different colors. Thus, there are three steps to draw a complete set of sinusoids:
How to combine these three steps to minimize Path creation is also a matter of care. For example, if we were to simply follow the steps listed above, we would first create a Path containing both the upper and lower sinusoids, calculate the points of the upper and lower sinusoids, and then fill the Path. Then compute the points of the upper string again, create a Path with only the upper string, and draw it using the Stroke pattern, followed by the lower string. We will repeat the creation of both paths and double the calculation of point coordinates.
If we can trade space for time as mentioned in Step 2 above. First, write down all the point positions in an array, then use these points to calculate and plot the Path of the upper string, and then save it; Then calculate and draw the Path of the lower string and save it. Finally, create a Path for the record fill area, using mpath.addPath (); To populate the path with the previous two paths. This reduces the computation of Path. Record the three paths in different variables at the same time, so that when the next loop comes around, you can use the Rewind method for memory reuse.
Note that Path provides a close method to close a line. This function can provide some convenience. But it doesn’t work all the time. Sometimes, we still need to add line segments manually to close an area. For example, in the following example, using close would result in a blank area in the middle:
5. Optimize the drawing steps
What? After the above steps of optimization, animation or lag? Don’t panic. Here’s another tool to accurately analyze Catton. Android also provides a tool called TraceView that tracks and monitors the execution time of each method. It opens in the Android Device Monitor. For example, the author found a lag in the animation during the development process, and then used the TraceView tool above to check and get the following picture:
Found that this method clapGradientRectAndDrawStroke take up 72.1% of the CPU time, and this method drawPath actual elapsed time. This shows that the drawing here exist obvious flaws and unreasonable, most of the time to draw clapGradientRectAndDrawStroke above. So let’s go back to the principle of drawing, in order to be able to cut out the intersection between the rectangle and the sine and show the gradient area. The author made the following attempts:
First draw a gradient filled rectangle; The sinusoidal area is then reversed filled with a transparent color (the white area), so that the area where they intersect is cut using SrcIn mode, which shows the area where the white covers the rectangle (which is actually transparent) plus the area where they do not intersect (inside the sinusoidal area). This can also achieve the effect given in the design drawing. The code is as follows:
mPath.rewind();
mPath.addPath(mPathLine1);
mPath.lineTo(getXPos(mDensity - 1), -mLineCacheY[mDensity - 1] + mHeight_2 * 2);
mPath.addPath(mPathLine2);
mPath.lineTo(getXPos(0), mLineCacheY[0]);
mPath.setFillType(Path.FillType.INVERSE_WINDING);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(null);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
mPaint.setColor(getResources().getColor(android.R.color.transparent));
canvas.drawPath(mPath, mPaint);
mPaint.setXfermode(null);
Copy the code
The above code also works, but the fill area is dramatically larger due to the reverse fill used. DrawPath (mPath, mPaint); Calls account for more than 70% of the computation.
Once we find the bottlenecks and know why, we can make targeted improvements. We just need to adjust the order of drawing, first fill the sinusoidal area forward, and then draw the gradient filled rectangle in SrcIn mode. This reduces the amount of area that needs to be drawn while still achieving the desired effect.
Here is a screenshot of the improved TraceView:
It can be seen from the screenshot that the calculation is evenly divided into different drawing methods, there are no bottleneck points, and the measured animation has become smooth. In general, Caton can find the real bottleneck point more accurately through this method.
conclusion
This paper briefly introduces the Android common View and SurfaceView drawing and animation principle, and then introduces the recorder waveform animation concrete implementation and optimization methods. However, due to the author’s limited level and experience, there must be a lot of mistakes and mistakes. You have more and better suggestions, welcome to share and discuss, common progress.