学习笔记| (四)View的工作原理

知识体系:

  • ViewRoot和DecorView
  • MeasureSpec
  • View的工作流程
    • measure过程
    • layout过程
    • draw过程
  • 自定义view
    • 分类
    • 须知
    • 事例
    • 思想

一、ViewRoot和DecorView

1.ViewRoot对应ViewRootImpl,它是WindowManage和DocorView直接的纽带。Activity创建完了之后,通过setContentView添加布局,键DecorView添加到Window中,同时创建ViewRootImpl,将DecorView和ViewRootImpl关联起来。

理解:为什么要将DecorView和ViewRootImpl关联?
可以将DecorView理解为View的一个容器,这时候还没有展示出来,要通过ViewRoot才能将它展示出来;

2.View创建有三个流程:measure、layout、draw,这三个流程都需要ViewRoot来完成


学习笔记| (四)View的工作原理_第1张图片
performTraversals工作流程.png

以performxx开头的方法都是在RootViewImpl中被调用的,
①performMeasure:
在RootViewImpl中调用performMeasure,在performMeasure中会调用View的measure方法,在View的measure中又会调用View的onMeasure
②performLayout:
③performDraw:
在RootViewImpl中调用performDraw,在performDraw中会调用RootViewImpl的draw方法,在View的measure中又会调用View的onMeasure

从上面这个图中可以看出,View的绘制流程是从ViewGroup的performTraversals开始的,依次会调用performMeasure、performLayout、performDraw,这三个方法完成了顶级View的三个绘制流程。在performMeasure中又会调用measure,在measure中又会调用onMeasure,在onMeasure中则会对所有的子View进行measure过程,这样就完成了依次measure过程,将绘制过程从父容器传递到了子View中,接着在子view会依次执行父容器的measure过程;performLayout过程和上面是一样的;performDraw有一点点不一样,只是在draw中通过dispatchDraw传到子view的,不过并没有本质的区别

measure之后可以得到View测量后的宽和高,几乎所有情况下,这个就是最终的宽和高了(有特殊情况)
通过getMeasuredWidth()/getMeasuredHeight()获得;

layout可以确定四个顶点的位置,通过getTop/getRight/getBottom/getLeft可以获取四个顶点的坐标;通过getWidth/getHeight可以获得最终的宽和高;

draw是将View显示在屏幕上的

为什么叫setContentView,而不叫setView;

因为DecorView作为顶级View,他会包含一个LinearLayout,一般由两部分组成:顶部标题栏和底部内容栏(id为content),而通过setContentView添加布局是添加到内容栏中的,所以叫setContentView,要获取内容了的view: content = findViewById(R.android.id.content),要获取它的子元素:content.getChilAt(0);

二、MeasureSpec

  1. MeasureSpec可以理解为一种测量规范,它会影响View的测量,而它的创建又会受父容器的影响,获得View的layoutparams之后,根据父容器的一些规则,转换为MeasureSpec。

2.MeasureSpec是int型,32位,高2位代表SpecMode,低30位表示SpecSize;这里所说的MeasureSpec都是指它的值;

3.SpecMode有三类:

  • UNSPECIFIED:表示父容器不限制子View的大小,通常用在系统中,表示一种测量状态;
  • EXACTLY:表示父容器知道子View的具体大小,对应于LayoutParams的match_parent或者是一个准确的数值;
  • AT_MOST:表示父容器会指定一个大小,子View的大小有它自己决定,只要不超过指定的大小就行了,对应wrap_content;

4.MeasureSpec和LayoutParams的关系:

  • 对于顶级View(DecorView)来说:
    MeasureSpec = 窗口尺寸 + View的LayoutParams

  • 对于普通View来说:
    MeasureSpec = 父容器的规则 + View的LayoutParams

5.对4中的源码分析:

  • 顶级View创建MeasureSpec:
在ViewRootImpl中,先看看 performTraversals():

private void performTraversals(){
    ......

    // Ask host how big it wants to be
    windowSizeMayChange |= measureHierarchy(host, lp, res,
          desiredWindowWidth, desiredWindowHeight);
    ......

}

