Android View related-View drawing process analysis

Starting from this chapter, let's learn together the more important View-related knowledge in Android. In this section, we will first look at the drawing process of View.

We know that any layout and control in Android will eventually inherit the View class directly or indirectly (ViewGroup also inherits View), which means that all related controls or layouts will use the same drawing process. We know that the starting point of the Android drawing process is the performTraversals() method of the ViewRootImpl class. The main function of this method is to judge whether the view size needs to be recalculated (Measure), whether the view position needs to be repositioned (layout), and whether the View needs to be redrawn (draw) according to the previously set state. There is a huge amount of code and logic in this method. It is too annoying to read in detail. Let's cut down the code and look at the core content of this class:

private void performTraversals() {
    ...
    //获取了尺寸规格,这是一个32位的int值,前两位代表测量模式(SpecMode),后30位代表数值(SpecSize),

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //后续所有View的MeasureSpec都是从这里来的
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    mView.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
    mView.draw(canvas);
}

It can be seen that the performTraversals() is used to determine whether to measure, place and draw the control, etc. Several important steps related to the View drawing process are measure (measurement), layout (position), draw (drawing), and its execution process Yes:

Here we explain these three steps one by one:

Measure

We know that all our control layouts are ultimately ViewGroup or View (ViewGroup is also a special View, we will distinguish the two when it comes to drawing process and event distribution). Let's first take a look at the measure method of the View class:

