preface

The Doodle framework is getting a major update! (>>>> Open source project Doodle! A powerful, customizable and extensible doodle framework)

V5.5: Added optimization drawing options, you can optimize drawing speed and performance, vertical enjoy silk slide.

boolean optimizeDrawing = true; // Whether to optimize rendering. You are advised to enable this function to optimize rendering speed and performance.
DoodleView mDoodleView = new DoodleView(this, bitmap, optimizeDrawing, doodleListener);
Copy the code

It’s not easy!

In fact, the author has felt the lag in graffiti for a long time, especially with the more graffiti, the more obvious the lag, but at that time, the heart is willing but the strength is insufficient, has been unable to find the best solution. Until recently inspiration broke out, finally solved, longitudinal enjoy silk slippery!

Preliminary resolution of the problem

When more and more graffiti, the operation of the more obvious lag, but also lead to the trace of graffiti is not smooth. The initial analysis is that DoodleView draws all doodles every time it refreshes, so the more doodles, the more time it takes to draw.

private void doDraw(Canvas canvas) {...for (IDoodleItem item : mItemStack) { // Time: Draw all doodles. item.draw(canvas); . }}Copy the code

Using Android Studio’s Profiler tool to view the CPU’s main time is in the drawing method:

In fact, except for the current doodle that needs to be redrawn, other doodles are unchanged and do not need to be redrawn. So how do you only draw the parts that need to be redrawn?

Through the study of wechat picture editing, it is found that the graffiti currently being operated is drawn in View canvas, and when the graffiti drawing is completed, the graffiti is merged into the picture, that is, the graffiti is drawn on the picture, and the new picture is drawn directly afterwards. So every time you refresh the View, you only draw the image and the doodle that you’re currently working on. (Before Doodle, it was all about drawing pictures and all doodles.)

This can be seen by comparing the before and after effects:

On the left, you can see what it looks like when you’re drawing (that is, when you swipe your finger across the screen). The lines are smooth, and because the View canvas has the same resolution as the screen, the lines are clear. On the right, at the end of the drawing (after the finger is raised), the edges of the lines are jagged, and the lines drawn on the image are blurry because of the lower resolution of the image.

Therefore, the author referred to this idea to optimize the drawing, sure enough, the final effect is very obvious, it will not become more and more difficult with the increase of graffiti, because each refresh basically only draw pictures and the current operation of graffiti that need to be redrawn, so the time is basically stable, not increasing.

So the problem is not perfect solution! ?

– no!

Further optimization

The author compares wechat graffiti again and finds that wechat graffiti is particularly smooth in drawing curves, while Doodle frame lacks such smoothness.

On the left is the circle drawn by quick sliding of wechat graffiti, while on the right is the circle drawn by Doodle after preliminary optimization. As people who pursue perfection, we must not lose to others in this respect. We must solve it!

Again, we use the almighty Profiler to find questions:

Originally main time drawBitmap above!

The image doesn’t change and doesn’t need to be redrawn. Can we just draw the current doodle instead of drawing the image? Of course you can!

The first thing to note here is that “no redraw” means that the View is refreshed without triggering the onDraw() method, which triggers the drawBitmap logic, but the image will still be displayed in the View. This involves hardware acceleration in the drawing mechanism of the Android system. When we have multiple views, calling the invalidate() of one view means that the view needs to be refreshed, triggering the onDraw method, but the other views are not redrawn (i.e. the corresponding onDraw() logic is not triggered). This point can be learned from the View source, you can understand a little:

// View.java
/** * This method is called by ViewGroup.drawChild() to have each child view draw itself. * * This is where the View specializes rendering behavior based on layer type, * and hardware acceleration. */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
  final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
    /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList. */
    booleandrawingWithRenderNode = mAttachInfo ! =null&& mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; .if (drawingWithRenderNode) { // Support hardware acceleration
        renderNode = updateDisplayListIfDirty(); // Update the list to be redrawn. }... }}/** * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported) */
    @NonNull
    public RenderNode updateDisplayListIfDirty(a) {...if(renderNode.isValid() && ! mRecreateDisplayList) {// The current view does not need to be redrawn
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList(); // Check if the underlying subview needs to be redrawn and updated

            return renderNode; // no work needed
        }
        // Hardware acceleration is not supported and will trigger 'draw()->onDraw()' logic. }Copy the code

In this case, we need to redesign the structure of the DoodleView as a container component (ViewGroup) with two child Views, one for drawing the background image and one for the doodle currently in operation.

So, if you just call the invalidate() method of the ForegroundView instance, you’ll just redraw the ForegroundView, and that’s all it takes. Conversely, if the BackgroundView changes and needs to be redrawn, its invalidate() method needs to be called.

OK! And you’re done! Longitudinal silky!

The latter

Note: After this function is enabled, the doodle item will be drawn on the top layer when it is selected for editing, and will not be drawn on the corresponding level until the editing is finished.

Code needs to be constantly optimized and restructured, maybe today’s good implementation will be replaced by a better solution tomorrow, which needs our continuous practice and verification, come on!

Finally, please support the Doodle framework >>>> open source project Doodle! A powerful, customizable and extensible doodle framework.