美文网首页
View绘制流程

View绘制流程

作者: code希必地 | 来源:发表于2020-09-10 17:44 被阅读0次

    1、ViewRootImpl.performTraversals()

    View的绘制流程是在ViewRootImpl.performTraversals()中完成的。

    private void performTraversals() {
                //...
            if (!mStopped) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  // 1
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);       
                }
            } 
    
            if (didLayout) {
                performLayout(lp, desiredWindowWidth, desiredWindowHeight);
                //...
            }
    
    
            if (!cancelDraw && !newSurface) {
                if (!skipDraw || mReportNextDraw) {
                    if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                        for (int i = 0; i < mPendingTransitions.size(); ++i) {
                            mPendingTransitions.get(i).startChangingAnimations();
                        }
                        mPendingTransitions.clear();
                    }
    
                    performDraw();
                }
            } 
            //...
    }
    

    performTraversals()会依次调用performMeasureperformLayoutperformDraw这三个方法,这三个方法会依次调用measurelayoutdraw来完成最顶层ViewDecorView的测量、布局和绘制。
    其中performMeasure调用了measure方法,在measure中又会调用onMeasure,在onMeasure中会遍历子View,调用子View的measure,measure流程就从父容器传递到了子元素中,这样就完成了一次测量流程,重复这个流程就完成了整个View树的测量。performLayoutperformDraw也有相同的流程,唯一不同的是performDraw是通过dispatchDraw向子View传递的。
    measure就是测量View的宽和高,layout是确定子View在布局中的位置,draw就是绘制View显示在屏幕上。

    2、measure

    在讲measure之前,我们先看下MeasureSpec

    2.1、MeasureSpec

    MeasureSpec概括了父容器传给子元素的布局要求,它代表一个32位的int值,前2位代表SpecMode,后30位代表SpecSize。SpecMode是指测量模式,而SpecSize是指某种测量模式下的规格大小。
    SpecMode有三种:

    • 1、UNSPECIFIED
      表示父容器不限制子View的大小,想显示多大展示多大,
    • 2 、EXACTLY
      表示父容器已经检测出View所需的精确大小,这时候View的最终大小就是SpecSize所指定的大小,对应LayoutParams中的match_parent和确切的值如:android:layout_width="100dp"
    • 3、AT_MOST
      表示子View不能超过父容器指定的大小即SpecSize。它对用于LayoutParams中的wrap_content

    2.2、MeasureSpec和LayoutParams的关系

    在View进行测量的时候,会在父容器的约束下将View的LayoutParams转换成对应MeasureSpec,然后在根据新的MeasureSpec来测量View的宽和高。
    View的measure过程是由父容器传递过来的,先看下ViewGroup的measureChildWithMargins()
    ViewGroup##measureChildWithMargins()

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

    上述方法会调用子View的measure方法进行View的测量,但是在测量之前会根据父容器传递过来的parentMeasureSpec和子View的LayoutParams以及本身的padding和子View的margin来创建新measureSpec,然后使用新的MeasureSpec再进行测量。
    ViewGroup##getChildMeasureSpec()

    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) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    

    上述代码已经非常清晰的描述了View的MeasureSpec的创建规则。
    下面我们用表格表示一下:

    childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
    dp/px EXACTLY EXACTLY EXACTLY
    match_parent EXACTLY AT_MOST UNSPECIFIED
    wrap_content AT_MOST AT_MOST UNSPECIFIED
    • 当View的宽高为确切的大小,无论父容器传递过来任何测量模式,View都是EXACTLY模式,并且大小遵循我们设置的值。
    • 当View为match_parent,只有父容器传递过来的模式为EXACTLY,View的测量才是EXACTLY模式,其大小就是父容器剩余空间。如果父容器是AT_MOST那么View也是AT_MOST并且View的大小不能超过父容器的剩余空间。
    • 当View为wrap_content,View都是AT_MOST并且大小不能超过父容器的剩余空间。

    2.3、View的measure过程

    measure最终会调用onMeasure,View的实际测量工作是在onMeasure中完成的

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    

    上述代码很简单就是通过setMeasuredDimension设置View的宽高。其宽高是在getDefaultSize()中计算的。

     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;
        }
    
    • 当测量模式为AT_MOST或者EXACTLY直接返回specSize,而这个specSize就是测量后的大小。根据上面的表格可知,当View为match_parent或wrap_content时返回的大小都是父容器的剩余空间大小。所以如果直接继承View需要自己处理wrap_content的情况。
    • 当测量模式为UNSPECIFIED,返回的是getSuggestedMinimumWidth()
    protected int getSuggestedMinimumWidth() {
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }
    

    如果设置了背景则取背景大小和属性android:minWidth的最大值,否则直接返回属性android:minWidth的值,如果没有设置该属性则返回0。

    2.4、ViewGroup的measure过程

    ViewGroup除了测量本身的大小,还需要遍历调用子View的measure去测量子View的大小。ViewGroup是一个抽象类,它并没有实现onMeasure方法。ViewGroup无法确定其子View是如果排列的,所以ViewGroup无法给出一个统一的计算方式。只能由具体的ViewGroup的实现类自己去实现onMeasure。但是其内部提供了measureChildren
    measureChildrenWitnMargins方法,其内部的实现也很简单:就是遍历子View调用measure,不过在测量之前会根据传递过来的MeasureSpec和子View的LayoutParams来构建新的MeasureSpec,然后使用新的MeasureSpec去测量子View的大小。

    3、layout过程

    layout()就是确定View在父容器中的位置,然后在onLayout()中又会去遍历子View并调用子View的layout()方法。这里看下View的layout()

    public void layout(int l, int t, int r, int b) {
            if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
                onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
    
            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);
    
               //......
        }
    

    layout()中通过setFrame()方法来设置View四个顶点的位置,即初始化mRight , mLeft, mBottom , mTop这个四个值,View的四个顶点一旦确定,在父容器中的位置也就确定了。接着会调用onLayout(),该方法中并没做任何的实现,onLayout()的实现和具体的布局有关系,所以View和ViewGroup均没有做实现。

    4、View的draw过程

    View的draw作用就是将View绘制到屏幕上显示,View的绘制遵循如下几步:

    • 1、绘制背景
    • 2、绘制自己,具体的实现在onDraw()
    • 3、绘制children,通过dispatchDraw()遍历子View并调用子View的draw()
    • 4、绘制装饰
      View##draw()
    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);
    
                drawAutofilledHighlight(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);
    
                // Step 7, draw the default focus highlight
                drawDefaultFocusHighlight(canvas);
    
                if (debugDraw()) {
                    debugDrawFocus(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) {
                if (drawTop) {
                    canvas.saveUnclippedLayer(left, top, right, top + length);
                }
    
                if (drawBottom) {
                    canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
                }
    
                if (drawLeft) {
                    canvas.saveUnclippedLayer(left, top, left + length, bottom);
                }
    
                if (drawRight) {
                    canvas.saveUnclippedLayer(right - length, top, right, bottom);
                }
            } 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);
    
            drawAutofilledHighlight(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);
    
            if (debugDraw()) {
                debugDrawFocus(canvas);
            }
        }
    

    相关文章

      网友评论

          本文标题:View绘制流程

          本文链接:https://www.haomeiwen.com/subject/vvkasktx.html