/**
 * 这个方法是为了确定View的大小,并且会为子View提供宽高的约束条件
 *为整个View树计算实际的大小,然后设置实际的高和宽,每个View控件的实际宽高都是由父视图和自身决定的。
 *实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,
 *这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。
 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
    heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    ...
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ...
}

/**
 * 继承View若有需求需要复写该方法,因为measure是final的
 *
 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

We can see from the performTraversals method of ViewRootImpl above that the two size specifications of the measure method are passed from its parent control, and the size specification of width and height is a 32-bit int variable, which stores the information required for measurement . The first two digits store the measurement mode, and there are three measurement modes in total:

  • MeasureSpec.EXACTLY: indicates a certain size
  • MeasureSpec.AT_MOST: Indicates getting the maximum size
  • MeasureSpec.UNSPECIFIED: Uncertain

The last thirty bits save the size, which is SpecSize, which is the size of the parent control. For the DecorVIew object of the system Window class, the Mode is generally MeasureSpec.EXACTLY, and the size corresponds to the screen width and height respectively. For the child View, the size is determined by the parent View and the child View.

For a View that is not a ViewGroup, the specific measurement will be done in onMeasure, and the default setMeasuredDimension of the View can help the system set the size of the View very well. Let's take a look at the default measurement of View, what is the final width and height:

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

It can be seen that the measurement mode is obtained here, and the width and height are initialized according to different measurement modes. If it is UNSPECIFIED, it is set to the minimum value (the minimum value is determined by the background and minHeight properties of the View), if it is AT_MOST or EXACTLY , it is set to the maximum width and height of the parent control. Then the measurement process of the bottom View here is completed.

Earlier we also mentioned that the measurement process of ViewGroup is slightly different from that of View:

We can see that ViewGroup provides us with four methods for our measurement, namely measureChildren, measureChild, measureChildWithMargin, getChildMeasureSpec

We know that View is a nested tree structure, and its measure execution process is also a recursive process. Let's take a look at the measurement process of a ViewGroup subclass LinearLayout:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    measureChildBeforeLayout(
        child, i, widthMeasureSpec, 0, heightMeasureSpec,
        totalWeight == 0 ? mTotalLength : 0);
}

void measureChildBeforeLayout(View child, int childIndex,
                              int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
                              int totalHeight) {
    measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                            heightMeasureSpec, totalHeight);
}

You can see that LinearLayout goes to the measureChildWithMargins method of ViewGroup after a series of calls. Let's take a look at this method:

protected void measureChildWithMargins(View child,
                                       int parentWidthMeasureSpec, int widthUsed,
                                       int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                                                          mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                                                          + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                                                           mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                                                           + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

It can be clearly seen that the padding information and margin information of the sub-View and the size already occupied by other sub-Views are added and then calculated to obtain the size specification of the sub-View, and then call the measure method of the sub-View to complete the measurement. There is a very important method getChildMeasureSpec(), let's see what is done in this method:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //获取测量模式和测量尺寸
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    //获取最大尺寸
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;
    //根据测量模式分别计算
    switch (specMode) {
        //该ViewGroup测量模式是EXACTLY确定大小
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                //子View的宽高大于等于0,说明设置过,则将其设置为EXACTLY,size为原大小
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子View请求获取父控件大小,设置为EXACTLY并将父控件剩余最大尺寸设置给该测量模式
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子View要求获取自适应大小,设置为AT_MOST并将父控件剩余最大尺寸设置给该测量模式
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        //该ViewGroup测量模式为AT_MOST
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                //子View的宽高大于等于0,说明设置过,则将其设置为EXACTLY,size为原大小
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子View请求获取父控件大小,设置为与父控件相同的AT_MOST并将父控件剩余最大尺寸设置给该测量模式
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子View要求获取自适应大小,设置为AT_MOST并将父控件剩余最大尺寸设置给该测量模式
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        //该ViewGroup测量模式为UNSPECIFIED
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                //子View的宽高大于等于0,说明设置过,则将其设置为EXACTLY,size为原大小
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子View请求获取父控件大小,设置为与父控件相同的UNSPECIFIED并将父控件剩余最大尺寸设置给该测量模式
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子View要求获取自适应大小,设置为UNSPECIFIED并将父控件剩余最大尺寸设置给该测量模式
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    //返回一个MesureSpec
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

As you can see, this method uses its own MesureSpec and the width and height values ​​of the child View to finally determine the MesureSpec of the child View.

Here we have figured out the measurement process of the ViewGroup:

  1. First call the mesure method of ViewGroup. We generally inherit ViewGroup and override its onMeasure method, as shown in the previous LinearLayout
  2. Call the onMeasure method of ViewGroup, traverse all its child Views in the method and measure the size of the child Views
  3. Use the MeasureSpec and the size of the child View generated in the own measure method to determine the size specification of the child View, and hand it over to the measure method of the child VIew to measure
  4. measure will call setMeasuredDimension() by default to complete the final measurement through the minimum size and the MeasureSpec of the child VIew.

Summary of the measurement process

  • MeasureSpec: measurement specification, a 32-bit int type number, the first two digits save the measurement mode SpecMode, and the last 30 digits save the specific size SpecSize. Among them, SpecMode is divided into three types:
MeasureSpec.EXACTLY //确定大小,父View希望子View的大小是确定的,由specSize决定;
MeasureSpec.AT_MOST //最大尺寸,父View希望子View的大小最多是specSize指定的值;
MeasureSpec.UNSPECIFIED //未指定,父View完全依据子View的设计值来决定; 
  • If you want to override the measure method of View, because the measure method is modified by the final keyword, it cannot be overridden. You can override the onMeasure method if necessary.
  • The MeasureSpec when measuring the topmost DecorView is determined by the getRootMeasureSpec method in ViewRootImpl (LayoutParams width and height parameters are MATCH_PARENT, specMode is the size specified by EXACTLY, and specSize is the screen size)
  • ViewGroup provides methods such as measureChildren, measureChild, measureChildWithMargin, and getChildMeasureSpec to simplify the way its subclasses obtain MeasureSpec
  • The size of a ViewGroup is determined by its child View and its parent View
  • The measurement process of View is measure -> onMeasure -> setMeasuredDimension

layout

Similar to the measurement process, the layout process is also a recursive call. We turn our attention back to the performTraversals() method of the iewRootImpl class. We can see that after the measure method, mView.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());such a method is called immediately. Let's check this method the code:

public void layout(int l, int t, int r, int b) {
    ...
    boolean changed = isLayoutModeOptical(mParent) ?
        setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        ...
        onLayout(changed, l, t, r, b);
        ...
    }
}

Similar to the measurement process, the final layout method calls onLayout, and onLayout in the View class is an empty method

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

Then let's follow the clues and take a look at the layout and onLayout in the ViewGroup:

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

/**
     * {@inheritDoc}
     */
