View的绘制流程

View的绘制是从ViewRootperformTraversals()开始的,该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘(draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个。performTraversals()这个方法完成了对顶级View的measure,layout,draw三个过程,其中又会分别对子View进行遍历,实现整个View的绘制。

 

private void performTraversals() {
        ......
        
        //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
        ......
        mView.draw(canvas);
        ......
    }

在第5~6行获取了根视图的MeasureSpec,size等于屏幕的大小,mode是At_Most。接着就调用measure()方法进行测量,所以首先看看measure的过程。


View的measure过程


measure()方法

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        // measure ourselves, this should set the measured dimension flag back
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......

    }

其中传入的两个参数信息是宽高的参数信息,用于计算实际宽高,它真实的测量是在onMeasure(int, int)中完成的,并且可以看到measure()方法是一个final方法,所以不能重写,所以我们只需要重写onMeasure()方法。至于widthMeasureSpec和heightMeasureSpec从哪里来的,下面会分析到。

onMeasure()方法

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
这个方法里面很简单,就调用了setMeasuredDimension()方法,如果我们重写onMeasure()方法必须去调用这个setMeasuredDimension()方法,它去存储了view的宽高的测量信息。这里传递进来的宽高信息都是用MeasureSpec存储的,先看看这个MeasureSpec

MeasureSpec

它是一个32位的int值,高两位代表模式,低30位代表大小,模式有以下三种:

UNSPECIFIED:父容器对这个view没有任何限制,可以得到任意它想要的大小

EXACTLY:父容器已经决定了View的精确大小,也就是父容器的SpecSize的大小,相当于我们的match_parent

AT_MOST:父容器指定了一个大小SpecSize,View不能超过这个大小,具体根据实际情况,相当于wrap_content


一个View的MeasureSpec确定了,再通过onMeasure()方法,那么它的大小也就确定了,一个View的MeasureSpec的确定是由父容器的MeasureSpec和它自身的LayoutParams共同决定的。


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;
    }
这个方法是根据传入的size和父类的MeasureSpec来返回一个默认的大小。上面可以看到这里的size是通过getSuggestedMinimumWidth或者getSuggestedMinimumHeight获取的,再通过MeasureSpec计算出它的大小,这里当mode为At_Most和Exactly,返回的都是specSize。

第3~4行,根据父类的MeasureSpec来计算出其中的mode和size。

第6~14行,用一个switch语句根据父类不同的mode来返回不同的大小。

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
这个方法的意思就是如果view没有设置背景,那么宽度就是mMinWidth,也就是对应的android:minWidth这个属性,如果设置了背景自然就返回背景的宽度。


setMeasuredDimension()方法

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
这是一个必须在onMeasure中调用的方法,它会去存储测量的宽高。看看这里面调用的setMeasuredDimensionRaw()方法

 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
可以看到测量的宽高都被存储到mMeasuredWidth和mMeasuredHeight中了。

至此一个简单view的测量就完毕了。可以知道基本上view都是嵌套的,并不会一个view这么简单,所以下面看看ViewGroup的测量。


ViewGroup的measure()

对于ViewGrouo来说,除了完成自身的measure之外,还需要去遍历所以的子元素,去调用它们的measure()方法,然后各个子View再递归去执行这个过程。ViewGroup是一个抽象类,并没有去重写View的onMeasure()方法,并没有定义具体的测量过程,所以需要它的子类去实现onMeasure()方法,这是因为各个ViewGroup的子类的特效不同导致的测量方法不同,但是ViewGroup中提供了measureChildren, measureChild, measureChildWithMargins方法来进行测量。

measureChildren()

 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);
            }
        }
    }
这里是对ViewGroup进行遍历,只要其中的子view不是GONE,那么就调用measureChild()方法


measureChild()

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
这个方法里调用了getChildMeasureSpec()方法获取到子view的MeasureSpec。然后获得child的MeasureSpec后继续调用measure方法,这里也就进入了之前分析的view的测量。在这里可以看到之前我们measure()方法传入的参数就是这里通过getChildMeasureSpec()方法得到的,下面看看这个子view的MeasureSpec是怎么计算出来的。

getChildMeasureSpec()

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //获取父类MeasureSpec的size和mode
        int specMode = View.MeasureSpec.getMode(spec);
        int specSize = View.MeasureSpec.getSize(spec);
        //得到父类的除去padding的剩余大小
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;
        //根据父类的mode做switch语句
        switch (specMode) {
            // Parent has imposed an exact size on us
            case View.MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    //如果layout_width(layout_height)属性设置了具体的值,并且大于0
                    //那么child的size就等于这个具体的值,mode为Exactly
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // 如果layout_width(layout_height)属性设置为match_parent
                    //意味着child想要尽可能大,最大为多少,当然是父类剩余的大小,这个大小是个确定的值所以mode为Exactly
                    resultSize = size;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // 如果layout_width(layout_height)属性设置为wrap_content
                    //意味着自身想要多大就给多大,但是不能超出父类的限制
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;
            //下面的情况就和上面的类似了
            // Parent has imposed a maximum size on us
            case View.MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = View.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 = View.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 = View.MeasureSpec.AT_MOST;
                }
                break;

            // Parent asked to see how big we want to be
            case View.MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = View.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 = View.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 = View.MeasureSpec.UNSPECIFIED;
                }
                break;
        }
        return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

