View drawing mechanism

First, View tree drawing process

When the Activity receives a focus, it is asked to draw the layout, which is handled by the Android Framework. Drawing measures and draws the layout tree from the root node. The entire View tree is drawn in the performTraversals() function of the Viewrot.java class. The work done by this function can be summarized as whether to recalculate the view size (measure), relocate the view position (layout) and draw (draw). The flow chart is as follows:

View draws the chain of process function calls

It should be noted that when the user calls Request, only measure and layout processes will be initiated, but draw process will not be executed

Second, the concept of

1. Measure and layout

From the overall view of the two steps of Measure and Layout:

The tree traverses in an orderly fashion, from parent to child, with each ViewGroup mapping all of its children, and the lowest View mapping itself.

2. Specific analysis of the measure process is initiated by the measure(INT, INT) method, which measures views orderly from top to bottom. At the end of the measure process, each View stores its own size and measurement specifications. The layout process is initiated by the layout(int, int, int, int) method and traverses from top to bottom. During this process, each parent view arranges its child view according to the size obtained by the measure process.

The Measure procedure assigns values to the mMeasuredWidth and mMeasuredHeight variables of a View and all of its children, which can be obtained using the getMeasuredWidth() and getMeasuredHeight() methods. Both values must be within the superview constraint to ensure that all superviews receive measurements from all child views. If the sub-view is not satisfied with the size obtained by Measure, the parent view will intervene and set measurement rules for the second Measure. For example, the parent view can first measure each subview according to the ungiven dimension. If the unconstrained size of the final subview is too large or too small, the parent view will use an exact size to measure the subview again.

3. Two classes of measure process transfer dimensions

  • Viewgroup.layoutparams (View’s own layout parameters)
  • MeasureSpecs class (Measurement requirements of a parent view versus a child view)

Viewgroup.layoutparams is a common class that specifies the height and width of a view. For each view height and width, you have the following options:

  • The specific value
  • MATCH_PARENT: the child view wants to be the same size as the parent (no padding)
  • WRAP_CONTENT means the view is exactly the size to wrap its content (including the padding value)

A subclass of ViewGroup has a subclass of ViewGroup.LayoutParams. For example, RelativeLayout owns a subclass of ViewGroup.layoutParams. Sometimes we need to use the view.getLayoutParams() method to get a view LayoutParams and then strong-cast it, but we don’t know what type it is, so we might get strong-cast errors. This method returns a LayoutParams of the parent View type. For example, if the View’s parent control is RelativeLayout, the resulting LayoutParams will be of type RelativeLayoutParams.

MeasureSpecs Measurement specifications, which contain information about measurement requirements and dimensions, come in three modes:

  • The parent view has no constraints on the child views, and can be of any desired size. Such as ListView, ScrollView, general custom View does not use,

  • The EXACTLY parent specifies an exact size for the child, and no matter how big the child wants it to be, it must be within the bounds of that specified size, corresponding to the property match_parent or a specific value, such as 100dp, Parent controls can get the dimensions of child controls directly with MeasureSpec. GetSize (MeasureSpec).

  • AT_MOST Specifies a maximum size for the child view. In this mode, the parent control cannot determine the size of the child View, and the child control can only calculate its own size according to the requirements. This mode is the case where the measurement logic of our custom View needs to be realized.

3. Measure core method

  • Measure (int widthMeasureSpec, int heightMeasureSpec) measure(int widthMeasureSpec, int heightMeasureSpec) However, the measure call chain will eventually call back the onMeasure() method of the View/ViewGroup object. Therefore, when customizing a View, you only need to duplicate the onMeasure() method.

  • OnMeasure (int widthMeasureSpec, int heightMeasureSpec) This method is used to implement the measurement logic in a customized view. The parameters of this method are the measurement requirements of the parent view on the width and height of the child view. In our own custom view, all we need to do is calculate the width and height of the view according to the widthMeasureSpec and heightMeasureSpec. Different modes handle this differently.

  • The setMeasuredDimension() method, called in the onMeasure(int widthMeasureSpec, int heightMeasureSpec) method, passes the calculated measurements to the method and the measurement period ends. This method must also be called, otherwise an exception will be raised. MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec You can refer to the onMeasure method of the ViewPagerIndicator.