private boolean measureHierarchy(){
    ....
    if (!goodMeasure) {
        //desiredWindowWidth是屏幕的尺寸
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
            windowSizeMayChange = true;
        }
    }
    ....

}

/**创建MeasureSpec
 * @param  int windowSize    屏幕尺寸
 * @param  int rootDimension LayoutParams中的宽和高
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // 精确模式,大小是窗口的大小
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // 最大模式,大小不能超过specsize
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // layoutparams给的固定值
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}
  • 普通View创建MeasureSpec:
对于普通View,View的measure过程是从ViewGroup中传递的,先看看**ViewGroup**的measureChildWithMargins:
protected void measureChildWithMargins(){
    
    //获取子View的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    //创建子View的MeasureSpec
    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的MeasureSpec
 * @param spec : 父容器的MeasureSpec
 * @param padding : 间距
 * @param childDimension :子View的LayoutParams
 * @return
 */
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的工作原理_第2张图片
普通view measurespec创建规则.png

从这个表格可以很清晰的知道,普通View的MeasureSpec是由父容器的MeasureSpec和view的LayoutParams共同决定的;
①当子View的大小确定的时候,无论父容器是什么SpecMode,子View都显示的是精确模式,大小为设置的大小;
②当子View设置的是matchparent时,子view以父容器的mode为准,大小为父容器的剩余空间大小/不超过父容器的剩余空间;
③当子View设置为wrap_content时,mode以子view为准,前面分析过,wrap_content对应的是AT_MOST,所以子View的mode为AT_MOST,大小不超过父容器的剩余空间;
④后面两种情况都不考虑UNSPECIFIED,因为这种模式一般用于系统内部多次调用measure的情况,不用考虑。

三、View的工作流程

  • measure过程
    1.view的measure过程:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ....
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ....

 }

view的measure方法是final类型,子类不能重写,所以在他的内部调用了onMeasure方法;

/**
 * 设置测量的数值
 * @param widthMeasureSpec  
 * @param heightMeasureSpec 
 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

/**
 * 获取默认的大小
 * @param size : 系统建议的大小
 * @param measureSpec : 获取的MeasureSpec
 * @return
 */
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;
}

/**
 * 获取建议的最小宽度
 * @return
 */
protected int getSuggestedMinimumWidth() {
    //如果没有背景,则为mMinWidth,值为android:minWidth设置的值,没有设置则为0;
    //如果设置了背景,则为android:minWidth和背景最小宽度的最大值
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

在上面代码中调用了mBackground.getMinimumWidth():
//返回的是drawable的原始宽度,前提是它有原始宽度才行,比如ShapeDrawable无原始宽度,BitmapDrawable有原始宽度;
public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

总结:在测量view大小的时候,exactly和at_most这两种情况下返回的都是测量的大小,这里说的是测量的大小而不是最终大小,是因为最终大小是在layout中确定的;其他情况下返回的是系统建议的最小大小,如果没有设置背景,则返回的是android:minwidth的值,没有设置的话就是0,;如果设置了背景,返回的是android:minwidth和backgroud最小宽度的最大值,而当drawable为背景,并且有原始大小的时候才能得到background的最小值;

另外在getDefaultSize()中,我们发现,当为exactly和atmost的时候,返回的大小都是specsize,当设置layout为wrap_content的时候,根据表4.1中我们可以看到,它的大小为parentSize,也就是说为parent的剩余宽度,这时候和设置为match_parent并没有区别。所以在自定义View直接继承View的时候,要重写onMeasure,并且规定当specmode为at_most的时候,给它一个固定宽度值/高度值;

2.ViewGtoup的measure过程:
因为ViewGroup是一个抽象类,它的具体实现了LinearLayout等的布局都不一样,所以不用在ViewGroup中实现onMeasure。

//测量子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) {
    //先获取子View的LayoutParams
    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在上面的代码中已经具体分析过了。

下面来看一下它的一个具体实现类LinearLayout:

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

先判断是水平排列还是垂直排列

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    ....

    for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            nonSkippedChildCount++;
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

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

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    // Restore the original height and record how much space
                    // we've allocated to excess-only children so that we can
                    // match the behavior of EXACTLY measurement.
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            /**
             * If applicable, compute the additional offset to the child's baseline
             * we'll need later when asked {@link #getBaseline}.
             */
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            // if we are trying to use a child index for our baseline, the above
            // book keeping only works if there are no children above it with
            // weight.  fail fast to aid the developer.
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        ....

}

