preface

Through this article, you will learn:

SetWillNotDraw (Boolean) {setWillNotDraw(Boolean) {setWillNotDraw(Boolean)

If you are not interested in the principle, please pull to the final summary to see the solution ~

Small example

We know that customizing a view overrides the onDraw() method as follows:

public class MyView extends View {

    private Paint paint;
    private Rect rect;
    private Bitmap bitmap;
    private Matrix matrix;

    public MyView(Context context) {
        super(context);
        init();
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.BLUE);
        rect = new Rect(0,0,100, 100);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(rect, paint);
    }
}
Copy the code

Effect:





public class MyFrameLayout extends FrameLayout { private Paint paint; private RectF rectF; public MyFrameLayout(@NonNull Context context) { super(context); init(); } public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { addView(new MyView(getContext())); paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.RED); rectF = new RectF(0, 0, 200, 200); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); } @Override protected void onDraw(Canvas canvas) { canvas.drawRect(rectF, paint); }}Copy the code

MyFrameLayout overwrites the onDraw() method to try to draw a red rectangle and adds MyView as a child to MyFrameLayout. Finally, add MyFrameLayout to the XML as the activity layout file:

    <com.fish.myapplication.MyFrameLayout
        android:id="@+id/myFrameLayout"
        android:layout_width="400dp"
        android:layout_height="400dp">
    </com.fish.myapplication.MyFrameLayout>
Copy the code

Take a look at the results:

MyView blocks MyFrameLayout. This can be ruled out because the blue area of MyView is 100px by 100px and the red area of MyFrameLayout is 200px by 200px. 2, MyFrameLayout onDraw() is not called.

Roots back

This involves the drawing process of view, without further elaboration, roughly pick the key point to say:

If the current view is not a viewGroup, it will not draw a child view(because it has no children).

View draw source code, corresponding to the above steps.

Public void draw(Canvas Canvas) {// omit Boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL)! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0; if (! verticalEdges && ! HorizontalEdges) {// Draw your own content onDraw(canvas); // Draw sub-view dispatchDraw(canvas); // We're done... return; } // omit}Copy the code

MyFrameLayout dispatchDraw() does not execute, but MyView onDraw() does, so MyFrameLayout dispatchDraw() does. We have reason to believe that MyFrameLayout Draw() is not executed, just MyFrameLayout dispatchDraw().

Hardware acceleration

Hardware acceleration is now enabled on Android by default. What is hardware acceleration? In order to speed up the Android drawing speed and properly release CPU resources, Android puts part of the drawing on the GPU. The canvas in the corresponding Android is divided into whether it supports hardware acceleration, so the drawing process is also different. The flow chart is as follows:

[] indicates that the corresponding method in this class is called. As you can see from the above figure, whether hardware acceleration is enabled or not, the logic judgment “skip draw” is experienced, and the branch of this judgment determines whether the viewGroup ondraw() method is executed. If the “skip draw” is true, then the dispatchDraw() method is called and the child view, if any, is called to draw. If “skip draw” does not work, then call draw(x1), as analyzed above: the dispatchDraw() and ondraw() methods are called.

Factors that determine whether to draw or not

From the analysis of the code, it is in accordance with our conjecture: MyFrameLayout skipped the drawing for some reason, only called dispatchDraw() method, thus onDraw() method is not executed, and finally did not draw its own content. Next, look at the criteria for “skip drawing”.

Software drawing:

View. Java draw(x1,x2,x3) // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); }...Copy the code

Hardware acceleration:

Java updateDisplayListIfDirty method // Fast path for layouts with no wallpapers ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (debugDraw()) { debugDrawFocus(canvas); } } else { draw(canvas); } / / omittedCopy the code

As you can see, whether hardware acceleration is supported or not is determined by the PFLAG_SKIP_DRAW flag, and now you need to find out when this flag is assigned and cleared. MyFrameLayout onDraw() is not set, MyView onDraw() is set, MyFrameLayout is set PFLAG_SKIP_DRAW, MyView is not set. MyFrameLayout simply inherits from FrameLayout, MyView simply inherits from View, and we don’t set flags for them separately, so we assume that the PFLAG_SKIP_DRAW flag is handled differently when the viewGroup and View are initialized. Let’s take a look at ViewGroup initialization:


Java setFlags method // omit if ((changed & DRAW_MASK)! = 0) { if ((mViewFlags & WILL_NOT_DRAW) ! = 0) { if (mBackground ! = null || mDefaultFocusHighlight ! = null || (mForegroundInfo ! = null && mForegroundInfo.mDrawable ! = null)) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } else { mPrivateFlags |= PFLAG_SKIP_DRAW; } } else { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } requestLayout(); invalidate(true); } / / omittedCopy the code

Here it becomes clear that the two tag values are connected:

If WILL_NOT_DRAW is set, then continue to check whether background, foreground(mDrawable field), focusHighLight are valid. If any of them is set, then clear PFLAG_SKIP_DRAW. Otherwise, add the tag. 2. If WILL_NOT_DRAW is not set, clear the PFLAG_SKIP_DRAW flag.

At this point, we know why the MyFrameLayout onDraw() method is not executed: ViewGroup sets WILL_NOT_DRAW by default, and PFLAG_SKIP_DRAW by default. MyFrameLayout Draw (x) is called by checking the PFLAG_SKIP_DRAW flag. The onDraw() method is finally called. The view does not have the WILL_NOT_DRAW flag set by default, so that’s it.

How to make viewGroup onDraw() execute

Now that you know why MyFrameLayout is not drawing, there is a way for it to perform the drawing process. Let’s start with WILL_NOT_DRAW

view.java /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or  not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); } /** * Returns whether or not this View draws on its own. * * @return true if this view has nothing to draw, false otherwise */ @ViewDebug.ExportedProperty(category = "drawing") public boolean willNotDraw() { return (mViewFlags &  DRAW_MASK) == WILL_NOT_DRAW; }Copy the code

SetWillNotDraw (Boolean willNotDraw); MyFrameLayout can use setWillNotDraw(false). It is also possible not to set this mark. As mentioned above, even if WILL_NOT_DRAW is set, it can still determine whether background, foreground, focusHighLight are valid. If we set one of these values, the PFLAG_SKIP_DRAW flag will be cleared. How do these three values affect the PFLAG_SKIP_DRAW flag

view.java public void setBackgroundDrawable(Drawable background) { if (background ! = null) { if ((mPrivateFlags & PFLAG_SKIP_DRAW) ! = 0) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; requestLayout = true; } } } public void setForeground(Drawable foreground) { if (foreground ! = null) { if ((mPrivateFlags & PFLAG_SKIP_DRAW) ! = 0) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } } } private void setDefaultFocusHighlight(Drawable highlight) { mDefaultFocusHighlight = highlight; mDefaultFocusHighlightSizeChanged = true; if (highlight ! = null) { if ((mPrivateFlags & PFLAG_SKIP_DRAW) ! = 0) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; }}}Copy the code

Now add a background to MyFrameLayout and look at the effect:








conclusion

To execute ViewGroup onDraw(), you only need to setWillNotDraw(false), set background, set foreground, and set focus.

If you don’t want to draw MyFrameLayout onDraw, you can also override MyFrameLayout dispatchDraw() and draw MyFrameLayout.

    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.drawRect(rectF, paint);
        super.dispatchDraw(canvas);
    }

    @Override
    protected void onDraw(Canvas canvas) {
//        canvas.drawRect(rectF, paint);
    }
Copy the code

Note that super.dispatchDraw(canvas) should be placed behind, otherwise the subview content will be overwritten by MyFrameLayout.