上一篇博文对 DecorView 和 ViewRootImpl 的关系进行了剖析,这篇文章主要是来剖析 View 绘制的三个基本流程: measure,layout,draw,只有把这三个基本流程搞清楚了,平时在自定义 View 的时候才会有清晰的思路!开始进入正题。
三个流程均是从 ViewRootImpl 的 performTraversals 方法开始的,如下所示:
- private void performTraversals() {
- ......intchildWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
- ......
- mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- ......
- mView.layout(0,0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- ......
- mView.draw(canvas);
- ......
- }
首先看下 getRootMeasureSpec 方法, 如下所示:
- /**
- * Figures out the measure spec for the root view in a window based on it's
- * layout params.
- *
- * @paramwindowSize
- * The available width or height of the window
- *
- * @paramrootDimension
- * The layout params for one dimension (width or height) of the
- * window.
- *
- * @returnThe measure spec to use to measure the root view.
- */
- private static int getRootMeasureSpec(intwindowSize,introotDimension) {intmeasureSpec;switch(rootDimension) {caseViewGroup.LayoutParams.MATCH_PARENT:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;caseViewGroup.LayoutParams.WRAP_CONTENT:// Window can resize. Set max size for root view.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// Window wants to be an exact size. Force root view to be that size.measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;
- }returnmeasureSpec;
- }
从上面的注释可以看出这个 getRootMeasureSpec 是为了根据根视图的 LayoutParams 计算根视图的 MeasureSpec,这个根视图就是上篇博客讲的 DecorView。
关于 MeasureSpec 来做一个简单的说明:通过 MeasureSpec.makeMeasureSpec 来得到一个 32 位的整数,高两位代码测量模式 mode, 低 30 位代表测量大小 size,如下所示:
- public static int makeMeasureSpec(@IntRange(from =0, to = (1<< MeasureSpec.MODE_SHIFT) -1)intsize,@MeasureSpecMode intmode) {if(sUseBrokenMakeMeasureSpec) {returnsize + mode;
- }else{return(size & ~MODE_MASK) | (mode & MODE_MASK);
- }
- }
然后再通过 getMode 和 getSize 这两个方法来得到对应的测试模式 mode 和测量尺寸 size,如下所示:
- /**
- * Extracts the mode from the supplied measure specification.
- *
- * @parammeasureSpec the measure specification to extract the mode from
- * @return{@link android.view.View.MeasureSpec#UNSPECIFIED},
- * {@link android.view.View.MeasureSpec#AT_MOST} or
- * {@link android.view.View.MeasureSpec#EXACTLY}
- */
- @MeasureSpecMode
- public static int getMode(intmeasureSpec) {//noinspection ResourceType
- return(measureSpec & MODE_MASK);
- }/**
- * Extracts the size from the supplied measure specification.
- *
- * @parammeasureSpec the measure specification to extract the size from
- * @returnthe size in pixels defined in the supplied measure specification
- */
- public static int getSize(intmeasureSpec) {return(measureSpec & ~MODE_MASK);
- }
通过 getRootMeasureSpec 来得到 DecorView 的 widthMeasureSpec 和 heightMeasureSpec 之后,就需要来设置 DecorView 的大小了,也就是调用:
- mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
发现这个 measure 是 View 的方法,如下所示:
- /**
- *
- * This is called to find out how big a view should be. The parent
- * supplies constraint information in the width and height parameters.
- *
- *
- *
- * The actual measurement work of a view is performed in
- * {@link #onMeasure(int, int)}, called by this method. Therefore, only
- * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
- *
- *
- *
- * @paramwidthMeasureSpec Horizontal space requirements as imposed by the
- * parent
- * @paramheightMeasureSpec Vertical space requirements as imposed by the
- * parent
- *
- * @see#onMeasure(int, int)
- */
- public final void measure(intwidthMeasureSpec,intheightMeasureSpec) {
- ...........
- onMeasure(widthMeasureSpec, heightMeasureSpec);
- ...........
- }
通过注释可以看出,这个方法是用来计算当前 View 应该为多大,也就是实际的宽高。widthMeasureSpec 和 heightMeasureSpec 是由父 View 传入的约束信息,代表了父 View 给当前 View 的测量规格,当前 View 的宽高是由父 View 和自身一起决定的。measure 方法是 final 的,不可重载,实际的测量过程是在 onMeasure 方法里面完成了,因此子类必须且只能重载 onMeasure 方法来实现自身的测量逻辑。
接下来看 onMeasure 方法:
- /**
- *
- * Measure the view and its content to determine the measured width and the
- * measured height. This method is invoked by {@link #measure(int, int)} and
- * should be overridden by subclasses to provide accurate and efficient
- * measurement of their contents.
- *
- *
- *
- * CONTRACT: When overriding this method, you
- * must call {@link #setMeasuredDimension(int, int)} to store the
- * measured width and height of this view. Failure to do so will trigger an
- *
- IllegalStateException
- , thrown by
- * {@link #measure(int, int)}. Calling the superclass'
- * {@link #onMeasure(int, int)} is a valid use.
- *
- *
- *
- * The base class implementation of measure defaults to the background size,
- * unless a larger size is allowed by the MeasureSpec. Subclasses should
- * override {@link #onMeasure(int, int)} to provide better measurements of
- * their content.
- *
- *
- *
- * If this method is overridden, it is the subclass's responsibility to make
- * sure the measured height and width are at least the view's minimum height
- * and width ({@link #getSuggestedMinimumHeight()} and
- * {@link #getSuggestedMinimumWidth()}).
- *
- *
- * @paramwidthMeasureSpec horizontal space requirements as imposed by the parent.
- * The requirements are encoded with
- * {@link android.view.View.MeasureSpec}.
- * @paramheightMeasureSpec vertical space requirements as imposed by the parent.
- * The requirements are encoded with
- * {@link android.view.View.MeasureSpec}.
- *
- * @see#getMeasuredWidth()
- * @see#getMeasuredHeight()
- * @see#setMeasuredDimension(int, int)
- * @see#getSuggestedMinimumHeight()
- * @see#getSuggestedMinimumWidth()
- * @seeandroid.view.View.MeasureSpec#getMode(int)
- * @seeandroid.view.View.MeasureSpec#getSize(int)
- */
- protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
注释已经写的非常明白了,子类必须复写 onMeasure 方法,且最终通过调用 setMeasuredDimension 方法来存储当前 View 测量得到的宽和高。这个宽和高是通过 getDefaultSize 方法得来的,如下所示:
- /**
- * Utility to return a default size. Uses the supplied size if the
- * MeasureSpec imposed no constraints. Will get larger if allowed
- * by the MeasureSpec.
- *
- * @paramsize Default size for this view
- * @parammeasureSpec Constraints imposed by the parent
- * @returnThe size this view should be.
- */
- public static int getDefaultSize(intsize,intmeasureSpec) {intresult = size;intspecMode = MeasureSpec.getMode(measureSpec);intspecSize = MeasureSpec.getSize(measureSpec);switch(specMode) {caseMeasureSpec.UNSPECIFIED:
- result = size;break;caseMeasureSpec.AT_MOST:caseMeasureSpec.EXACTLY:
- result = specSize;break;
- }returnresult;
- }
可以看出,如果 specMode 等于 AT_MOST 或者 EXACTLY 就返回 specSize,也就是父类指定的 specSize,否则返回通过 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 得到的 size,从名字可以看出是建议的最小宽度和高度,代码如下所示:
- protected int getSuggestedMinimumHeight() {return(mBackground ==null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
- }protected int getSuggestedMinimumWidth() {return(mBackground ==null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
- }
可以看出,建议的最小宽度和高度是由 view 的 background 以及其 mMinWidth、mMinHeight 共同决定的。
setMeasuredDimension 方法如下所示:
- protected final void setMeasuredDimension(intmeasuredWidth,intmeasuredHeight) {booleanoptical = isLayoutModeOptical(this);if(optical != isLayoutModeOptical(mParent)) {
- Insets insets = getOpticalInsets();intopticalWidth = insets.left + insets.right;intopticalHeight = insets.top + insets.bottom;
- measuredWidth += optical ? opticalWidth : -opticalWidth;
- measuredHeight += optical ? opticalHeight : -opticalHeight;
- }
- setMeasuredDimensionRaw(measuredWidth, measuredHeight);
- }private void setMeasuredDimensionRaw(intmeasuredWidth,intmeasuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
- mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
- }
可以看出这个方法就是给 mMeasuredHeight 和 mMeasuredWidth 进行赋值。进行了赋值之后调用 View 的 getMeasuredWidth 和 getMeasuredHeight 方法才能得到其正确的测量宽高!
上面提到 View 的 measure 方法传入的 widthMeasureSpec 和 heightMeasureSpec 是由父 View 传入的约束信息,那么这些信息是何时传入的呢?由于 View 是嵌套的,因此 measure 过程也是递归传递的,子 View 的 measure 是由父类调用的,然后子 View 根据传入的父类约束来设置自身的测量规格。
继承自 ViewGroup 的视图均需要实现 onMeasure 方法,在这个方法里面对其子 View 进行测量,同时也对自身进行测量,比如 LinearLayout 的 onMeasure 方法如下:
- @Override
- protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {if(mOrientation == VERTICAL) {
- measureVertical(widthMeasureSpec, heightMeasureSpec);
- }else{
- measureHorizontal(widthMeasureSpec, heightMeasureSpec);
- }
- }
根据布局的方向分别调用 measureHorizontal 和 measureVertical 方法。
在 ViewGroup 中定义了 measureChildren, measureChild, measureChildWithMargins 方法来对子视图进行测量。measureChildren 内部循环调用了 measureChild。 measureChild 和 measureChildWithMargins 的区别在于 measureChildWithMargins 把 child 的 margin 也考虑在内。下面来对 measureChildWithMargins 方法来分析:
- /**
- * Ask one of the children of this view to measure itself, taking into
- * account both the MeasureSpec requirements for this view and its padding
- * and margins. The child must have MarginLayoutParams The heavy lifting is
- * done in getChildMeasureSpec.
- *
- * @paramchild The child to measure
- * @paramparentWidthMeasureSpec The width requirements for this view
- * @paramwidthUsed Extra space that has been used up by the parent
- * horizontally (possibly by other children of the parent)
- * @paramparentHeightMeasureSpec The height requirements for this view
- * @paramheightUsed Extra space that has been used up by the parent
- * vertically (possibly by other children of the parent)
- */
- protected void measureChildWithMargins(View child,intparentWidthMeasureSpec,intwidthUsed,intparentHeightMeasureSpec,intheightUsed) {finalMarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//子视图的测量规格是由父视图的测量测量规格以及子视图的LayoutParams来共同决定的
- final intchildWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
- + widthUsed, lp.width);final intchildHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
- + heightUsed, lp.height);//调用子视图的measure方法来设置子视图的测量规格child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
从以上代码可以看出:子视图的测量规格是由父视图的测量测量规格以及子视图的 LayoutParams 来共同决定的,因此关键函数是 getChildMeasureSpec 函数,如下所示:
- /**
- * Does the hard part of measureChildren: figuring out the MeasureSpec to
- * pass to a particular child. This method figures out the right MeasureSpec
- * for one dimension (height or width) of one child view.
- *
- * The goal is to combine information from our MeasureSpec with the
- * LayoutParams of the child to get the best possible results. For example,
- * if the this view knows its size (because its MeasureSpec has a mode of
- * EXACTLY), and the child has indicated in its LayoutParams that it wants
- * to be the same size as the parent, the parent should ask the child to
- * layout given an exact size.
- *
- * @param spec The requirements for this view
- * @param padding The padding of this view for the current dimension and
- * margins, if applicable
- * @param childDimension How big the child wants to be in the current
- * dimension
- * @return a MeasureSpec integer for the child
- */
- public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
- int specMode = MeasureSpec.getMode(spec); //得到父视图的mode
- int specSize = MeasureSpec.getSize(spec); //得到父视图的size
- //得到Parent视图剩余的大小
- int size = Math.max(0, specSize - padding);
- int resultSize = 0;
- int resultMode = 0;
- //根据Parent视图的specMode来进行分支判断
- switch (specMode) {
- // Parent has imposed an exact size on us
- case MeasureSpec.EXACTLY:
- //父类是精确模式
- if (childDimension >= 0) {
- //子视图是精确模式,直接设置了精确的大小(在xml当中设置了layout_width="xxx"或者在代码中设置了具体的数值),子视图的size就是精确值,子视图的mode就是EXACTLY
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
- //如果子视图的layout_width或者layout_height为MATCH_PARENT,也就是为父视图的大小,那么子视图的size就是Parent视图剩余的大小,且mode与父类相同,也为EXACTLY
- // Child wants to be our size. So be it.
- resultSize = size;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
- //如果子视图的layout_width或者layout_height为WRAP_CONTENT,也就是不超过父视图的大小,那么子视图的size为size,且mode为AT_MOST。
- // 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) {
- //子视图是精确模式,直接设置了精确的大小(在xml当中设置了layout_width="xxx"或者在代码中设置了具体的数值),子视图的size就是精确值,子视图的mode就是EXACTLY
- // Child wants a specific size... so be it
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
- //如果子视图的layout_width或者layout_height为MATCH_PARENT,也就是为父视图的大小,那么子视图的size就是Parent视图剩余的大小,且mode与父类相同,也是AT_MOST。
- // 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) {
- //如果子视图的layout_width或者layout_height为WRAP_CONTENT,也就是不超过父视图的大小,那么子视图的size为size,且mode为AT_MOST。
- // 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;
- }
- // 将resultSize和resultMode进行组装为32为整数返回
- //noinspection ResourceType
- return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
- }
可以看到,getChildMeasureSpec 就是根据父视图的 specSize 和 specMode 以及 child 视图的 LayoutParams 来确定子视图的 resultSize 和 resultMode,然后把 resultSize 和 resultMode 进行组装成 32 位的整数,作为 child.measure 的参数来对子视图进行测量。
有一个需要特别注意的地方:
- public static int getDefaultSize(intsize,intmeasureSpec) {intresult = size;intspecMode = MeasureSpec.getMode(measureSpec);intspecSize = MeasureSpec.getSize(measureSpec);
- ......caseMeasureSpec.AT_MOST:caseMeasureSpec.EXACTLY:
- result = specSize;break;
- }returnresult;
- }
以上就是 View 和 ViewGroup 的 measure 过程, 在 ViewGroup 的实现视图当中递归调用子视图的的 measure 方法来实现整个 View 树的测量。在自定义 View 的时候,当我们需要对 View 的尺寸进行更改的时候,需要实现 onMeasure 方法,在里面根据父视图给的 specSize 和 specMode 来设置当前 View 的 specMode 和 specSize, 需要注意的是当父视图给的 specMode==AT_MOST 的时候,需要给当前 View 的宽高设置一个具体的值。
讲完了 View 的 measure 过程,接下来就是 layout 过程。那么这个 layout 过程是干什么的呢?在 measure 过程当中设置了 view 的宽高,那么设置了宽高之后,具体 view 是显示在屏幕的哪个位置呢?这个就是 layout 过程干的事。
layout 跟 measure 一样,也是递归结构,来看下 View 的 layout 方法:
- /**
- * Assign a size and position to a view and all of its
- * descendants
- *
- *
- This is the second phase of the layout mechanism.
- * (The first is measuring). In this phase, each parent calls
- * layout on all of its children to position them.
- * This is typically done using the child measurements
- * that were stored in the measure pass().
- *
- *
- Derived classes should not override this method.
- * Derived classes with children should override
- * onLayout. In that method, they should
- * call layout on each of their children.
- *
- * @paraml Left position, relative to parent
- * @paramt Top position, relative to parent
- * @paramr Right position, relative to parent
- * @paramb Bottom position, relative to parent
- */
- @SuppressWarnings({"unchecked"})public void layout(intl,intt,intr,intb) {if((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) !=0) {
- onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
- mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
- }intoldL = mLeft;intoldT = mTop;intoldB = mBottom;intoldR = mRight;//setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
- //判断布局是否发生改变
- booleanchanged = 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 来设置自身的位置,然后调用了 onLayout 方法,是不是跟 measure 方法里面调用 onMeasure 方法类似!来看下 onLayout 方法:
- /**
- * Called from layout when this view should
- * assign a size and position to each of its children.
- *
- * Derived classes with children should override
- * this method and call layout on each of
- * their children.
- * @paramchanged This is a new size or position for this view
- * @paramleft Left position, relative to parent
- * @paramtop Top position, relative to parent
- * @paramright Right position, relative to parent
- * @parambottom Bottom position, relative to parent
- */
- protected void onLayout(booleanchanged,intleft,inttop,intright,intbottom) {
- }
发现 onLayout 是一个空方法,通过注释可以看出:具有子视图的子类需要重写这个 onLayout 方法并且调用其每一个子视图的 layout 方法。 这就完全明白了:也就是说直接或者间接继承自 ViewGroup 的视图需要重写 onLayout 方法,然后调用其每个子视图的 layout 方法来设置子视图的位置!我们可以查看 LinearLayout,其肯定是实现了 onLayout 方法,在这个方法里面来一一设置子视图的位置!LinearLayout 的 onLayout 方法如下所示:
- @Override
- protected void onLayout(booleanchanged,intl,intt,intr,intb) {if(mOrientation == VERTICAL) {
- layoutVertical(l, t, r, b);
- }else{
- layoutHorizontal(l, t, r, b);
- }
- }
来看下 layoutVertical 方法:
- /**
- * Position the children during a layout pass if the orientation of this
- * LinearLayout is set to {@link #VERTICAL}.
- *
- * @see#getOrientation()
- * @see#setOrientation(int)
- * @see#onLayout(boolean, int, int, int, int)
- * @paramleft
- * @paramtop
- * @paramright
- * @parambottom
- */
- voidlayoutVertical(intleft,inttop,intright,intbottom) {final intpaddingLeft = mPaddingLeft;intchildTop;intchildLeft;// Where right end of child should go
- final intwidth = right - left;intchildRight = width - mPaddingRight;//child可以使用的空间
- // Space available for child
- intchildSpace = width - paddingLeft - mPaddingRight;//得到 child的个数
- final intcount = getVirtualChildCount();final intmajorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;final intminorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;//根据majorGravity计算childTop的位置
- switch(majorGravity) {caseGravity.BOTTOM:// mTotalLength contains the padding alreadychildTop = mPaddingTop + bottom - top - mTotalLength;break;// mTotalLength contains the padding already
- caseGravity.CENTER_VERTICAL:
- childTop = mPaddingTop + (bottom - top - mTotalLength) /2;break;caseGravity.TOP:default:
- childTop = mPaddingTop;break;
- }// 开始进行遍历child视图
- for(inti =0; i < count; i++) {finalView child = getVirtualChildAt(i);if(child ==null) {
- childTop += measureNullChild(i);
- }else if(child.getVisibility() != GONE) {//child不为GONE,因为GONE是不占空间的
- final intchildWidth = child.getMeasuredWidth();// 得到onMeasure之后的测量宽度
- final intchildHeight = child.getMeasuredHeight();// 得到onMeasure之后的测量高度
- finalLinearLayout.LayoutParams lp =
- (LinearLayout.LayoutParams) child.getLayoutParams();intgravity = lp.gravity;if(gravity <0) {
- gravity = minorGravity;
- }final intlayoutDirection = getLayoutDirection();final intabsoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);// 根据absoluteGravity计算childLeft的值
- switch(absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {caseGravity.CENTER_HORIZONTAL:
- childLeft = paddingLeft + ((childSpace - childWidth) /2)
- + lp.leftMargin - lp.rightMargin;break;caseGravity.RIGHT:
- childLeft = childRight - childWidth - lp.rightMargin;break;caseGravity.LEFT:default:
- childLeft = paddingLeft + lp.leftMargin;break;
- }if(hasDividerBeforeChildAt(i)) {
- childTop += mDividerHeight;
- }
- childTop += lp.topMargin;//通过setChildFrame函数来设置child的位置, setChildFrame函数如下所示setChildFrame(child, childLeft, childTop + getLocationOffset(child),
- childWidth, childHeight);
- childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
- i += getChildrenSkipCount(child, i);
- }
- }
- }private void setChildFrame(View child,intleft,inttop,intwidth,intheight) {
- child.layout(left, top, left + width, top + height);
- }
上面这个方法还是比较易懂的,主要就是调用 child 的 layout 方法来设置 child 的位置,当我们给一个 View 设置好位置之后,其内部的四个变量 mLeft、mTop、mRight 和 mBottom 也就确定了,不过要注意这些值都是相对父视图而言的,而不是相对整个屏幕而言的。这个四个变量是通过以下方式获取的。
- public final int getWidth() {returnmRight - mLeft;
- }public final int getHeight() {returnmBottom - mTop;
- }public final int getLeft() {returnmLeft;
- }public final int getRight() {returnmRight;
- }public final int getTop() {returnmTop;
- }public final int getBottom() {returnmBottom;
- }
在 View 当中还有下面两个函数,这也解释了为什么有时候 getWidth() 和 getMeasuredWidth() 以及 getHeight() 和 getMeasuredHeight() 会得到不同的值的原因。
- public final int getMeasuredWidth() {returnmMeasuredWidth & MEASURED_SIZE_MASK;
- }public final int getMeasuredHeight() {returnmMeasuredHeight & MEASURED_SIZE_MASK;
- }
以上就是 View 的 layout 过程,layout 相对 measure 过程来说还是算比较简单的。
* 总结起来就是:直接或者间接继承自 ViewGroup 的视图需要重写 onLayout 方法,然后调用其每个子视图的 layout 方法来设置子视图的位置。*
讲完了 View 的 layout 流程,接下来就是 draw 流程,draw 负责对 view 进行绘制。在 ViewRootImpl 的 drawSoftware 方法当中:
- /**
- * @returntrue if drawing was successful, false if an error occurred
- */
- private boolean drawSoftware(Surface surface, AttachInfo attachInfo,intxoff,intyoff,booleanscalingRequired, Rect dirty) {// Draw with software renderer.
- finalCanvas canvas;try{final intleft = dirty.left;final inttop = dirty.top;final intright = dirty.right;final intbottom = dirty.bottom;
- canvas = mSurface.lockCanvas(dirty);
- ................
- mView.draw(canvas);
- .........return true;
- }
在上述方法当中调用了 mView 的 draw 方法,来看 View 的 draw 方法:
- /**
- * Manually render this view (and all of its children) to the given Canvas.
- * The view must have already done a full layout before this function is
- * called. When implementing a view, implement
- * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
- * If you do need to override this method, call the superclass version.
- *
- * @paramcanvas The Canvas to which the View is rendered.
- */
- @CallSuper
- public void draw(Canvas canvas) {
- .............../*
- * 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
- intsaveCount;if(!dirtyOpaque) {
- drawBackground(canvas);
- }
- ........// skip step 2 & 5 if possible (common case).......// Step 2, save the canvas' layers
- if(drawTop) {
- canvas.saveLayer(left, top, right, top + length,null, flags);
- }
- ........// Step 3, draw the content
- if(!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);// Step 5, draw the fade effect and restore layers
- finalPaint p = scrollabilityCache.paint;finalMatrix matrix = scrollabilityCache.matrix;finalShader 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);
- }
- ...............// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);
- }
通过注释可以看出整个绘制过程分为 6 部分,在大多数情况下第 2 步和第 5 步可以跳过,在自定义 View 的时候需要实现 onDraw 方法而不是实现 draw 方法。 接下来对剩下的四步进行分析:
- private void drawBackground(Canvas canvas) {finalDrawable background = mBackground;if(background ==null) {return;
- }
- setBackgroundBounds();
- ...............final intscrollX = mScrollX;final intscrollY = mScrollY;if((scrollX | scrollY) ==0) {
- background.draw(canvas);
- }else{
- canvas.translate(scrollX, scrollY);
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
如上所示,调用了 background 的 draw 方法,也就是 Drawable 的 draw 方法。
- /**
- * Implement this to do your drawing.
- *
- * @paramcanvas the canvas on which the background will be drawn
- */
- protected void onDraw(Canvas canvas) {
- }
我们发现 onDraw 是一个空的方法,需要子类去实现,一般我们在自定义 View 的时候都会重写 onDraw 方法来进行绘制。
- /**
- * Called by draw to draw the child views. This may be overridden
- * by derived classes to gain control just before its children are drawn
- * (but after its own view has been drawn).
- * @paramcanvas the canvas on which to draw the view
- */
- protected void dispatchDraw(Canvas canvas) {
- }
发现 dispatchDraw 为空,根据注释:如果 View 包含子类就需要重写这个方法,那么说明下 ViewGroup 应该重写了这个方法,看下 ViewGroup 的 dispatchDraw 方法,如下所示:
- @Override
- protected void dispatchDraw(Canvas canvas) {
- .............for(inti =0; i < childrenCount; i++) {while(transientIndex >=0&& mTransientIndices.get(transientIndex) == i) {finalView transientChild = mTransientViews.get(transientIndex);if((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
- transientChild.getAnimation() !=
来源: http://blog.csdn.net/liuyi1207164339/article/details/70545887