这个方法主要就是通过自身的LayoutParams和父类的MeasureSpec来获得child的MeasureSpec,根据上面的情况可以得到下面的对照表:


记得之前在view的getDefaultSize()方法中,无论mode是At_Most或者Exactly都是返回specSize,根据这个表对照,可以知道当我们自定义一个View,对它的onMeasure不做任何处理的话,宽高设置成match_parent或者wrap_content,返回的大小都是parentSize,也就是充满父容器,当然设置具体的值返回的就是childSize了。所以那么如果我们要自定义view设置wrap_content时,不要充满父容器的效果该怎么实现,看看下面的处理方式。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //通过getChildMeasureSpec()方法获取到的MeasureSpec,获取到它们的mode和size
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //mWidth、mHeight是固定的值
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            //宽高都设置为wrap_content
            setMeasuredDimension(mWidth, mHeight);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //宽度设置为wrap_content
            setMeasuredDimension(mWidth, heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //高度设置为wrap_content
            setMeasuredDimension(widthSize, mHeight);
        }
    }
这样写当你自定义的View设置为wrap_content时就不会填满父容器,而是你指定的mWidth、mHeight。


measure总结

从最开始遍历ViewGroup的子View,如果这个子View是ViewGroup那么再重复上面的过程,如果是View,那么就调用它的measure()方法,在measure()方法中会调用onMeasure()方法,在onMeasure()又会最终调用setMeasuredDimension(),把测量值存储起来,这样递归调用就完成了View的测量过程。

measure()方法是final的,所以不能重写,只能重写onMeasure()方法实现自己的逻辑,但是在onMeasure()方法中必须去调用setMeasuredDimension(),因为这个方法才最终完成测量,所以要么自己去调用要么写supper.onMeasure()

一个View的大小是由MeasureSpec决定的,一个MeasureSpec是由它父类的MeasureSpec和自身的LayoutParams决定的。

可以调用getMeasuredWidth()和getMeasuredHeight()来获得测量大小,但是必须在测量后调用。


layout过程

layout是用来确定视图的位置,是View绘制的第二阶段,parent会调用每一个子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);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
这个方法大致流程是这样的,首先会判断位置是否发生了改变,看看是否需要重新layout,这里会调用setFrame方法来设定View四个顶点的位置,也就是初始化mLeft、mRight、mTop和mBottom;如果位置发生了变化就会调用onLayout方法,这个onLayout方法是空的,没什么好看的

ViewGroup的layout方法

 @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);
可以看到ViewGroup的layout方法是final类型的,并且onLayout方法是个抽象方法,所以ViewGroup的子类就必须实现这个onLayout方法,在这个方法中去确定子View的位置。既然如此,那么就来看看LinearLayout的onLayout方法是怎么实现的。

LinearLayout的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 layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        //得到宽度
        final int width = right - left;
        int childRight = width - mPaddingRight;

        //出去padding的宽度
        int childSpace = width - paddingLeft - mPaddingRight;

        final int count = getVirtualChildCount();
        //根据Gravity计算childTop
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        switch (majorGravity) {
            case Gravity.BOTTOM:
                // mTotalLength contains the padding already
                childTop = mPaddingTop + bottom - top - mTotalLength;
                break;

            // mTotalLength contains the padding already
            case Gravity.CENTER_VERTICAL:
                childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                break;

            case Gravity.TOP:
            default:
                childTop = mPaddingTop;
                break;
        }
        //开始遍历
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                //获取child测量后的宽高
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

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

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                //计算childLeft
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }
                //加上分割线的高度
                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }
                //加上子View的marginTop
                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                //childTop加上这个放置了的child的高度和marginBottom
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }
这段代码可以看到会遍历所以子View并调用setChildFrame方法指定子View的位置,在其中childTop的值会不断的增加,也就是依次向下放置这些子View。

所以在parent调用自己的layout()方法后,就会调用onLayout方法,在这个方法中又会去调用子View的layout方法,就这样一层层的传递下去就完成了整个View的layout。

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


之前我们知道了getMeasuredWidth方法可以得到测量后的宽度,那么这个方法和getWidth又有什么不同呢?看看这个方法

public final int getWidth() {
        return mRight - mLeft;
    }
这个mRight和mLeft是在layout过程中给他赋值的,也就是说这个方法必须在layout完成后调用。getWidth和getMeasuredWidth得到的值理论上来说都是一样的,只不过它们调用的时机不一样,getMeasuredWidth赋值要早一些,所以某些特殊情况它们的值可能不同。


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

            // 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;
        }
draw过程的传递是通过dispatchDraw()方法实现的,这个方法也是一个空方法,如果是一个ViewGroup就需要去实现,类似的在这里面调用子View的draw方法。




















  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值