android应用层view绘制流程之measure,layout,draw三步曲_第1页
android应用层view绘制流程之measure,layout,draw三步曲_第2页
android应用层view绘制流程之measure,layout,draw三步曲_第3页
android应用层view绘制流程之measure,layout,draw三步曲_第4页
android应用层view绘制流程之measure,layout,draw三步曲_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Android 应用层 View 绘制流程之 measure,layout,draw 三步曲 View 的 measure 过程 三个流程均是从 ViewRootImpl 的 performTraversals 方法开始的,如下所示: private void performTraversals() 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); 首先看下 getRootMeasureSpec 方法,如下所示: /* * Figures out the measure spec for the root view in a window based on its * layout params. * * param windowSize * The available width or height of the window * * param rootDimension * The layout params for one dimension (width or height) of the * window. * * return The measure spec to use to measure the root view. */ private static int getRootMeasureSpec(int windowSize, int rootDimension) int measureSpec; switch (rootDimension) case ViewGroup.LayoutParams.MATCH_PARENT: / Window cant resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.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; return measureSpec; 从上面的注释可以看出这个 getRootMeasureSpec 是为了根据根视图的 LayoutParams 计算根 视图的 MeasureSpec,这个根视图就是上篇博客讲的 DecorView。 关于 MeasureSpec 关于 MeasureSpec 来做一个简单的说明:通过 MeasureSpec.makeMeasureSpec 来得到一个 32 位的整数,高两位代码测量模式 mode,低 30 位代表测量大小 size,如下所示: public static int makeMeasureSpec(IntRange(from = 0, to = (1 * 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. * * * * param widthMeasureSpec Horizontal space requirements as imposed by the * parent * param heightMeasureSpec Vertical space requirements as imposed by the * parent * * see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) . 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 subclasss responsibility to make * sure the measured height and width are at least the views minimum height * and width (link #getSuggestedMinimumHeight() and * link #getSuggestedMinimumWidth(). * * * param widthMeasureSpec horizontal space requirements as imposed by the parent. * The requirements are encoded with * link android.view.View.MeasureSpec. * param heightMeasureSpec 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() * see android.view.View.MeasureSpec#getMode(int) * see android.view.View.MeasureSpec#getSize(int) */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 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. * * param size Default size for this view * param measureSpec Constraints imposed by the parent * return The size this view should be. */ 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; 可以看出,如果 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(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); private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; 可以看出这个方法就是给 mMeasuredHeight 和 mMeasuredWidth 进行赋值。进行了赋值之 后调用 View 的 getMeasuredWidth 和 getMeasuredHeight 方法才能得到其正确的测量宽高! ViewGroup 的 measure 过程 上面提到 View 的 measure 方法传入的 widthMeasureSpec 和 heightMeasureSpec 是由父 View 传入的约束信息,那么这些信息是何时传入的呢?由于 View 是嵌套的,因此 measure 过程 也是递归传递的,子 View 的 measure 是由父类调用的,然后子 View 根据传入的父类约束 来设置自身的测量规格。 继承自 ViewGroup 的视图均需要实现 onMeasure 方法,在这个方法里面对其子 View 进行 测量,同时也对自身进行测量,比如 LinearLayout 的 onMeasure 方法如下: Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 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. * * param child The child to measure * param parentWidthMeasureSpec The width requirements for this view * param widthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * param parentHeightMeasureSpec The height requirements for this view * param heightUsed Extra space that has been used up by the parent * vertically (possibly by other children of the parent) */ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); /子视图的测量规格是由父视图的测量测量规格以及子视图的 LayoutParams 来共同 决定的 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); /调用子视图的 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 cant 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 cant 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 的参数来对子视图进行测量。 有一个需要特别注意的地方: 当 childDimension = LayoutParams.WRAP_CONTENT 的时候,其 specSize 和 specMode 分 别为父视图的 size 和 MeasureSpec.AT_MOST。 再回到上面的 View 测量过程当中的 getDefaultSize 方法,如下所示。我们发现当 View 的 specMode 为 AT_MOST 的时候,其 size 默认就是 parent 视图的 size! 因此,在我们自定义 View 的时候,需要考虑当 specMode 为 AT_MOST 的时候(也就是在 xml 布局当中设置为 WRAP_CONTENT 的时候)给当前 View 的宽高设置一个具体的值, 大家可以去看看比如 TextView 的源代码,均对 WRAP_CONTENT 的情况进行了特殊的处 理! public static int getDefaultSize(int size, int measureSpec) int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; return result; 以上就是 View 和 ViewGroup 的 measure 过程,在 ViewGroup 的实现视图当中递归调用子视 图的的 measure 方法来实现整个 View 树的测量。在自定义 View 的时候,当我们需要对 View 的尺寸进行更改的时候,需要实现 onMeasure 方法,在里面根据父视图给的 specSize 和 specMode 来设置当前 View 的 specMode 和 specSize,需要注意的是当父视图给的 specMode=AT_MOST 的时候,需要给当前 View 的宽高设置一个具体的值。 View 的 layout 过程 讲完了 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 ldren 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. * * param l Left position, relative to parent * param t Top position, relative to parent * param r Right position, relative to parent * param b Bottom position, relative to parent */ SuppressWarnings(“unchecked“) public void layout(int l, int t, int r, int b) if (mPrivateFlags3 mPrivateFlags3 int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; /setFrame 方法把参数分别赋值给 mLeft、mTop 、 mRight 和 mBottom 这几个变量 /判断布局是否发生改变 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed | (mPrivateFlags 在 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. * param changed This is a new size or position for this view * param left Left position, relative to parent * param top Top position, relative to parent * param right Right position, relative to parent * param bottom Bottom position, relative to parent */ protected void onLayout(boolean changed, int left, int top, int right, int bottom) 发现 onLayout 是一个空方法,通过注释可以看出:具有子视图的子类需要重写这个 onLayout 方法并且调用其每一个子视图的 layout 方法。 这就完全明白了:也就是说直接或者间接继承自 ViewGroup 的视图需要重写 onLayout 方 法,然后调用其每个子视图的 layout 方法来设置子视图的位置!我们可以查看 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); 来看下 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) * param left * param top * param right * param bottom */ 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; /child 可以使用的空间 / Space available for child int childSpace = width - paddingLeft - mPaddingRight; /得到 child 的个数 final int count = getVirtualChildCount(); final int majorGravity = mGravity final int minorGravity = mGravity /根据 majorGravity 计算 childTop 的位置 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; / 开始进行遍历 child 视图 for (int i = 0; i = 0 if (transientChild.mViewFlags transientIndex+; if (transientIndex = transientCount) transientIndex = -1; final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if (child.mViewFlags . 从上述方法看出主要是遍历 child,然后调用 child 的 drawChi

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论