在测量子view高度的时候,用mTotalLength记录每一个view的高度和宽度还有间距后,通过setMeasuredDimension测量LinearLayout的大小,如果它的布局采用的是match_parent,则大小为测量的大小,如果为wrap_content,则大小为子view的高度和,但是仍然不能超过parent的剩余高度。

3.在Activity中获取view的宽和高:
①通过onWindowFocusChanged:(会调用多次)

public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);

    if (hasFocus){
       int width =  mTitleText.getMeasuredWidth();
       int height =  mTitleText.getMeasuredHeight();
       Log.e(TAG,"onWindowFocusChanged:"+width+","+height);
    }
}

②通过view.post:

mTitleText.post(new Runnable() {
    @Override
    public void run() {
        int width =  mTitleText.getMeasuredWidth();
        int height =  mTitleText.getMeasuredHeight();
        Log.e(TAG,"onStart-->post:"+width+","+height);
    }
});

③通过ViewTreeObserver(会调用多次):

ViewTreeObserver tree = mTitleText.getViewTreeObserver();
tree.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int width =  mTitleText.getMeasuredWidth();
        int height =  mTitleText.getMeasuredHeight();
        Log.e(TAG,"onStart-->ViewTreeObserver:"+width+","+height);
    }
});

④通过measure:
这种方式比较复杂,而且要区分不同的情况:

当为match_parent的时候,没法获取,因为这时候要知道父容器的剩余大小,无法获取
//具体数值
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
mTitleText.measure(widthMeasureSpec,heightMeasureSpec);
//wrap_content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
mTitleText.measure(widthMeasureSpec,heightMeasureSpec);
  • layout过程:

layout过程是ViewGroup用来确定子View的位置,当ViewGroup的位置确定了之后,通过for循环遍历子元素,调用子元素的layout(),在layout()中又会调用onLayout()确定子元素的位置

在View中

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;

    //通过setFrame设定四个顶点的位置-->这样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);

        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }

        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList listenersCopy =
                    (ArrayList)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;

    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    }
}

因为onLayout的实现和具体的布局有关,所以在它的子类中实现就行了:

//它的实现和具体的布局有关
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

在ViewGroup中:

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;

    // Where right end of child should go
    final int width = right - left;
    int childRight = width - mPaddingRight;

    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();

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

            childTop += lp.topMargin;

            //确定子view的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}
private void setChildFrame(View child, int left, int top, int width, int height) {
        //调用子view的layout
        child.layout(left, top, left + width, top + height);
    }

测量宽高和最终宽高有什么区别:
实质上一般情况下,他们的值是相等的,只是在measure过程中获得是测量宽高,在layout过程中获得是最终的宽和高,时间上会先获得测量的宽高,而在layout过程中,可能会出现这样的情况,导致他们的值不相等:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right+100, bottom+100);
}

这时候,最终宽高会始终比测量宽高大100

  • draw过程

是将view绘制在屏幕上,步骤:
①画背景:mBackGround.draw()-->canvas
②画自己:onDraw
③画子元素:dispatchDraw()
④画装饰:onDrawScrollBar()

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

view还有一个特殊的方法:

public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

它的意思是,当不需要绘制任何内容,设置为true之后系统会进行相应的优化,这个方法在view中是默认关闭的,在viewgroup中是默认开启的;它在实际开发中的意义是:当自定义view继承自ViewGroup并且本身不具备绘制功能是,开启它,系统将会进行相应的优化;当ViewGroup调用onDraw绘制内容时,要**显示的关闭WILL_NOT_DRAW **

四、自定义view

1.分类:

  • 继承View,重新onDraw:
    可以实现不规则的效果,但是得重写OnDraw,需要自己支持padding和wrap_content,有必要时要自定义属性

  • 继承ViewGroup,重写onLayout

  • 继承特定的View(如TextView)

  • 继承特定的ViewGroup(如LinearLayout)

你可能感兴趣的:(学习笔记| (四)View的工作原理)