@Override
protected abstract void onLayout(boolean changed,
                                 int l, int t, int r, int b);

Here you can see that the layout method is a final modified method that cannot be overridden by subclasses, while onLayout is an abstract method, which requires its subclasses to implement this method. We can't see the clue here, so let's open the LinearLayout just now to see what it has done in it overriding onLayout:

@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 layoutHorizontal(int left, int top, int right, int bottom) {
    final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
    final int paddingTop = getPaddingTop();

    int childTop;
    int childLeft;

    //获取当前ViewGroup高度(bottom - top)
    final int height = bottom - top;
    //获取底部View的位置
    int childBottom = height - getPaddingBottom();

    //ViewGroup可供子View 显示的高度
    int childSpace = height - paddingTop - getPaddingBottom();
    //获取子View个数
    final int count = getVirtualChildCount();
    //获取Gravity相关信息
    final int majorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;

    final boolean baselineAligned = mBaselineAligned;

    final int[] maxAscent = mMaxAscent;
    final int[] maxDescent = mMaxDescent;

    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    //根据Gravity来设置childLeft值
    switch (GravityCompat.getAbsoluteGravity(majorGravity, layoutDirection)) {
        case Gravity.RIGHT:
            // mTotalLength contains the padding already
            childLeft = getPaddingLeft() + right - left - mTotalLength;
            break;
        case Gravity.CENTER_HORIZONTAL:
            // mTotalLength contains the padding already
            childLeft = getPaddingLeft() + (right - left - mTotalLength) / 2;
            break;
        case Gravity.LEFT:
        default:
            childLeft = getPaddingLeft();
            break;
    }

    int start = 0;
    int dir = 1;
    //In case of RTL, start drawing from the last child.
    if (isLayoutRtl) {
        start = count - 1;
        dir = -1;
    }
    //对各个子View进行遍历
    for (int i = 0; i < count; i++) {
        int childIndex = start + dir * i;
        final View child = getVirtualChildAt(childIndex);

        if (child == null) {
            childLeft += measureNullChild(childIndex);
        } else if (child.getVisibility() != GONE) {
            //获取子View的宽度和高度(onMeasure计算出来的)
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            int childBaseline = -1;

            final LinearLayoutCompat.LayoutParams lp =
                (LinearLayoutCompat.LayoutParams) child.getLayoutParams();

            if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
                childBaseline = child.getBaseline();
            }

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
           //根据Gravity来设置childTop
            switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
                case Gravity.TOP:
                    childTop = paddingTop + lp.topMargin;
                    if (childBaseline != -1) {
                        childTop += maxAscent[INDEX_TOP] - childBaseline;
                    }
                    break;

                case Gravity.CENTER_VERTICAL:
                    // Removed support for baseline alignment when layout_gravity or
                    // gravity == center_vertical. See bug #1038483.
                    // Keep the code around if we need to re-enable this feature
                    // if (childBaseline != -1) {
                    //     // Align baselines vertically only if the child is smaller than us
                    //     if (childSpace - childHeight > 0) {
                    //         childTop = paddingTop + (childSpace / 2) - childBaseline;
                    //     } else {
                    //         childTop = paddingTop + (childSpace - childHeight) / 2;
                    //     }
                    // } else {
                    childTop = paddingTop + ((childSpace - childHeight) / 2)
                        + lp.topMargin - lp.bottomMargin;
                    break;

                case Gravity.BOTTOM:
                    childTop = childBottom - childHeight - lp.bottomMargin;
                    if (childBaseline != -1) {
                        int descent = child.getMeasuredHeight() - childBaseline;
                        childTop -= (maxDescent[INDEX_BOTTOM] - descent);
                    }
                    break;
                default:
                    childTop = paddingTop;
                    break;
            }
           //累加childLeft
            if (hasDividerBeforeChildAt(childIndex)) {
                childLeft += mDividerWidth;
            }

            childLeft += lp.leftMargin;
            //调用子View的layout方法
            setChildFrame(child, childLeft + getLocationOffset(child), childTop,
                          childWidth, childHeight);
            //累加childLeft
            childLeft += childWidth + lp.rightMargin +
                getNextLocationOffset(child);

            i += getChildrenSkipCount(child, childIndex);
        }
    }
}

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

