View的工作原理

view经过measure、layout、draw三个过程才将一个view绘制出来,其中measure负责测量view,layout负责确定view在父容器中的位置,draw负责将view绘制在屏幕上。在measure、layout方法中又会调用onMeasure、onLayout方法,完成对子view的测量和定位,在draw方法中会调用dispatchDraw方法,对子view的绘制。

1、MeasureSpace

MeasureSpace是一个32位的int值,高两位代表SpecMode,低30位代表SpecSize。对于普通的view,其MeasureSpace由父容器的MeasureSpace和其自身的LayoutParams共同决定。
SpecMode有三类:

  • EXACITY:
    如果在当前view的LayoutParams中,指定了具体的宽高或者宽高是MATCH_PARENT(父容器是精确模式),那么当前view的SpecMode是精确模式,SpecSize是在LayoutParams中指定的具体值或者是父容器的剩余空间。
  • AT_MOST:
    如果在当前view的LayoutParams中,设置的宽高是WARAP_CONTENT,或者父容器的SpecMode是最大模式(但是当前view的LayoutParams不是具体值),那么当前view的SpecMode是最大模式,SpecSize不能超过父容器的剩余空间。
  • UNSPECIFIED:父容器不对view的大小有任何限制,当前view想要多大就给多大,listview,scrollview。

如果view采用固定宽高,其MeasureSpace是精确模式,大小就是在LayoutParams中指定的大小;当父容器的MeasureSpace是精确模式时,如果当前view的LayoutParams是MATCH_PARENT,该view的MeasureSpace是精确模式,大小是父容器的剩余空间;如果当前view的LayoutParams是WARAP_CONTENT,或者当父容器的MeasureSpace是最大模式时,该view的MeasureSpace是最大模式,大不能超过父容器的剩余空间。

2、measure过程

  • measure方法用来测量view的大小,但是实际的测量过程是在onMeasure方法中进行的。
  • measure方法是final,子view不能重写。
  • 主要是通过对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值来完成测量的。
View的工作原理_第1张图片
view树的measure过程 来自http://blog.csdn.net/yanbober/
1、view的measure过程
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        //回调onMeasure()方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
    }

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

通过setMeasuredDimension方法对view的成员变量mMeasuredWidth、mMeasuredHeight进行赋值

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ...
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

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

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

在onMeasure方法中,可以根据父容器的MeasureSpace和view的LayoutParams,计算view的大小

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

但是在系统的默认实现中,最大模式下view的大小等于父容器的剩余空间,可以重写它

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth,mHeight);
        }else if (widthSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth,heightSpecSize);
        }else if ( heightSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,mHeight);
        }else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
2、viewgroup的measure过程

viewgroup没有实现onMeasure方法,不同的viewgroup子类具有不同的布局特征,这也导致他们的测量细节各不相同。可以在其子类中实现onMeasure方法。

例如, LinearLayout 的measure过程

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

LinearLayout会遍历每个子view,并最终调用每个子view的measure方法。当子元素测量完毕后,LinearLayout会测量自己的大小。

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        ...
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            ...
            final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
            measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,heightMeasureSpec, 0);
            ...
            ...
        }
        ...

        mTotalLength += mPaddingTop + mPaddingBottom;
        int heightSize = mTotalLength;
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        ...
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

    }

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
    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);
    }

3、layout过程

layout方法确定当前view四个顶点相对与父容器的位置,即为mLeft、mTop、mBottom、mRight赋值;在onLayout方法中会循环调用子view的layout方法,确定子view的位置。

1、view的layout过程

view在layout方法中已经得到了四个顶点位置,所以onLayout()是一个空实现

public void layout(int l, int t, int r, int b) {  
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
    // 确定View的位置
    // 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回  
    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);  
  ...

}  

private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    Insets parentInsets = mParent instanceof View ?((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();

    return setFrame(
            left   + parentInsets.left - childInsets.left,
            top    + parentInsets.top  - childInsets.top,
            right  + parentInsets.left + childInsets.right,
            bottom + parentInsets.top  + childInsets.bottom);
}

protected boolean setFrame(int left, int top, int right, int bottom) {
    ...
    // 确定View的四个顶点
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;

    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
2、viewgroup的layout过程

在layout方法中,可以确定viewgroup自身的位置。但是如果要确定子view在viewgroup的位置,需要实现onLayout方法。由于确定位置与具体布局有关,所以onLayout方法在ViewGroup中是一个抽象的方法:需要具体的子类去实现。
例如,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);
        }
    }


在layoutVertical中遍历view,并根据layoutDirection和gravity计算每个子view的childTop、childLeft,调用setChildFrame,完成对子view的定位。

    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();
            int gravity = lp.gravity;

            ...
            //根据layoutDirection和gravity计算childTop、childLeft
            ...
            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

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


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

3、draw过程

view的绘制过程如下:

  • 1、绘制背景
  • 2、绘制自己
  • 3、绘制children
  • 4、绘制装饰
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

        // Step 1, draw the background, if needed    
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // Step 2, save the canvas' layers

        // Step 3, draw the content 通常在自定义view的时候,需要子类实现
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children  遍历子View并绘制包含的所有子View
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
    
        // Step 6, draw decorations (foreground, scrollbars) (滚动指示器、滚动条、和前景)
        onDrawForeground(canvas);
    }

注意:
viewgroup默认情况下是不需要绘制内容的,如果需要绘制内容,可以在构造函数中设置setWillNotDraw(false)

参考: Android开发艺术探讨、 Android应用层View绘制流程与源码分析、自定义View Measure过程 - 最易懂的自定义View原理系列、自定义View Layout过程 - 最易懂的自定义View原理系列、自定义View Draw过程- 最易懂的自定义View原理系列

你可能感兴趣的:(View的工作原理)