View drawing is like drawing. Regardless of the Android concept, if you want to draw a picture, what are the basic questions that come to mind first:

  • Painting?
  • The painting?
  • How to draw?

Android drawing system is also based on this idea to draw the View, the answers to the above questions are hidden in:

  • Measure
  • Positioning (Layout)
  • Draw

This article will analyze “Draw” from the perspective of source code. In View drawing system, draw refers to the order of drawing, and it is up to each sub-view to decide what to draw.

This is the third in a series of articles on Android custom controls.

  1. Android custom controls | View drawing principle (pictures?)
  2. Android custom controls | View drawing principle (pictures?)
  3. Android custom controls | View drawing principle (drawing?)
  4. Android custom controls | source there is treasure in the automatic line feed control
  5. Android custom controls | three implementation of little red dot (on)
  6. Android custom controls | three implementation of little red dot (below)
  7. Android custom controls | three implementation of little red dot (end)

View.draw()

When analyzing the measurement and positioning of a View, we found that they are all top-down, that is, the parent control always triggers the measurement or positioning of the child control. I wonder if “drawing” is the same? View.draw(), view.draw ()

public void draw(Canvas canvas) { /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, Save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, Draw the background, if needed // if (! dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) // Final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0; if (! verticalEdges && ! HorizontalEdges) {// Step 3, draw the content if (! dirtyOpaque) onDraw(canvas); // dispatchDraw(canvas); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // make decorations (foreground, scrollbars); / / Step 7, the draw the default focus highlight / / Step 7: draw the default highlight drawDefaultFocusHighlight (canvas); if (debugDraw()) { debugDrawFocus(canvas); } / / we 're done... return; }}Copy the code

This method is too long… Fortunately, there’s a comment that helps us refine a thread. The comment states that there are six steps in drawing. They are:

  1. Draw control background
  2. Save canvas layer
  3. Draws the content of the control itself
  4. Draw child control
  5. Draw the fade effect and restore the canvas layer (feel this step is symmetric with the second step)
  6. Painted decoration

Why is it so complicated after refining the main line… Fortunately, the comment also saves us some steps. It says, “Usually steps 2 and 5 are skipped.” Of the remaining steps, there are three that are most important:

  1. Draw control background
  2. Draws the content of the control itself
  3. Draw child control

DrawBackground (drawBackground()), self (onDraw()), and child (dispatchDraw()). Things painted later will be on top of it.

DrawBackground () :

/** * Draws the background onto the specified canvas. * * @param canvas Canvas on which to draw the background */ Private void Drawable (Canvas Canvas) {//Drawable final Drawable background = mBackground; if (background == null) { return; } setBackgroundBounds(); . Drawable final int scrollX = mScrollX; Drawable final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); }}Copy the code

The background is an image of type Drawable, drawn directly on the canvas by calling drawable.draw (). Now look at onDraw() :

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    /**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }
}
Copy the code

View.ondraw () is an empty implementation. View is a base class that abstracts the order in which it is drawn. It is up to the subclasses to decide what to draw. Look at imageView.ondraw () :

public class ImageView extends View { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); . Drawable if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {mDrawable. Draw (canvas); } else { final int saveCount = canvas.getSaveCount(); canvas.save(); if (mCropToPadding) { final int scrollX = mScrollX; final int scrollY = mScrollY; canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, scrollX + mRight - mLeft - mPaddingRight, scrollY + mBottom - mTop - mPaddingBottom); } canvas.translate(mPaddingLeft, mPaddingTop); if (mDrawMatrix ! = null) { canvas.concat(mDrawMatrix); } mDrawable.draw(canvas); canvas.restoreToCount(saveCount); }}}Copy the code

ImageView draws a Drawable in the same way that a View draws a background.

ViewGroup.dispatchDraw()

View.dispatchdraw () is also an empty implementation. It is also true that View is a leaf node and has no children:

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    /**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {

    }
}
Copy the code

So ViewGroup implements dispatchDraw() :

public abstract class ViewGroup extends View implements ViewParent, ViewManager { @Override protected void dispatchDraw(Canvas canvas) { ... // Only use the preordered list if not HW accelerated, Since the HW pipeline will do the // Draw reordering Using predefined drawing list (according to z - order value ascending order all child controls) final ArrayList < View > preorderedList = usingRenderNodeProperties? null : buildOrderedChildList(); / / custom drawing order final Boolean customOrder = preorderedList = = null && isChildrenDrawingOrderEnabled (); For (int I = 0; i < childrenCount; i++) { ... // If there is no custom drawing order and predefined drawing list, The traversal in order of increasing index I child controls final int childIndex = getAndVerifyPreorderedIndex (childrenCount, I, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) {/ / trigger child controls draw themselves more | = drawChild (canvas, child, drawingTime); }}... } private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) { final int childIndex; if (customOrder) { final int childIndex1 = getChildDrawingOrder(childrenCount, i); if (childIndex1 >= childrenCount) { throw new IndexOutOfBoundsException("getChildDrawingOrder() " + "returned invalid index " + childIndex1 + " (child count is " + childrenCount + ")"); } childIndex = childIndex1; } else {//1. If there is no custom drawing order, traversal order is the same as I increment order. } return childIndex; } private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children, int childIndex) { final View child; if (preorderedList ! = null) { child = preorderedList.get(childIndex); if (child == null) { throw new RuntimeException("Invalid preorderedList contained null child at index " + childIndex); }} else {//2. If there is no predefined drawing list, iterate child = children[childIndex]; } return child; }}Copy the code

The parent controls in dispatchDraw() iterate over all the child controls and trigger them to draw themselves. There is also a way to customize the drawing order of child controls (which is not important for this topic).

Further down the call chain:

public abstract class ViewGroup extends View implements ViewParent, ViewManager { /** * Draw one child of this View Group. This method is responsible for getting * the canvas in the right Translating so * that the child's scrolled origin is at 0, 0, Draw a child of The ViewGroup * * @param canvas The canvas on which to draw The child  * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } } public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource { /** * 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) { ... if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else {// Draw draw(canvas); }... }Copy the code

Viewgroup.drawchild () will eventually call view.draw (). Therefore, View drawing is a top-down recursive process, “recursive” means that the parent control in viewgroup.dispatchdraw () traverses the child control and calls view.draw () to trigger its draw itself, “return” means that all the child control after the completion of drawing the parent control to continue the sequence of drawing steps’

conclusion

After the analysis of three articles, WE have a general understanding of the View drawing process:

  • ViewThe drawing process, like drawing, solves three problems in order of importance:
    1. Painting? (Measure)
    2. The painting? (Positioning layout)
    3. How to draw? (Draw draw)
  • Measurement, positioning, mapping are all fromViewThe root of the tree starts from the top down, that is, the parent control drives the child control. The parent control is measured after the child control, but the parent control is positioned and drawn before the child control.
  • Parent control during measurementViewGroup.onMeasure(), iterates over all child controls and drives them to measure themselvesView.measure(). The parent control also combines the layout requirements of the parent control with the layout requirements of the child control to form oneMeasureSpecObject is passed to the child control to instruct it to measure itself.View.setMeasuredDimension()Is the end of the measurement process, and it representsViewThe size has a definite value.
  • The parent control is called after it has finished positioning itselfViewGroup.onLayout()Iterate over all child controls and drive them to position themselvesView.layout(). The child control is always positioned relative to the upper left corner of the parent control.View.setFrame()Is the end of the positioning process and indicates that the view rectangular region and its position relative to the parent control have been determined.
  • Control in order to draw the background, draw itself, draw the child. The parent control is called after it has finished drawing itselfViewGroup.dispatchDraw()Iterate over all child controls and drive them to draw themselvesView.draw()