A few days ago, I encountered a problem that viewgroup.ondraw could not be called. I checked some materials on the Internet and found that they were basically confused with the difference between onDraw and onDraw. Taking advantage of the National Day holiday, I briefly sorted out the logic here.

View.drawandView.onDrawCall relation of

First, view. draw and view. onDraw are two different methods, and view. onDraw can only be called if view. draw is called. There is the following code in View.draw:

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null| |! mAttachInfo.mIgnoreDirtyState);// Is a solid control

if(! dirtyOpaque) { drawBackground(canvas);// Draw the background}...// Step 3, draw the content
if(! dirtyOpaque) onDraw(canvas);/ / called ontouchCopy the code

From the above code:

  1. View.drawMethod is calledView.onDraw
  2. onlydirtyOpaqueFalse (transparent, non-solid)View.onDrawMethods.

Therefore, if you want the viewgroup. onDraw method to be called, two conditions must be met:

  1. Try to getViewGroup.drawMethod called
  2. letdrawIn the methoddirtyOpaqueTo false.

Now that we’re talking about view. draw and view. onDraw, here’s the difference. View View source code, view. draw basically contains 6 steps:

  1. Draw the background with the View. DrawBackground method.
  2. If necessary, save the canvas’ layers to prepare for fading, If necessary, save the canvas. SaveLayer for fading in or out.
  3. Draw the content, passView.onDrawMethod to achieve, general custom View, is through the method to draw content. After obtaining Canvas, you can draw any content to achieve personalized customization.
  4. Draw the children, using the view. dispatchDraw method, which a ViewGroup will implement to draw its own child View.
  5. If necessary, draw the fading edges and restore layers, If necessary, draw the fading contents and restore the previously saved canvas layer.
  6. Draw decorations (scrollbars), via the view. onDrawScrollBars method to achieve the operation of drawing the scrollbars is implemented here.

In simple terms, view.draw is responsible for drawing all the contents of the current View and all the contents of its children, and is a complete set. Whereas view.ondraw is only responsible for drawing the content itself, which is a subset.

ViewGroup.drawCall timing of

Draw method will be called in the View. Draw method with three parameters, as shown below:

if(! hasDisplayList) {// Software drawing
    // Fast path for layouts with no backgrounds
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {      
        // Skip the drawing of the current View and draw the child View directly
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        dispatchDraw(canvas);
    } else {                            
        // At this point, the coordinate system has been switched to the View's own coordinate system, so we can draw the current View purely, and return to draw(canvas).draw(canvas); }}Copy the code

Under software drawing, the three-parameter view. draw is responsible for switching the View coordinate system from the parent View to the current View, and then handing it to the current View to draw. Normally, handing the current View to draw is done by calling the single-argument view.draw method. However, there is an optimization logic: if the current View does not need to be drawn (marked with the PFLAG_SKIP_DRAW flag), then the children of the current View will be drawn directly via the dispatchDraw method.

So, whether or not our viewgroup. draw method is called depends entirely on whether mPrivateFlags contains the PFLAG_SKIP_DRAW flag:

  1. If mPrivateFlags containsPFLAG_SKIP_DRAW, then it will skip the draw method of the current View and call dispatchDraw method to draw the child View of the current View.
  2. If mPrivateFlags is not includedPFLAG_SKIP_DRAWThe current View’s Draw method is called and everything is drawn.

So what does PFLAG_SKIP_DRAW depend on?

setWillNotDraw

The View has a setWillNotDraw method, which controls whether to skip the view. draw method in order to optimize. Let’s look at the method:

public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}Copy the code

This method is very simple. Let’s continue with the setFlags method:

void setFlags(int flags, int mask) {
int old = mViewFlags;
/ / set the flags
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
int changed = mViewFlags ^ old;
// If mViewFlags does not change, return directly
if (changed == 0) {
    return;
}
intprivateFlags = mPrivateFlags; .if((changed & DRAW_MASK) ! =0) {
    if((mViewFlags & WILL_NOT_DRAW) ! =0) {
        //mViewFlags sets WILL_NOT_DRAW flag
        if(mBseackground ! =null) {
            // If the current View has a background, unflag PFLAG_SKIP_DRAW for mPrivateFlags, but set another flag PFLAG_ONLY_DRAWS_BACKGROUND
            mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND;
        } else {
            // If the View has no background, set the PFLAG_SKIP_DRAW flag for PrivateFlags directlymPrivateFlags |= PFLAG_SKIP_DRAW; }}else {
        // Cancel the PFLAG_SKIP_DRAW flag for mPrivateFlags because mViewFlags does not set WILL_NOT_DRAW
        mPrivateFlags &= ~PFLAG_SKIP_DRAW;
    }
    requestLayout();
    invalidate(true); }}Copy the code

In order to set the PFLAG_SKIP_DRAW flag to mPrivateFlags, two conditions must be met:

  1. For mViewFlags, set the WILL_NOT_DRAW flag
  2. The current View has no background image

SetWillNotDraw (true) must set the WILL_NOT_DRAW flag to mViewFlags. If the current View does not have a background at this point, the PFLAG_SKIP_DRAW flag is set to mPrivateFlags. However, if the current View has a background image, then the mPrivateFlags PFLAG_SKIP_DRAW flag is unset and another flag, PFLAG_ONLY_DRAWS_BACKGROUND, is set. The logic of the setWillNotDraw method is shown below:

setWillNotDraw

Set the background

That there is a question that if we are in the process of running, cancel the current View of background, the current View is set for mPrivateFlags PFLAG_SKIP_DRAW flag? Answer: Yes, that’s what the PFLAG_ONLY_DRAWS_BACKGROUND flag does.

We look at the View. SetBackgroundDrawable method implementation:

public void setBackgroundDrawable(Drawable background) {
if (background == mBackground) {
    return;
}
if(background ! =null) {... mBackground = background;if((mPrivateFlags & PFLAG_SKIP_DRAW) ! =0) {
        // mPrivateFlags PFLAG_SKIP_DRAW should be removed and PFLAG_ONLY_DRAWS_BACKGROUND should be changed to pFLAG_draws_background. This is consistent with the logic in the setFlags methodmPrivateFlags &= ~PFLAG_SKIP_DRAW; mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND; }}else{
    // Cancel the background image here
    mBackground = null;
    if((mPrivateFlags & PFLAG_ONLY_DRAWS_BACKGROUND) ! =0) {/* * This view ONLY drew the background before and we're removing * the background, so now it won't draw anything * (hence we SKIP_DRAW) */
        // If mPrivateFlags contains PFLAG_ONLY_DRAWS_BACKGROUND, the WILL_NOT_DRAW flag was set before mViewFlags, but since the current View has a background image, PFLAG_ONLY_DRAWS_BACKGROUND must be set first. Now the background of the current View is gone, so you can re-set PFLAG_SKIP_DRAW to mPrivateFlagsmPrivateFlags &= ~PFLAG_ONLY_DRAWS_BACKGROUND; mPrivateFlags |= PFLAG_SKIP_DRAW; }}}Copy the code

The comments in the above code make this very clear. If you cancel the background of the current View, the PFLAG_ONLY_DRAWS_BACKGROUND flag of mPrivateFlags will be replaced with the PFLAG_SKIP_DRAW flag. The logic of the setBackgroundDrawable method is as follows:

setBackgroundDrawable

This concludes our analysis of the PFLAG_SKIP_DRAW flag. Back to our initial question: why is the viewgroup.draw (viewgroup.ondraw) method not called by default? ViewGroup mPrivateFlags PFLAG_SKIP_DRAW (ViewGroup mPrivateFlags PFLAG_SKIP_DRAW) By default, ViewGroup initialization will set WILL_NOT_DRAW for mViewFlags. By default, viewgroups also have no background images, so PFLAG_SKIP_DRAW is used for ViewGroup mPrivateFlags. The viewgroup. onDraw method will not be called, and the viewgroup. onDraw method will not be called.

 private void initViewGroup(a) {
    // ViewGroup doesn't draw by default
    if (!debugDraw()) {
        setFlags(WILL_NOT_DRAW, DRAW_MASK);
    }

    ...
}Copy the code

To summarize, the immediate factors that determine whether the view. draw method is called are: whether view. mPrivateFlags contains the PFLAG_SKIP_DRAW flag; To include this id, two conditions need to be met:

  1. View.mviewflags contains the WILL_NOT_DRAW flag, which can be set with view.setwillNotDraw (true).
  2. The current View has no background image.

    So if we want to haveViewGroup.drawTo be called, just break any of the above conditions.
  3. Call view.setwillNotDraw (false) to cancel the WILL_NOT_DRAW flag in view.mViewFlags
  4. Sets the background image for the ViewGroup

ViewGroup.onDrawCall timing of

If viewgroup. draw is called, viewgroup. onDraw will not be called. Viewgroup. onDraw is invoked only when view. mPrivateFlags is not a solid control (PFLAG_DIRTY_OPAQUE is not marked).

Solid control: The onDraw method of a control ensures that all areas of the control are completely covered by the content it draws. In other words, the content beneath the control cannot be seen through the area to which it belongs, that is, there are no translucent or empty parts.

MPrivateFlags is PFLAG_DIRTY_OPAQUE. By looking at the source to find relevant logic in ViewGroup. InvalidateChild method:

// Child refers to the child View that calls invalidate directly.
public final void invalidateChild(View child, final Rect dirty) {
// Calculate whether the child View is solid
final booleanisOpaque = child.isOpaque() && ! drawAnimation && child.getAnimation() ==null && childMatrix.isIdentity();
PFLAG_DIRTY and PFLAG_DIRTY_OPAQUE are mutually exclusive
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;

do { // Loop over to ViewRootImpl
    View view = null;/ / the parent View
    if (parent instanceof View) {
        view = (View) parent;
    }
    if(view ! =null) { // Flag the current parent View
        // If the parent View contains the FADING_EDGE_MASK flag, the parent View should be FLAG_DIRTY, indicating that the viewgroup. onDraw method is called
        if((view.mViewFlags & FADING_EDGE_MASK) ! =0 &&
                            view.getSolidColor() == 0) {
            opaqueFlag = PFLAG_DIRTY;
        }
        if((view.mPrivateFlags & PFLAG_DIRTY_MASK) ! = PFLAG_DIRTY) {PFLAG_DIRTY and PFLAG_DIRTY_OPAQUE are mutually exclusiveview.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; }}... }Copy the code

As you can see from the code above, the ViewRootImpl method traces back up and marks the parent control as PFLAG_DIRTY_OPAQUE if the child control is solid, and PFLAG_DIRTY otherwise. For a control that contains the PFLAG_DIRTY_OPAQUE flag, the drawBackground method (to draw the background) and the onDraw method (to draw its own content) are skipped during drawing.

Deciding whether a View is solid is entirely up to the isOpaque method, which by default checks whether view.mPrivateFlags contains the PFLAG_OPAQUE_MASK flag. The PFLAG_OPAQUE_MASK flag (solid) consists of PFLAG_OPAQUE_BACKGROUND (solid background) and PFLAG_OPAQUE_SCROLLBARS (solid scrollbars). The View is opaque only if it is solid in both background and scrollbar. The real way to calculate whether a View is solid is computeOpaqueFlags, as shown below:

 protected void computeOpaqueFlags(a) {
    // Opaque if:
    // - Has a background
    // - Background is opaque
    // - Doesn't have scrollbars or scrollbars overlay
    // If the View contains a background and the background is opaque, flag PFLAG_OPAQUE_BACKGROUND
    if(mBackground ! =null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
        mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
    } else {
        mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
    }

    final int flags = mViewFlags;
    PFLAG_OPAQUE_SCROLLBARS if there are no OVERLAY bars, or if the OVERLAY bar is of type OVERLAY, flag PFLAG_OPAQUE_SCROLLBARS
    if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
        mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
    } else{ mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS; }}Copy the code

The View is solid only if both PFLAG_OPAQUE_BACKGROUND and PFLAG_OPAQUE_SCROLLBARS are marked. This method is called at various points in the View to determine in real time whether the View is solid or not. Of course, if the default implementation of isOpaque method does not meet our requirements, we can implement it ourselves, which is officially recommended.

The Demo verification

Let’s verify this logic with a Demo:

  1. Set a custom parent ViewGroupA and child ViewB.
  2. Call setWillNotDraw(false) to ensure that the parent ViewGroupA’s draw method is called.
  3. Set a Click event on the child ViewB by calling the child viewb. invalidate method.
  4. Observe whether the draw and onDraw methods of the parent ViewGroupA and child ViewB are called by clicking on the child ViewB.

The Demo above must be drawn in software to be valid. In hardware drawing, the child ViewB calls the invalidate method, only triggering the child ViewB’s own draw method. Its parent View does not need to be redrawn.

If we set a solid color background on the child ViewB (the child ViewB becomes solid), we can conclude as follows:

  1. The draw and onDraw methods of both parent ViewGroupA and child ViewB are called when the View tree is first rendered.
  2. On subsequent clicks on the child ViewB, the draw and onDraw methods of the child ViewB will be called, as will the draw method of the parent ViewGroupA, but the onDraw method of the parent ViewGroupA will not be called.

If we do not set the background for the child ViewB (the child ViewB becomes non-solid), we can conclude as follows:

  1. The draw and onDraw methods of both parent ViewGroupA and child ViewB are called when the View tree is first rendered.
  2. On subsequent clicks on the child ViewB, the draw and onDraw methods of both the parent ViewGroupA and the child ViewB are called.

To control whether a View is solid or not, we can override isOpaque.

To summarize, viewgroup. onDraw will be called whenever the viewgroup. draw method is called when rendering the View tree for the first time. DrawBackground and viewgroup.ondraw will be called only if the child View is not solid and the child viewgroup.invalidate method is called.

conclusion

Finally, draw and onDraw methods of ViewGroup are called logic diagrams using a graph.

ViewGroup draw and onDraw method call logic diagram