We can see from the code that the width and height set by onMeasure are generally obtained, and then the child View is traversed, the placement position of the child VIew is set according to the needs of the control itself, and the layout method of the child View is called to place it.

This is the summary of the entire layout process, which is simpler than measure, and we don't need to care about the layout process in View

Summary of layout process

The entire layout process is relatively simple. The process of recursively calling the layout method of the sub-View from the top-level View down, in which the ViewGroup determines the left, top, right, bottom and other values ​​of the sub-View through the width and height of the sub-View obtained in the measurement process, and Use these values ​​to place each View in the appropriate position

  • The layout method of View can be overridden, while the layout method of ViewGroup is modified by the final keyword and cannot be overridden anymore
  • Use the getWidth() and getHeight() methods of the View to obtain the width and height measured by the View. You must ensure that these two methods are called after the onLayout process to return valid values.
  • After the measure operation is completed, the measuredWidth and measuredHeight of each View are obtained. After the layout operation is completed, the left, top, right, and bottom of each View are obtained (relative to the parent control)

draw

Next comes the last step of the View drawing process, which we can see in the performTraversals() method of ViewRootImpl:

final Canvas canvas;
canvas = mSurface.lockCanvas(dirty);
mView.draw(canvas);

ok, let's continue to look at the draw method in View:

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
        (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /*
         * 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
    int saveCount;

    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);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // we're done...
        return;
    }

    /*
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,
         * this is why we repeat some of the tests that have been
         * done above)
         */

    boolean drawTop = false;
    boolean drawBottom = false;
    boolean drawLeft = false;
    boolean drawRight = false;

    float topFadeStrength = 0.0f;
    float bottomFadeStrength = 0.0f;
    float leftFadeStrength = 0.0f;
    float rightFadeStrength = 0.0f;

    // Step 2, save the canvas' layers
    int paddingLeft = mPaddingLeft;

    final boolean offsetRequired = isPaddingOffsetRequired();
    if (offsetRequired) {
        paddingLeft += getLeftPaddingOffset();
    }

    int left = mScrollX + paddingLeft;
    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    int top = mScrollY + getFadeTop(offsetRequired);
    int bottom = top + getFadeHeight(offsetRequired);

    if (offsetRequired) {
        right += getRightPaddingOffset();
        bottom += getBottomPaddingOffset();
    }

    final ScrollabilityCache scrollabilityCache = mScrollCache;
    final float fadeHeight = scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;

    // clip the fade length if top and bottom fades overlap
    // overlapping fades produce odd-looking artifacts
    if (verticalEdges && (top + length > bottom - length)) {
        length = (bottom - top) / 2;
    }

    // also clip horizontal fades if necessary
    if (horizontalEdges && (left + length > right - length)) {
        length = (right - left) / 2;
    }

    if (verticalEdges) {
        topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
        drawTop = topFadeStrength * fadeHeight > 1.0f;
        bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
        drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
    }

    if (horizontalEdges) {
        leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
        drawLeft = leftFadeStrength * fadeHeight > 1.0f;
        rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
        drawRight = rightFadeStrength * fadeHeight > 1.0f;
    }

    saveCount = canvas.getSaveCount();

    int solidColor = getSolidColor();
    if (solidColor == 0) {
        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

        if (drawTop) {
            canvas.saveLayer(left, top, right, top + length, null, flags);
        }

        if (drawBottom) {
            canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
        }

        if (drawLeft) {
            canvas.saveLayer(left, top, left + length, bottom, null, flags);
        }

        if (drawRight) {
            canvas.saveLayer(right - length, top, right, bottom, null, flags);
        }
    } else {
        scrollabilityCache.setFadeColor(solidColor);
    }

    // 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
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;

    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, top, right, top + length, p);
    }

    if (drawBottom) {
        matrix.setScale(1, fadeHeight * bottomFadeStrength);
        matrix.postRotate(180);
        matrix.postTranslate(left, bottom);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, bottom - length, right, bottom, p);
    }

    if (drawLeft) {
        matrix.setScale(1, fadeHeight * leftFadeStrength);
        matrix.postRotate(-90);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, top, left + length, bottom, p);
    }

    if (drawRight) {
        matrix.setScale(1, fadeHeight * rightFadeStrength);
        matrix.postRotate(90);
        matrix.postTranslate(right, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(right - length, top, right, bottom, p);
    }

    canvas.restoreToCount(saveCount);

    // Overlay is part of the content and draws beneath Foreground
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
}