MeasureChildren (int widthMeasureSpec, int heightMeasureSpec); measureChildren (Int widthMeasureSpec, int heightMeasureSpec); measureChildren (Int widthMeasureSpec, int heightMeasureSpec); measureChildren (Int widthMeasureSpec, int heightMeasureSpec) MeasureChild method call flowchart:

Source code analysis

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec The most heavy work is handled in the getChildMeasureSpec method * * @param widthMeasureSpec calls the width of the View * @Param heightMeasureSpec calls the View */ Protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren;for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if((child.mViewFlags & VISIBILITY_MASK) ! = GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); / / get the Child LayoutParams final int childWidthMeasureSpec = getChildMeasureSpec (parentWidthMeasureSpec, / / to get ChildView  widthMeasureSpec mPaddingLeft + mPaddingRight, lp.width); Final int childHeightMeasureSpec = getChildMeasureSpec (parentHeightMeasureSpec, / / get heightMeasureSpec ChildView mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } /** * This method is the most taxing part of measureChildren, calculating its own MeasureSpec for each ChildView. * The goal is to combine ChildView's MeasureSpec and LayoutParams to get the most appropriate result. * * @param Padding Specifies the paddingand of the current View. It's possible to have the margins * * @param childDimension on the current dimensionreturnChild view MeasureSpec */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) {......... // Create a child's MeasureSpec based on the measurement requirements and size of the childreturnMeasureSpec.makeMeasureSpec(resultSize, resultMode); } /** ** is used to get the final size of the View. The parent View provides the width and height constraints * The real measurement of a View is done in onMeasure(int, int), which is called by this method. * Therefore, only onMeasure(int, int) can and must be overridden by subclasses * * @param widthMeasureSpec @param heightMeasureSpec in the vertical direction, Public final void Measure (int widthMeasureSpec, int heightMeasureSpec) {public final void Measure (int widthMeasureSpec, int heightMeasureSpec) { onMeasure(widthMeasureSpec, heightMeasureSpec); . } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
Copy the code

4. Layout related concepts and core methods

The first thing to make clear is that the location of the child view is relative to the parent view. The onLayout method of a View is empty and the onLayout method of a ViewGroup is abstract. Therefore, if a custom View is to inherit from a ViewGroup, it must implement the onLayout function.

During layout, the subview calls the getMeasuredWidth() and getMeasuredHeight() methods to obtain the measuredWidth and mMeasuredHeight obtained by the Measure process. As its own width and height. Then call the layout(L, T, R, b) function of each child view to determine the position of each child view in the parent view.

LinearLayout onLayout source analysis

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else{ layoutHorizontal(l, t, r, b); Void layoutVertical(int left, int top, int right, int bottom) {void layoutVertical(int left, int top, int right, int bottom) {for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    childTop += measureNullChild(i);
                } else if(child.getVisibility() ! ChildWidth = child.getMeasuredWidth(); child.getMeasuredWidth(); child.getMeasuredWidth(); // Width final int childHeight = child.getMeasuredheight (); // Measure height... Determine the values of childLeft and childTopsetChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                }
            }
    }

    private void setChildFrame(View child, int left, int top, int width, int height) {        
        child.layout(left, top, left + width, top + height);
    }   

    View.java
    public void layout(int l, int t, int r, int b) {
        ...
        setFrame(l, t, r, b)} /** * Set the coordinates on the child View relative to its parent */ protected BooleansetFrame(int left, int top, int right, int bottom) {
        ...
     }
Copy the code

5. Related concepts and core methods of drawing process

Let’s start with the functions related to the DRAW process:

  • View.draw(Canvas Canvas) : Since the ViewGroup does not copy this method, all views end up being drawn by calling the View’s draw method. In a custom view, this method should not be overridden, but the onDraw(Canvas) method should be overridden. If the custom view does want to overwrite this method, call super.draw(Canvas) first to complete the system drawing, and then do the custom drawing.

  • View.ondraw () : View’s onDraw(Canvas) is an empty implementation by default, and the custom drawing process requires a method that overwrites its own content.

  • DispatchDraw () initiates the drawing of the subview. The View defaults to an empty implementation and the ViewGroup overwrites dispatchDraw() to draw its subviews. We don’t need to worry about this method, custom viewgroups should not duplicate dispatchDraw().

Drawing flow chart

View.draw(Canvas) source code analysis

/**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to whichRendered. The View is rendered automatically based on the given Canvas (including all of its child views). You must complete the Layout before calling this method. When you customize a view, you should implement the onDraw(Canvas) method instead of the Draw(Canvas) method. If you do need to override the method, remember to call the parent method first. */ public void draw(Canvas canvas) { / * Draw traversal performs several drawing stepswhich must be executed
         * in the appropriate order:
         *
         *      1. Draw the background if need
         *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
         *      4. Draw children (dispatchDraw)
         *      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 & 5if possible (common case)
        final int viewFlags = mViewFlags;
        if(! verticalEdges && ! horizontalEdges) { // Step 3, draw the contentif(! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas);if(mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // we're done... return; } // Step 2, save the canvas' layers
        ...

        // Step 3, draw the content
        if(! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); }Copy the code

From the above process, we can also derive some optimization tips: the second and fifth steps will be skipped when the Layer is not needed. Therefore, in the process of drawing, the layer can be saved as much as possible, which can improve the drawing efficiency

Viewgroup.dispatchdraw () source code analysis

dispatchDraw(Canvas canvas){

...

 if((flags & FLAG_RUN_ANIMATION) ! = 0 &&cananimate ()) {final Boolean buildCache =! isHardwareAccelerated();for (int i = 0; i < childrenCount; i++) {
                final View child = children[i];
                if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {// Draw only VISIBLE layouts, Final LayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, childrenCount); // Add animation for layout changesbindLayoutAnimation(child); // Bind the animation to Childif (cache) {
                        child.setDrawingCacheEnabled(true);
                        if (buildCache) {
                            child.buildDrawingCache(true);
                        }
                    }
                }
            }

    final LayoutAnimationController controller = mLayoutAnimationController;
            if(controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; } controller.start(); // Start the View animation} // Draw ChildViewfor (int i = 0; i < childrenCount; i++) {
            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
            if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { more |= drawChild(canvas, child, drawingTime); }}... } protected boolean drawChild(Canvas canvas, View child, long drawingTime) {return child.draw(canvas, this, drawingTime);
}

/**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     * This draw() method is an implementation detail and is not intended to be overridden or
     * to be called from anywhere else other than ViewGroup.drawChild().
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
    }

Copy the code

DrawChild (canvas, this,drawingTime) directly calls the View’s child.draw(canvas, this,drawingTime) method. You should not override or call this method anywhere except by the viewGroup.drawChild () method, which belongs to the ViewGroup. The view.draw(Canvas) method is the method that can be copied in our custom control. For details, please refer to the above description of view.draw(Canvas). Draw (canvas, this, drawingTime) must handle the parent View’s logic, but the View’s final drawing is still the view.draw (canvas) method.

Invalidate () requests the View tree to be redrawn. The draw procedure does not call layout() if the View size does not change, and draws only views that call the invalidate() method.

RequestLayout () calls this method when the layout changes, such as orientation or size changes. In custom views, if you want to remeasure the size in some cases, you should manually call this method. It triggers the measure() and Layout() procedures, but does not draw.