- 浏览: 279666 次
- 性别:
- 来自: 济南
文章分类
最新评论
本文原创, 转载请注明出处:http://blog.csdn.net/qinjuning
上篇文章<<Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)>>中,我们
了解了View树的转换过程以及如何设置View的LayoutParams的。本文继续沿着既定轨迹继续未完成的job。
主要知识点如下:
1、MeasureSpc类说明
2、measure过程详解(揭秘其细节);
3、root View被添加至窗口时,UI框架是如何设置其LayoutParams值得。
在讲解measure过程前,我们非常有必要理解MeasureSpc类的使用,否则理解起来也只能算是囫囵吞枣。
1、MeasureSpc类说明
1.1 SDK 说明如下
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec
represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and
a mode.
即:
MeasureSpc类封装了父View传递给子View的布局(layout)要求。每个MeasureSpc实例代表宽度或者高度
(只能是其一)要求。它有三种模式:
①、UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
②、EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
③、AT_MOST(至多),子元素至多达到指定大小的值。
常用的三个函数:
static int getMode(int measureSpec) : 根据提供的测量值(格式)提取模式(上述三个模式之一)
static int getSize(int measureSpec) : 根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
static int makeMeasureSpec(int size,int mode) : 根据提供的大小值和模式创建一个测量值(格式)
以上摘取自: <<MeasureSpec介绍及使用详解>>
1.2 MeasureSpc类源码分析其为View.java类的内部类,路径:\frameworks\base\core\java\android\view\View.java
public class View implements ... { ... public static class MeasureSpec { private static final int MODE_SHIFT = 30; //移位位数为30 //int类型占32位,向右移位30位,该属性表示掩码值,用来与size和mode进行"&"运算,获取对应值。 private static final int MODE_MASK = 0x3 << MODE_SHIFT; //向右移位30位,其值为00 + (30位0) , 即 0x0000(16进制表示) public static final int UNSPECIFIED = 0 << MODE_SHIFT; //向右移位30位,其值为01 + (30位0) , 即0x1000(16进制表示) public static final int EXACTLY = 1 << MODE_SHIFT; //向右移位30位,其值为02 + (30位0) , 即0x2000(16进制表示) public static final int AT_MOST = 2 << MODE_SHIFT; //创建一个整形值,其高两位代表mode类型,其余30位代表长或宽的实际值。可以是WRAP_CONTENT、MATCH_PARENT或具体大小exactly size public static int makeMeasureSpec(int size, int mode) { return size + mode; } //获取模式 ,与运算 public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } //获取长或宽的实际值 ,与运算 public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } } ... }
MeasureSpec类的处理思路是:
①、右移运算,使int 类型的高两位表示模式的实际值,其余30位表示其余30位代表长或宽的实际值----可以是
WRAP_CONTENT、MATCH_PARENT或具体大小exactly size。
②、通过掩码MODE_MASK进行与运算 “&”,取得模式(mode)以及长或宽(value)的实际值。
2、measure过程详解
2.1 measure过程深入分析
之前的一篇博文<<Android中View绘制流程以及invalidate()等相关方法分析>>,我们从”二B程序员”的角度简单 解了measure过程的调用过程。过了这么多,我们也该升级了,- - 。现在请开始从”普通程序员”角度去理解这个
过程。我们重点查看measure过程中地相关方法。
我们说过,当UI框架开始绘制时,皆是从ViewRoot.java类开始绘制的。
ViewRoot类简要说明: 任何显示在设备中的窗口,例如:Activity、Dialog等,都包含一个ViewRoot实例,该
类主要用来与远端 WindowManagerService交互以及控制(开始/销毁)绘制。
Step 1、 开始UI绘制 , 具体绘制方法则是:
路径:\frameworks\base\core\java\android\view\ViewRoot.java public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks { ... //mView对象指添加至窗口的root View ,对Activity窗口而言,则是DecorView对象。 View mView; //开始View绘制流程 private void performTraversals(){ ... //这两个值我们在后面讨论时,在回过头来看看是怎么赋值的。现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。 int childWidthMeasureSpec; //其值由MeasureSpec类构建 , makeMeasureSpec int childHeightMeasureSpec;//其值由MeasureSpec类构建 , makeMeasureSpec // Ask host how big it wants to be host.measure(childWidthMeasureSpec, childHeightMeasureSpec); ... } ... }
Step 2 、调用measure()方法去做一些前期准备
measure()方法原型定义在View.java类中,final修饰符修饰,其不能被重载:
public class View implements ... { ... /** * This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. * * @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) { //判断是否为强制布局,即带有“FORCE_LAYOUT”标记 以及 widthMeasureSpec或heightMeasureSpec发生了改变 if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag //清除MEASURED_DIMENSION_SET标记 ,该标记会在onMeasure()方法后被设置 mPrivateFlags &= ~MEASURED_DIMENSION_SET; // measure ourselves, this should set the measured dimension flag back // 1、 测量该View本身的大小 ; 2 、 设置MEASURED_DIMENSION_SET标记,否则接写来会报异常。 onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= LAYOUT_REQUIRED; //下一步是layout了,添加LAYOUT_REQUIRED标记 } mOldWidthMeasureSpec = widthMeasureSpec; //保存值 mOldHeightMeasureSpec = heightMeasureSpec; //保存值 } ... }
参数widthMeasureSpec和heightMeasureSpec由父View构建,表示父View给子View的测量要求。其值地构建
会在下面步骤中详解。
measure()方法显示判断是否需要重新调用设置改View大小,即调用onMeasure()方法,然后操作两个标识符:
①、重置MEASURED_DIMENSION_SET : onMeasure()方法中,需要添加该标识符,否则,会报异常;
②、添加LAYOUT_REQUIRED : 表示需要进行layout操作。
最后,保存当前的widthMeasureSpec和heightMeasureSpec值。
Step 3 、调用onMeasure()方法去真正设置View的长宽值,其默认实现为:
/** * 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 overriden by subclasses to provide accurate and efficient * measurement of their contents. * * @param widthMeasureSpec horizontal space requirements as imposed by the parent. * The requirements are encoded with * @param heightMeasureSpec vertical space requirements as imposed by the parent. * The requirements are encoded with */ //设置该View本身地大小 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } /** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no contraints. 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. */ //@param size参数一般表示设置了android:minHeight属性或者该View背景图片的大小值 public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); //根据不同的mode值,取得宽和高的实际值。 switch (specMode) { case MeasureSpec.UNSPECIFIED: //表示该View的大小父视图未定,设置为默认值 result = size; break; case MeasureSpec.AT_MOST: //表示该View的大小由父视图指定了 case MeasureSpec.EXACTLY: result = specSize; break; } return result; } //获得设置了android:minHeight属性或者该View背景图片的大小值, 最为该View的参考值 protected int getSuggestedMinimumWidth() { int suggestedMinWidth = mMinWidth; // android:minHeight if (mBGDrawable != null) { // 背景图片对应地Width。 final int bgMinWidth = mBGDrawable.getMinimumWidth(); if (suggestedMinWidth < bgMinWidth) { suggestedMinWidth = bgMinWidth; } } return suggestedMinWidth; } //设置View在measure过程中宽和高 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= MEASURED_DIMENSION_SET; //设置了MEASURED_DIMENSION_SET标记 }
主要功能就是根据该View属性(android:minWidth和背景图片大小)和父View对该子View的"测量要求",设置该 View的mMeasuredWidth 和mMeasuredHeight 值。
这儿只是一般的View类型地实现方法。一般来说,父View,也就是ViewGroup类型,都需要在重写onMeasure() 方法,遍历所有子View,设置每个子View的大小。基本思想如下:遍历所有子View,设置每个子View的大小。伪
代码表示为:
//某个ViewGroup类型的视图 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //必须调用super.ononMeasure()或者直接调用setMeasuredDimension()方法设置该View大小,否则会报异常。 super.onMeasure(widthMeasureSpec , heightMeasureSpec) //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), // getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); //遍历每个子View for(int i = 0 ; i < getChildCount() ; i++){ View child = getChildAt(i); //调用子View的onMeasure,设置他们的大小。childWidthMeasureSpec , childHeightMeasureSpec ? child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } }
Step 2、Step 3代码也比较好理解,但问题是我们示例代码中widthMeasureSpec、heightMeasureSpec是如何
确定的呢?父View是如何设定其值的?
要想回答这个问题,我们看是去源代码里找找答案吧。在ViewGroup.java类中,为我们提供了三个方法,去设置
每个子View的大小,基本思想也如同我们之前描述的思想:遍历所有子View,设置每个子View的大小。
主要有如下方法:
/** * Ask all of the children of this view to measure themselves, taking into * account both the MeasureSpec requirements for this view and its padding. * We skip children that are in the GONE state The heavy lifting is done in * getChildMeasureSpec. */ //widthMeasureSpec 和 heightMeasureSpec 表示该父View的布局要求 //遍历每个子View,然后调用measureChild()方法去实现每个子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) { // 不处于 “GONE” 状态 measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } /** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding. * The heavy lifting is done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param parentHeightMeasureSpec The height requirements for this view */ //测量每个子View高宽时,清楚了该View本身的边距大小,即android:padding属性 或android:paddingLeft等属性标记 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); // LayoutParams属性 //设置子View的childWidthMeasureSpec属性,去除了该父View的边距值 mPaddingLeft + mPaddingRight final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); //设置子View的childHeightMeasureSpec属性,去除了该父View的边距值 mPaddingTop + mPaddingBottom final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
measureChildren()方法:遍历所有子View,调用measureChild()方法去设置该子View的属性值。
measureChild() 方法 : 获取特定子View的widthMeasureSpec、heightMeasureSpec,调用measure()方法
设置子View的实际宽高值。
getChildMeasureSpec()就是获取子View的widthMeasureSpec、heightMeasureSpec值。
/** * 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. */ // spec参数 表示该父View本身所占的widthMeasureSpec 或 heightMeasureSpec值 // padding参数 表示该父View的边距大小,见于android:padding属性 或android:paddingLeft等属性标记 // childDimension参数 表示该子View内部LayoutParams属性的值,可以是wrap_content、match_parent、一个精确指(an exactly size), // 例如:由android:width指定等。 public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); //获得父View的mode int specSize = MeasureSpec.getSize(spec); //获得父View的实际值 int size = Math.max(0, specSize - padding); //父View为子View设定的大小,减去边距值, int resultSize = 0; //子View对应地 size 实际值 ,由下面的逻辑条件赋值 int resultMode = 0; //子View对应地 mode 值 , 由下面的逻辑条件赋值 switch (specMode) { // Parent has imposed an exact size on us //1、父View是EXACTLY的 ! case MeasureSpec.EXACTLY: //1.1、子View的width或height是个精确值 (an exactly size) if (childDimension >= 0) { resultSize = childDimension; //size为精确值 resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。 } //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; //size为父视图大小 resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。 } //1.3、子View的width或height为 WRAP_CONTENT else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; //size为父视图大小 resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST 。 } break; // Parent has imposed a maximum size on us //2、父View是AT_MOST的 ! case MeasureSpec.AT_MOST: //2.1、子View的width或height是个精确值 (an exactly size) if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; //size为精确值 resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。 } //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT 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; //size为父视图大小 resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST } //2.3、子View的width或height为 WRAP_CONTENT else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; //size为父视图大小 resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST } break; // Parent asked to see how big we want to be //3、父View是UNSPECIFIED的 ! case MeasureSpec.UNSPECIFIED: //3.1、子View的width或height是个精确值 (an exactly size) if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; //size为精确值 resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY } //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; //size为0! ,其值未定 resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED } //3.3、子View的width或height为 WRAP_CONTENT else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; //size为0! ,其值未定 resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED } break; } //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
为了便于分析,我将上面的逻辑判断语句使用列表项进行了说明.
getChildMeasureSpec()方法的主要功能如下:
根据父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View内部
LayoutParams属性值,共同决定子View的measureSpec值的大小。主要判断条件主要为MeasureSpec的mode
类型以及LayoutParams的宽高实际值(lp.width,lp.height),见于以上所贴代码中的列表项: 1、 1.1 ; 1.2 ; 1.3 ;
2、2.1等。
例如,分析列表3:假设当父View为MeasureSpec.UNSPECIFIED类型,即未定义时,只有当子View的width
或height指定时,其mode才为MeasureSpec.EXACTLY,否者该View size为 0 ,mode为MeasureSpec.UNSPECIFIED时
,即处于未指定状态。
由此可以得出, 每个View大小的设定都事由其父View以及该View共同决定的。但这只是一个期望的大小,每个
View在测量时最终大小的设定是由setMeasuredDimension()最终决定的。因此,最终确定一个View的“测量长宽“是
由以下几个方面影响:
1、父View的MeasureSpec属性;
2、子View的LayoutParams属性 ;
3、setMeasuredDimension()或者其它类似设定mMeasuredWidth 和mMeasuredHeight 值的方法。
setMeasuredDimension()原型:
//设置View在measure过程中宽和高 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= MEASURED_DIMENSION_SET; //设置了MEASURED_DIMENSION_SET标记 }
将上面列表项转换为表格为:
这张表格更能帮助我们分析View的MeasureSpec的确定条件关系。
为了帮助大家理解,下面我们分析某个窗口使用地xml布局文件,我们弄清楚该xml布局文件中每个View的
MeasureSpec值的组成。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/llayout" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
该布局文件共有两个View: ①、id为llayout的LinearLayout布局控件 ;
②、id为tv的TextView控件。
假设LinearLayout的父View对应地widthSpec和heightSpec值皆为MeasureSpec.EXACTLY类型(Activity窗口
的父View为DecorView,具体原因见第三部分说明)。
对LinearLayout而言比较简单,由于 android:layout_width="match_parent",因此其width对应地widthSpec
mode值为MeasureSpec.EXACTLY ,size由父视图大小指定 ;由于android:layout_height = "match_parent",
因此其height对应地heightSpec mode值为MeasureSpec.EXACTLY,size由父视图大小指定 ;
对TextView而言 ,其父View为LinearLayout的widthSpec和heightSpec值皆为MeasureSpec.EXACTLY类型,
由于android:layout_width="match_parent" , 因此其width对应地widthSpec mode值为MeasureSpec.EXACTLY,
size由父视图大小指定 ; 由于android:layout_width="wrap_content" , 因此其height对应地widthSpec mode值为
MeasureSpec.AT_MOST,size由父视图大小指定 。
我们继续窥测下LinearLayout类是如何进行measure过程的:
public class LinearLayout extends ViewGroup { ... @Override //onMeasure方法。 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向, if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } //垂直方向布局 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; //该LinearLayout测量子View时的总高度。 float totalWeight = 0; //所有子View的权重和 , android:layout_weight int maxWidth = 0; //保存子View中最大width值 ... final int count = getVirtualChildCount(); //子View的个数 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); ... // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); ... LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); totalWeight += lp.weight; //满足该条件地View会在该LinearLayout有剩余高度时,才真正调用measure() if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { ... } else { int oldHeight = Integer.MIN_VALUE; //如果View的hight值为0,并且设置了android:layout_weight属性,重新纠正其height值为WRAP_CONTENT if (lp.height == 0 && lp.weight > 0) { oldHeight = 0; 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). //对每个子View调用measure()方法 measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); //这三行代码做了如下两件事情: //1、获得该View的measuredHeight值,每个View都会根据他们地属性正确设置值 > 0 ; //2、更新mTotalLength值:取当前高度mTotalLength值与mTotalLength + childHeight 的最大值 // 于是对于android:layout_height="wrap_height"属性地LinearLayout控件也就知道了它的确切高度值了。 final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); ... } final int margin = lp.leftMargin + lp.rightMargin; final int measuredWidth = child.getMeasuredWidth() + margin; maxWidth = Math.max(maxWidth, measuredWidth); ... } //后续还有很多处理,包括继续measure()某些符合条件地子View ... } void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) { //调用measureChildWithMargins()方法去设置子View大小 measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); } ... }
继续看看measureChildWithMargins()方法,该方法定义在ViewGroup.java内,基本流程同于measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理。
measureChildWithMargins@ViewGroup.java
/** * 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. */ //基本流程同于measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理 //widthUsed参数 表示该父View已经使用的宽度 //heightUsed参数 表示该父View已经使用的高度 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //获得子View的childWidthMeasureSpec和childHeightMeasureSpec值 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); }
measure()过程时,LinearLayout类做了如下事情 :
1、遍历每个子View,对其调用measure()方法;
2、子Viewmeasure()完成后,需要取得该子View地宽高实际值,继而做处理(例如:LinearLayout属性为
android:widht="wrap_content"时,LinearLayout的实际width值则是每个子View的width值的累加值)。
2.2 WRAP_CONTENT、MATCH_PARENT以及measure动机揭秘
子View地宽高实际值 ,即child.getMeasuredWidth()值得返回最终会是一个确定值? 难道WRAP_CONTENT(
其值为-2)、MATCH_PARENT(值为-1)或者说一个具体值(an exactly size > 0)。前面我们说过,View最终“测量”值的
确定是有三个部分组成地:
①、父View的MeasureSpec属性;
②、子View的LayoutParams属性 ;
③、setMeasuredDimension()或者其它类似设定mMeasuredWidth 和mMeasuredHeight 值的方法。
因此,一个View必须以某种合适地方法确定它地最终大小。例如,如下自定义View:
//自定义View public Class MyView extends View { //针对不同地mode值,设置本View地大小 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ //获得父View传递给我们地测量需求 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int width = 0 ; int height = 0 ; //对UNSPECIFIED 则抛出异常 if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) throw new RuntimeException("widthMode or heightMode cannot be UNSPECIFIED"); //精确指定 if(widthMode == MeasureSpec.EXACTLY){ width = 100 ; } //模糊指定 else if(widthMode == MeasureSpec.AT_MOST ) width = 50 ; //精确指定 if(heightMode == MeasureSpec.EXACTLY){ height = 100 ; } //模糊指定 else if(heightMode == MeasureSpec.AT_MOST ) height = 50 ; setMeasuredDimension(width , height) ; } }
该自定义View重写了onMeasure()方法,根据传递过来的widthMeasureSpec和heightMeasureSpec简单设置了
该View的mMeasuredWidth 和mMeasuredHeight值。
对于TextView而言,如果它地mode不是Exactly类型 , 它会根据一些属性,例如:android:textStyle
、android:textSizeandroid:typeface等去确定TextView类地需要占用地长和宽。
因此,如果你地自定义View必须手动对不同mode做出处理。否则,则是mode对你而言是无效的。
Android框架中提供地一系列View/ViewGroup都需要去进行这个measure()过程地 ,因为在layout()过程中,父
View需要调用getMeasuredWidth()或getMeasuredHeight()去为每个子View设置他们地布局坐标,只有确定布局
坐标后,才能真正地将该View 绘制(draw)出来,否则该View的layout大小为0,得不到期望效果。我们继续看看
LinearLayout的layout布局过程:
public class LinearLayout extends ViewGroup { ... @Override //layout 过程 protected void onLayout(boolean changed, int l, int t, int r, int b) { //假定是垂直方向布局 if (mOrientation == VERTICAL) { layoutVertical(); } else { layoutHorizontal(); } } //对每个子View调用layout过程 void layoutVertical() { ... final int count = getVirtualChildCount(); ... for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { //一般为非null childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { //获得子View测量时的实际宽高值, final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); ... // 封装了child.layout()方法,见如下 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } } //width = getMeasuredWidth() ; height = childHeight(); View的大小就是测量大小 private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); } ... }
对一个View进行measure操作地主要目的就是为了确定该View地布局大小,见上面所示代码。但measure操作
通常是耗时的,因此对自定义ViewGroup而言,我们可以自由控制measure、layout过程,如果我们知道如何layout
一个View,我们可以跳过该ViewGroup地measure操作(onMeasure()方法中measure所有子View地),直接去layout
在前面一篇博客<<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明>>中,我们自定义了一个 ViewGroup, 并且重写了onMeasure()和onLayout()方法去分别操作每个View。就该ViewGroup而言,我们只需要
重写onLayout()操作即可,因为我们知道如何layout每个子View。如下代码所示:
//自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置 public class MultiViewGroup extends ViewGroup { private void init() { // 初始化3个 LinearLayout控件 LinearLayout oneLL = new LinearLayout(mContext); oneLL.setBackgroundColor(Color.RED); addView(oneLL); ... } @Override // 我们知晓每个子View的layout布局大小,因此我们不需要为每个子View进行measure()操作了。 // protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // setMeasuredDimension(width, height); // // 设置该ViewGroup的大小 // int width = MeasureSpec.getSize(widthMeasureSpec); // int height = MeasureSpec.getSize(heightMeasureSpec); // int childCount = getChildCount(); // for (int i = 0; i < childCount; i++) { // View child = getChildAt(i); // // 设置每个子视图的大小 , 即全屏 // child.measure(MultiScreenActivity.screenWidth, MultiScreenActivity.scrrenHeight); // } } // layout过程 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub Log.i(TAG, "--- start onLayout --"); int startLeft = 0; // 每个子视图的起始布局坐标 int startTop = 10; // 间距设置为10px 相当于 android:marginTop= "10px" int childCount = getChildCount(); Log.i(TAG, "--- onLayout childCount is -->" + childCount); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); child.layout(startLeft, startTop, startLeft + MultiScreenActivity.screenWidth, startTop + MultiScreenActivity.scrrenHeight); startLeft = startLeft + MultiScreenActivity.screenWidth ; //校准每个子View的起始布局位置 //三个子视图的在屏幕中的分布如下 [0 , 320] / [320,640] / [640,960] } } }
更多关于自定义ViewGroup无须重写measure动作的,可以参考 Android API :
中文翻译见于:<<Android中View绘制优化之三---- 优化View>>
3、root View被添加至窗口时,UI框架是如何设置其LayoutParams值
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
//显示一个悬浮窗吧 , just so so public void showView() { //解析布局文件 LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); //rootView对应地LayoutParams属性值为null,将会在UI绘制时设定其值 View rootView = layoutInflater.inflate(R.layout.main, null); WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE); //设置WindowManager.LayoutParams参数值,作为该窗口的各种属性 WindowManager.LayoutParams winparams = WindowManager.LayoutParams(); // 以屏幕左上角为原点,设置x、y初始值 winparams.x = 0; winparams.y = 0; //设置悬浮窗口长宽数据 winparams.width = WindowManager.LayoutParams.WRAP_CONTENT;; winparams.height = WindowManager.LayoutParams.WRAP_CONTENT;; windowManager.addView(rootView, winparams); }
@Override public Object getSystemService(String name) { if (WINDOW_SERVICE.equals(name)) { return WindowManagerImpl.getDefault(); } ... }WindowManager是个接口,具体返回对象则是WindowManagerImpl的单例对象。
public class WindowManagerImpl implements WindowManager{ public static WindowManagerImpl getDefault() { return mWindowManager; } //以特定Window属性添加一个窗口 public void addView(View view, ViewGroup.LayoutParams params) { addView(view, params, false); } //参数nest表示该窗口是不是一个字窗口 private void addView(View view, ViewGroup.LayoutParams params, boolean nest) { ... final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; ViewRoot root; View panelParentView = null; //该子窗口对应地父窗口View synchronized (this) { ...//需要对传递过来地参数进行检测... //对每个窗口皆构建一个ViewRoot对象 root = new ViewRoot(view.getContext()); root.mAddNesting = 1; //设置root View 的LayoutParams为wparams,即WindowManager.LayoutParams类型 view.setLayoutParams(wparams); ...//对参数检测,以及拷贝原有数组... //将窗口对应地view、root、wparams保存在属性集合中 mViews[index] = view; mRoots[index] = root; mParams[index] = wparams; } // do this last because it fires off messages to start doing things // 调用ViewRoot对象去通知系统添加一个窗口 root.setView(view, wparams, panelParentView); } ... //这三个数组分别保存了一个窗口对应地属性 private View[] mViews; //root View对象 , View类型 private ViewRoot[] mRoots; //ViewRoot类型 , 与WMS通信 private WindowManager.LayoutParams[] mParams; //窗口属性 //WindowManagerImpl实现了单例模式 private static WindowManagerImpl mWindowManager = new WindowManagerImpl(); }
WindowManagerImpl类的三个数组集合保存了每个窗口相关属性,这样我们可以通过这些属性去操作特定的
public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks { View mView; //所有窗口地root View final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); ... /** * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; mWindowAttributes.copyFrom(attrs); //保存WindowManager.LayoutParams属性值 attrs = mWindowAttributes; ... mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); //请求UI开始绘制。 mInputChannel = new InputChannel(); //创建一个InputChannel对象,接受消息 try { //通知WindowManagerService添加一个窗口 res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); } ... view.assignParent(this); //将root View的父View设置为该ViewRoot对象(实现了ViewParent接口) ... } } } }说明:ViewRoot类继承了Handler,实现了ViewParent接口
1、保存相关属性值,例如:mView、mWindowAttributes等;
2、调用requestLayout()方法请求UI绘制,由于ViewRoot是个Handler对象,异步请求;
3、通知WindowManagerService添加一个窗口;
4、注册一个事件监听管道,用来监听:按键(KeyEvent)和触摸(MotionEvent)事件。
/** * {@inheritDoc} */ public void requestLayout() { checkThread(); //检查是不是UI线程调用,如果不是UI线程,会报异常 mLayoutRequested = true; //置为真,表示需要进行measure和layout过程 scheduleTraversals(); } //开始UI绘制流程 public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //防止多次调用 sendEmptyMessage(DO_TRAVERSAL); //异步请求UI绘制 } } @Override public void handleMessage(Message msg) { switch (msg.what) { case DO_TRAVERSAL: performTraversals(); //开始UI绘制 break; } }
private void performTraversals() { // cache mView since it is used so much below... final View host = mView; mTraversalScheduled = false; boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; //表示该窗口期望width值 int desiredWindowHeight; //表示该窗口期望width值 int childWidthMeasureSpec; //保存root View的widthMeasureSpec int childHeightMeasureSpec; //保存root View的heightMeasureSpec final View.AttachInfo attachInfo = mAttachInfo; final int viewVisibility = getHostVisibility(); boolean viewVisibilityChanged = mViewVisibility != viewVisibility || mNewSurfaceNeeded; float appScale = mAttachInfo.mApplicationScale; WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; surfaceChanged = true; params = lp; } Rect frame = mWinFrame; if (mFirst) { //mFirst表示是否是第一次绘制该Window fullRedrawNeeded = true; mLayoutRequested = true; DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics(); //第一次绘制时desiredWindowWidth,desiredWindowHeight 值大小为屏幕大小 desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; ... } else { //不是第一次绘制,则desiredWindowWidth值为frame保存大小,frame值会由WMS填充 desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); ... } ... boolean insetsChanged = false; if (mLayoutRequested) { ...//获得root View的widthMeasureSpec 和 heightMeasureSpec值 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); //开始measure过程 host.measure(childWidthMeasureSpec, childHeightMeasureSpec); } ... final boolean didLayout = mLayoutRequested; boolean triggerGlobalLayoutListener = didLayout || attachInfo.mRecomputeGlobalAttributes; if (didLayout) { ... //layout过程 host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); ... } ... if (!cancelDraw && !newSurface) { mFullRedrawNeeded = false; draw(fullRedrawNeeded); ... }
/** * @param windowSize The available width or height of the window * * @param rootDimension The layout params for one dimension (width or height) of the window. */ private int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't 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; }
发表评论
-
unity基础开发----物体位移和旋转实用代码
2013-11-21 22:46 1268using UnityEngine; using Syst ... -
Android中View绘制优化之一---- 优化布局层次
2012-09-04 23:00 1065... -
Android中View绘制优化二一---- 使用<include />标签复用布局文件
2012-09-08 13:54 1049... -
Android中View绘制优化之三---- 优化View
2012-09-13 21:00 1081... -
兰林任务管理应用程序雏形版以及概要说明
2012-09-15 21:54 874... -
Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)
2012-10-10 18:14 1160... -
Android中文件选择器的实现
2012-11-30 08:59 1164... -
【编译原理】使用Lex将C/C++文件输出为HTML文件
2012-07-20 09:37 106208年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大 ... -
【编译原理】正则表达式
2012-07-21 21:49 229308年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大 ... -
【OpenCV】访问Mat图像中每个像素的值
2012-07-22 07:10 1168今天百度搜资料还搜到了自己的。。。《访问图像中每个像素的值 ... -
【编译原理】用Yacc做语法分析
2012-07-23 05:47 176608年9月入学,12年7月毕 ... -
【UML】UML几种图的绘制
2012-07-24 09:49 98408年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大 ... -
【OpenCV】邻域滤波:方框、高斯、中值、双边滤波
2012-07-26 10:52 1452邻域滤波(卷积) 邻域算子值利用给定像素 ... -
【数据结构】排序算法:希尔、归并、快速、堆排序
2012-07-28 06:15 101908年9月入学,12年7月毕 ... -
【OpenCV】角点检测:Harris角点及Shi-Tomasi角点检测
2012-07-31 13:25 1542角点 特征检测与匹配 ... -
【UML】案例分析:机场运作系统
2012-08-01 17:22 310508年9月入学,12年7月毕 ... -
【OpenCV】边缘检测:Sobel、拉普拉斯算子
2012-08-04 13:41 1542边缘 边缘(edge)是指图像局部强度变化最显著的部分。主要 ... -
【OpenCV】Canny 边缘检测
2012-08-08 10:17 1994Canny 边缘检测算法 1986 ... -
【UML】案例分析:新型超市购物自助系统
2012-08-19 01:13 129408年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大 ... -
【数据结构】二叉树、AVL树
2012-08-21 00:30 109508年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大 ...
相关推荐
标题中的"S-measure(matlab)_S-measure_libraryzsp_"提到了S-measure,这是一个在图像分析和图形检测领域中使用的评价指标。S-measure,全称Structural Similarity Measure,是针对图像分割结果的质量评估工具,它在...
直线拟合是数据分析中的一个重要概念,它通过对数据点进行统计分析,找到最能代表这些点趋势的直线,这在测量中尤其有用,因为直线可以代表目标的边缘或者边界。 一维测量通常涉及以下几个步骤: 1. 图像采集:...
这个名为"apm_test.rar_android_android 回音_apm_far.pcm_flatness measure_voi"的压缩包文件似乎专注于Android系统中的回音消除算法,这对于实现高质量的VoIP(Voice over IP)服务至关重要。以下是对这个主题的...
在Android平台上,有许多应用程序专门设计来帮助用户计算BMI,以了解自己的身体状况。这款名为“BMI”的游戏化健康测量软件,旨在使健康测量变得更加有趣和便捷。 该软件的主要功能是测量个人的BMI,它需要用户输入...
标题中的“PR-F_Measure.zip_f measure _f-measure_ground truth_ground truth”指的是一个压缩文件,其中包含了一个名为“PR-F_Measure”的应用程序,该应用主要用于评估图像处理中的边缘检测方法,如Canny和Sobel...
在项目中使用自定义布局时,可以像使用系统布局一样在XML布局文件中声明它,并添加子视图。在Android Studio中,可以创建一个res/layout目录下的XML文件,声明自定义布局类,并在`<layout>`标签中设置属性。 ```xml...
标题中的“voltage_measure_C语言_lcd1602_51单片机_ADC0831_”项目涉及几个关键的硬件和软件组件,这些组件共同工作以实现一个实用的电压测量系统。在这个系统中,51系列单片机作为核心处理器,负责协调整个系统的...
"ads.rar_Measure For Measure_OCT"这一压缩包文件,其名称暗示了OCT技术在特定测量任务中的重要性,尤其可能是针对角膜或眼内角度的测量。 OCT是一种基于干涉原理的光学成像技术,它通过发射近红外光并分析反射...
根据【压缩包子文件的文件名称列表】中的"1.DSN",我们可以推测这是一个电路设计文件,可能是Proteus的DSN格式文件,其中包含了电路的详细布局和元器件信息。 在实际应用中,这个项目可能包含以下知识点: 1. **...
"measure_alpha_gamma_pscx" 是一个与电力系统分析相关的PSCAD模型,主要涉及的是阿尔法(Alpha)和伽马(Gamma)角的测量。在电力系统中,这两个角度是交流电路参数的重要组成部分,通常与功率因数和电能质量有关。...
在XML布局文件中,我们应尽量使用dp作为尺寸单位,而不是px。例如: ```xml android:layout_width="wrap_content" android:layout_height="48dp" android:text="点击我" /> ``` 2. **使用sp(scale-...
在Android开发中,布局文件(Layout XML)是构建用户界面的核心元素。布局文件定义了UI组件的排列方式、大小和相互关系。这篇文章将深入探讨几种常见的Android Layout XML属性,帮助开发者更好地理解和使用它们。 ...
"Evaluation_evaluation_f-measure_椭圆_椭圆检测_"这个标题暗示了我们正在讨论一个用于评估椭圆检测算法性能的系统,该系统可能包含了计算Recall、Precision和F-measure指标的Python代码。这些指标是衡量检测算法...
在Android中,我们可以使用LayoutInflater类来动态地从XML布局文件中加载视图,并将其添加到父视图中。这里涉及的关键步骤包括: 1. 初始化LayoutInflater:通常在Activity或Fragment中,我们可以通过`...
最后,别忘了在XML布局文件中使用自定义的`WordWrapLayout`,并添加子View: ```xml xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height...
首先,我们需要在布局文件中声明这个自定义视图,并设置其属性,如方向(水平)、填充和边距等。 ```xml android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation=...
新软件可以计算晶体在旋涡流体和温度变化中的增长,以及处于平衡状态下的气泡(见书封面)。 **4. 双泡猜想** - 2000年,Hutchings、Morgan、Ritoré 和 Ros 宣布证明了双泡猜想,即熟悉的双肥皂泡提供了最小面积...