It can be seen from the comments that the drawing process is divided into 6 steps:

  1. To draw the View background, this step is achieved by calling its private method drawBackground, which internally involves the use of Canvas, and will be explained in a separate article later
  2. Save Canvas Hierarchy (can be skipped)
  3. To draw the View content, call onDraw to achieve it. The default onDraw is an empty method, which needs to be implemented by ourselves.
  4. Use dispatchDraw to draw all child Views (empty method in the View class because there is no word View)
  5. Draw gradient effect and restore layer (can be skipped)
  6. Draw foreground color, scroll bar, call onDrawForeground to achieve

Here we need to pay attention to the dispatchDraw method in ViewGroup:

/**
     * {@inheritDoc}
     */
@Override
protected void dispatchDraw(Canvas canvas) {
    //核心代码,遍历挨个调用子View的draw方法
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
        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);
        }
    }
    while (transientIndex >= 0) {
        // there may be additional transient views after the normal views
        final View transientChild = mTransientViews.get(transientIndex);
        if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
            transientChild.getAnimation() != null) {
            more |= drawChild(canvas, transientChild, drawingTime);
        }
        transientIndex++;
        if (transientIndex >= transientCount) {
            break;
        }
    }
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

It can be seen that the final call still returns to the draw method of the View, and then starts to execute the above process.

Drawing process summary

Drawing is a process of displaying View on the screen. There are some details in the process that we need to pay more attention to:

  • View only draws the background by default. The drawing of the specific content needs to be implemented in the subclass, that is, it needs to be drawn in the onDraw method.
  • ViewGroup needs to override the dispatchDraw method to draw all child Views
  • The drawing process in Android is inseparable from the support of Canvas (paint will be introduced later)
  • By default, the ViewGroup.drawChild drawing order of the child View is the same as the order in which the child View is added, but you can also override the ViewGroup.getChildDrawingOrder() method to provide a different order.

Summarize

At this point, our Android View drawing process is over, and there are many things, but there are not many core things. As long as you understand recursion in depth, you can understand what the whole process is like.

Next, I will try to understand the use of invalidate , postInvalidate and requestLayout in View and ViewGroup , to analyze the usage and principles of these three methods and their functions, and I will also try to learn the process of customizing View. And the use of Canvas, etc., please look forward to it~

My personal blog , welcome to communicate~

enjoy~

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325542857&siteId=291194637