Android中View绘制流程:measure过程,layout过程
来源:互联网 发布:enc加密软件 编辑:程序博客网 时间:2024/05/18 03:54
基本概念以及流程
在正式介绍之前,还是需要理解与UI相关的基本概念。
- Activity:Activity包含一个Window,该Window在Activity的attach方法中通过调用PolicyManager.makeNewWindo创建;
- View:最基本的UI组件,表示屏幕上的一个矩形区域;
- DecorView:是Window中View的RootView,设置窗口属性;
- Window:表示顶层窗口,管理界面的显示和事件的响应;每个Activity 均会创建一个 PhoneWindow对象,是Activity和整个View系统交互的接口
- WindowManager:一个interface,继承自ViewManager。所在应用进程的窗口管理器;有一个implementation WindowManagerImpl;主要用来管理窗口的一些状态、属性、view增加、删除、更新、窗口顺序、消息收集和处理等。
- ViewRoot:通过IWindowSession接口与全局窗口管理器进行交互:界面控制和消息响应;
- ActivityThread:应用程序的主线程,其中会创建关联当前Activity与Window;创建WIndowManager实现类实例,把当前DecoView加入到WindowManager;
通过源码简单理了下从启动Activity到创建View的过程,大致如下:
在上图中,performLaunchActivity函数是关键函数,除了新建被调用的Activity实例外,还负责确保Activity所在的应用程序启动、读取manifest中关于此activity设置的主题信息以及上图对“6.onCreate”调用也是通过对 mInstrumentation.callActivityOnCreate来实现的。图中的“8.mContentParent.addView”其实就是架构图中phoneWindow内DecorView里面的ContentViews,该对象是一个ViewGroup类实例。在调用AddView之后,最终就会触发ViewRoot中的scheduleTraversals这个异步函数,从而进入ViewRoot的performTraversals函数,在performTraversals函数中就启动了View的绘制流程。
performTraversals函数在2.3.5版本源码中就有近六百行的代码,跟我们绘制view相关的可以抽象成如下的简单流程图:
上述流程主要调用了View的measure、layout和draw三个函数。
measure过程
MeasureSpc类说明
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) : 根据提供的大小值和模式创建一个测量值(格式)
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); } } ... }
ViewGroup.LayoutParams类及其派生类
ViewGroup.LayoutParams类说明
Android API中如下介绍: LayoutParams are used by views to tell their parents how they want to be laid out.
意思大概是说: View通过LayoutParams类告诉其父视图它想要地大小(即,长度和宽度)。
因此,每个View都包含一个ViewGroup.LayoutParams类或者其派生类,View类依赖于ViewGroup.LayoutParams。
源码:
public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... public static class LayoutParams { /** * Special value for the height or width requested by a View. * FILL_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. This value is deprecated * starting in API Level 8 and replaced by {@link #MATCH_PARENT}. */ @Deprecated public static final int FILL_PARENT = -1; // 注意值为-1,Android2.2版本不建议使用 /** * Special value for the height or width requested by a View. * MATCH_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. Introduced in API Level 8. */ public static final int MATCH_PARENT = -1; // 注意值为-1 /** * Special value for the height or width requested by a View. * WRAP_CONTENT means that the view wants to be just large enough to fit * its own internal content, taking its own padding into account. */ public static final int WRAP_CONTENT = -2; // 注意值为-2 /** * Information about how wide the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT , * in API Level 8) or WRAP_CONTENT. or an exact size. */ public int width; //该View的宽度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值 /** * Information about how tall the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT , * in API Level 8) or WRAP_CONTENT. or an exact size. */ public int height; //该View的高度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值 /** * Used to animate layouts. */ public LayoutAnimationController.AnimationParameters layoutAnimationParameters; /** * Creates a new set of layout parameters. The values are extracted from * the supplied attributes set and context. The XML attributes mapped * to this set of layout parameters are:、 */ public LayoutParams(Context c, AttributeSet attrs) { TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout); setBaseAttributes(a, R.styleable.ViewGroup_Layout_layout_width, R.styleable.ViewGroup_Layout_layout_height); a.recycle(); } /** * Creates a new set of layout parameters with the specified width * and height. */ public LayoutParams(int width, int height) { this.width = width; this.height = height; } /** * Copy constructor. Clones the width and height values of the source. * * @param source The layout params to copy from. */ public LayoutParams(LayoutParams source) { this.width = source.width; this.height = source.height; } /** * Used internally by MarginLayoutParams. * @hide */ LayoutParams() { } /** * Extracts the layout parameters from the supplied attributes. * * @param a the style attributes to extract the parameters from * @param widthAttr the identifier of the width attribute * @param heightAttr the identifier of the height attribute */ protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { width = a.getLayoutDimension(widthAttr, "layout_width"); height = a.getLayoutDimension(heightAttr, "layout_height"); } }
我们发现FILL_PARENT/MATCH_PARENT值为 -1 ,WRAP_CONETENT值为-2,是不是有点诧异? 将值设置为负值的目的是为了区别View的具体值(an exact size) 总是大于0的。
ViewGroup子类可以实现自定义LayoutParams,自定义LayoutParams提供了更好地扩展性,例如LinearLayout就有LinearLayout. LayoutParams自定义类(见下文)。整个LayoutParams类家族还是挺复杂的。
ViewGroup.LayoutParams及其常用派生类的类图(部分类图)如下:
有两种方法会设置View的LayoutParams属性:
1、 直接添加子View时,常见于如下几种方法:ViewGroup.java
//Adds a child view. void addView(View child, int index) //Adds a child view with this ViewGroup's default layout parameters //and the specified width and height. void addView(View child, int width, int height) //Adds a child view with the specified layout parameters. void addView(View child, ViewGroup.LayoutParams params)
2、 通过xml布局文件指定某个View的属性为:android:layout_heigth=””以及android:layout_weight=”” 时。
总的来说,这两种方式都会设定View的LayoutParams属性值—-指定的或者Default值。
直接添加子View时,比较容易理解,我们先来看看这种方式设置LayoutParams的过程:
路径:\frameworks\base\core\java\android\view\ViewGroup.java
public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... /** * Adds a child view. If no layout parameters are already set on the child, the * default parameters for this ViewGroup are set on the child. * * @param child the child view to add * * @see #generateDefaultLayoutParams() */ public void addView(View child) { addView(child, -1); } /** * Adds a child view. If no layout parameters are already set on the child, the * default parameters for this ViewGroup are set on the child. * * @param child the child view to add * @param index the position at which to add the child * * @see #generateDefaultLayoutParams() */ public void addView(View child, int index) { LayoutParams params = child.getLayoutParams(); if (params == null) { params = generateDefaultLayoutParams(); //返回默认地LayoutParams类,作为该View的属性值 if (params == null) {//如果不能获取到LayoutParams对象,则抛出异常。 throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); } } addView(child, index, params); } /** * Adds a child view with this ViewGroup's default layout parameters and the * specified width and height. * * @param child the child view to add */ public void addView(View child, int width, int height) { //返回默认地LayoutParams类,作为该View的属性值 final LayoutParams params = generateDefaultLayoutParams(); params.width = width; //重新设置width值 params.height = height; //重新设置height值 addView(child, -1, params); //这儿,我们有指定width、height的大小了。 } /** * Adds a child view with the specified layout parameters. * * @param child the child view to add * @param params the layout parameters to set on the child */ public void addView(View child, LayoutParams params) { addView(child, -1, params); } /** * Adds a child view with the specified layout parameters. * * @param child the child view to add * @param index the position at which to add the child * @param params the layout parameters to set on the child */ public void addView(View child, int index, LayoutParams params) { ... // addViewInner() will call child.requestLayout() when setting the new LayoutParams // therefore, we call requestLayout() on ourselves before, so that the child's request // will be blocked at our level requestLayout(); invalidate(); addViewInner(child, index, params, false); } /** * Returns a set of default layout parameters. These parameters are requested * when the View passed to {@link #addView(View)} has no layout parameters * already set. If null is returned, an exception is thrown from addView. * * @return a set of default layout parameters or null */ protected LayoutParams generateDefaultLayoutParams() { //width 为 WRAP_CONTENT大小 , height 为WRAP_CONTENT //ViewGroup的子类可以重写该方法,达到其特定要求。稍后会以LinearLayout类为例说明。 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { if (!checkLayoutParams(params)) { //params对象是否为null params = generateLayoutParams(params); //如果params对象是为null,重新构造个LayoutParams对象 } //preventRequestLayout值为false if (preventRequestLayout) { child.mLayoutParams = params; //为View的mLayoutParams属性赋值 } else { child.setLayoutParams(params);//为View的mLayoutParams属性赋值,但会调用requestLayout()请求重新布局 } //if else 语句会设置View为mLayoutParams属性赋值 ... } ... }
主要功能就是在添加子View时为其构建了一个LayoutParams对象。但更重要的是,ViewGroup的子类可以重载上面的几个方法,返回特定的LayoutParams对象,例如:对于LinearLayout而言,则是LinearLayout.LayoutParams对象。这么做地目的是,能在其他需要它的地方,可以将其强制转换成LinearLayout.LayoutParams对象。
LinearLayout重写函数地实现为:
public class LinearLayout extends ViewGroup { ... @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LinearLayout.LayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { //该LinearLayout是水平方向还是垂直方向 if (mOrientation == HORIZONTAL) { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } else if (mOrientation == VERTICAL) { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } return null; } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } /** * Per-child layout information associated with ViewLinearLayout. * * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity */ //自定义的LayoutParams类 public static class LayoutParams extends ViewGroup.MarginLayoutParams { /** * Indicates how much of the extra space in the LinearLayout will be * allocated to the view associated with these LayoutParams. Specify * 0 if the view should not be stretched. Otherwise the extra pixels * will be pro-rated among all views whose weight is greater than 0. */ @ViewDebug.ExportedProperty(category = "layout") public float weight; // 见于属性,android:layout_weight="" ; /** * Gravity for the view associated with these LayoutParams. * * @see android.view.Gravity */ public int gravity = -1; // 见于属性, android:layout_gravity="" ; /** * {@inheritDoc} */ public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a =c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout); weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0); gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1); a.recycle(); } /** * {@inheritDoc} */ public LayoutParams(int width, int height) { super(width, height); weight = 0; } /** * Creates a new set of layout parameters with the specified width, height * and weight. * * @param width the width, either {@link #MATCH_PARENT}, * {@link #WRAP_CONTENT} or a fixed size in pixels * @param height the height, either {@link #MATCH_PARENT}, * {@link #WRAP_CONTENT} or a fixed size in pixels * @param weight the weight */ public LayoutParams(int width, int height, float weight) { super(width, height); this.weight = weight; } public LayoutParams(ViewGroup.LayoutParams p) { super(p); } public LayoutParams(MarginLayoutParams source) { super(source); } } ... }
LinearLayout.LayoutParams类继承至ViewGroup.MarginLayoutParams类,添加了对android:layout_weight以及android:layout_gravity这两个属性的获取和保存。而且它的重写函数返回的都是LinearLayout.LayoutParams类型。这样,我们可以再对子View进行其他操作时,可以将将其强制转换成LinearLayout.LayoutParams对象进行 使用。
例如,LinearLayout进行measure过程,使用了LinearLayout.LayoutParam对象,有如下代码:
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); } } /** * Measures the children when the orientation of this LinearLayout is set * to {@link #VERTICAL}. * * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. * @param heightMeasureSpec Vertical space requirements as imposed by the parent. * * @see #getOrientation() * @see #setOrientation(int) * @see #onMeasure(int, int) */ void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; ... // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); //获得索引处为i的子VIew ... //注意,我们将类型为 ViewGroup.LayoutParams的实例对象强制转换为了LinearLayout.LayoutParams, //即父对象转换为了子对象,能这样做的原因就是LinearLayout的所有子View的LayoutParams类型都为 //LinearLayout.LayoutParams LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); ... } ... }
超类ViewGroup.LayoutParams强制转换为了子类LinearLayout.LayoutParams,因为LinearLayout的每个
”直接“子View的LayoutParams属性都是LinearLayout.LayoutParams类型,因此可以安全转换。
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.measure(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标记 }
将上面列表项转换为表格为:
为了帮助大家理解,下面我们分析某个窗口使用地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对应地widthSpecmode值为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等属性的处理。
/** * 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、子View measure()完成后,需要取得该子View地宽高实际值,继而做处理(例如:LinearLayout属性为android:widht=”wrap_content”时,LinearLayout的实际width值则是每个子View的width值的累加值)。
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
layout过程
DecorView类的成员函数layout是从父类View继承下来的,因此,我们就从View类的成员函数layout开始分析应用程序窗口的布局过程,如图所示:
Step 1:View.layout
frameworks/base/core/java/android/view/View.java中。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... int mPrivateFlags; ...... public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { ...... onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; } ...... }
参数l、t、r和b分别用来描述当前视图的左上右下四条边与其父视图的左上右下四条边的距离,这样当前视图通过这四个参数就可以知道它在父视图中的位置以及大小。
View类的成员函数layout首先调用另外一个成员函数setFrame来设置当前视图的位置以及大小。设置完成之后,如果当前视图的大小或者位置与上次相比发生了变化,那么View类的成员函数setFrame的返回值changed就会等于true。在这种情况下, View类的成员函数layout就会继续调用另外一个成员函数onLayout重新布局当前视图的子视图。此外,如果此时View类的成员变量mPrivateFlags的LAYOUT_REQUIRED位不等于0,那么也表示当前视图需要重新布局它的子视图,因此,这时候View类的成员函数layout也会调用另外一个成员函数onLayout。
当前视图的子视图都重新布局完成之后,View类的成员函数layout就可以将成员变量mPrivateFlags的LAYOUT_REQUIRED位设置为0了,因为此时当前视图及其子视图都已经执行了一次布局操作了。
View类的成员函数layout最后还会将成员变量mPrivateFlags的FORCE_LAYOUT位设置为0,也是因为此时当前视图及其子视图的布局已经是最新的了。
接下来,我们就继续分析View类的成员函数setFrame和onLayout的实现,以便可以了解当前视图及其子视图是如何执行布局操作的。
Step 2:View.setFrame
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... int mPrivateFlags; ...... int mViewFlags; ...... protected int mLeft; ...... protected int mRight; ...... protected int mTop; ...... protected int mBottom; ...... private boolean mBackgroundSizeChanged; ...... protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; ...... if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & DRAWN; // Invalidate our old position invalidate(); int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; mLeft = left; mTop = top; mRight = right; mBottom = bottom; mPrivateFlags |= HAS_BOUNDS; int newWidth = right - left; int newHeight = bottom - top; if (newWidth != oldWidth || newHeight != oldHeight) { onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, therby clearing // the DRAWN bit. mPrivateFlags |= DRAWN; invalidate(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; } return changed; } ...... }
View类的成员变量mLeft、mRight、mTop和mBottom分别用来描述当前视图的左右上下四条边与其父视图的左右上下四条边的距离,如果它们的值与参数left、right、top和bottom的值不相等,那么就说明当前视图的大小或者位置发生变化了。这时候View类的成员函数setFrame就需要将参数left、right、top和bottom的值分别记录在成员变量mLeft、mRight、mTop和mBottom中。在记录之前,还会执行两个操作:
1. 将成员变量mPrivateFlags的DRAWN位记录在变量drawn中,并且调用另外一个成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行。如果已经执行了的话,那么就会再请求执行一个UI绘制操作,以便可以在修改当前视图的大小和位置之前,将当前视图在当前位置按照当前大小显示一次。
- 计算当前视图上一次的宽度oldWidth和oldHeight,以便接下来可以检查当前视图的大小是否发生了变化。
当前视图距离父视图的边距一旦设置好之后,它就是一个具有边界的视图了,因此,View类的成员函数setFrame接着还会将成员变量mPrivateFlags的HAS_BOUNDS设置为1。
View类的成员函数setFrame再接下来又会计算当前视图新的宽度newWidth和高度newHeight,如果它们与上一次的宽度oldWidth和oldHeight的值不相等,那么就说明当前视图的大小发生了变化,这时候就会调用另外一个成员函数onSizeChanged来让子类有机会处理这个变化事件。
View类的成员函数setFrame接下来继续判断当前视图是否是可见的,即成员变量mViewFlags的VISIBILITY_MASK位的值是否等于VISIBLE。如果是可见的话,那么就需要将成员变量mPrivateFlags的DRAWN位设置为1,以便接下来可以调用另外一个成员函数invalidate来成功地执行一次UI绘制操作,目的是为了将当前视图马上显示出来。
View类的成员变量mPrivateFlags的DRAWN位描述的是当前视图上一次请求的UI绘制操作是否已经执行过了。如果它的值等于1,就表示已经执行过了,否则的话,就表示还没在等待执行。前面第一次调用View类的成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行时,如果发现已经执行了,那么就会重新请求执行一次新的UI绘制操作,这时候会导致当前视图的成员变量mPrivateFlags的DRAWN位重置为0。注意,新请求执行的UI绘制只是为了在修改当前视图的大小以及大小之前,先将它在上一次设置的大小以及位置中绘制出来,这样就可以使得当前视图的大小以及位置出现平滑的变换。换句话说,新请求执行的UI绘制只是为了获得一个中间效果,它不应该影响当前视图的绘制状态,即不可以修改当前视图的成员变量mPrivateFlags的DRAWN位。因此,我们就需要在前面第一次调用View类的成员函数invalidate前,先将当前视图的成员变量mPrivateFlags的DRAWN位保存下来,即保存在变量drawn中,然后等到调用之后,再将变量drawn的值恢复到当前视图的成员变量mPrivateFlags的DRAWN位中去。
另一方面,如果当前视图的大小和位置发生了变化,View类的成员函数setFrame还会将成员变量mBackgroundSizeChanged的值设置为true,以便可以表示当前视图的背景大小发生了变化。
最后,View类的成员函数setFrame将变量changed的值返回给调用者,以便调用者可以知道当前视图的大小和位置是否发生了变化
接下来它就会调用另外一个成员函数onLayout来重新布局当前视图的子视图的布局了。View类的成员函数onLayout是由子类来重写的,并且只有当该子类描述的是一个容器视图时,它才会重写父类View的成员函数onLayout。前面我们已经假设当前正在处理的是应用程序窗口的顶层视图,它的类型为DecorView,并且它描述的是一个容器视图,因此,接下来我们就会继续分析DecorView类的成员函数onLayout的实现。
事实上,DecorView类是通过FrameLayout类来间接继承View类的,并且它的成员函数onLayout是从FrameLayout类继承下来的,因此,接下来我们实际上要分析的是FrameLayout类的成员函数onLayout的实现。
Step 3:onLayout
public class FrameLayout extends ViewGroup { ...... protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int count = getChildCount(); final int parentLeft = mPaddingLeft + mForegroundPaddingLeft; final int parentRight = right - left - mPaddingRight - mForegroundPaddingRight; final int parentTop = mPaddingTop + mForegroundPaddingTop; final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom; mForegroundBoundsChanged = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft = parentLeft; int childTop = parentTop; final int gravity = lp.gravity; if (gravity != -1) { final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (horizontalGravity) { case Gravity.LEFT: childLeft = parentLeft + lp.leftMargin; break; case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = parentRight - width - lp.rightMargin; break; default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } } ...... }
FrameLayout类的成员变量mPaddingLeft、mPaddingRight、mPaddingTop、mPaddingBottom和mForegroundPaddingLeft、mForegroundPaddingRight、mForegroundPaddingTop、mForegroundPaddingBottom的含义我们在前面分析Android应用程序窗品的测量过程时已经解释过了,它们描述的是当前视图的内边距,而参数left、top、right和bottom描述的是当前视图的外边距,即它与父窗口的边距。通过上述这些参数,我们就可以得到当前视图的子视图所能布局在的区域。
FrameLayout类的成员函数onLayout通过一个for循环来布局当前视图的每一个子视图。如果一个子视图child是可见的,那么FrameLayout类的成员函数onLayout就会根据当前视图可以用来显示子视图的区域以及它所设置的gravity属性来得到它在应用程序窗口中的左上角位置(childeLeft,childTop)。
当一个子视图child在应用程序窗口中的左上角位置确定了之后,再结合它在前面的测量过程中所确定的宽度width和高度height,我们就可以完全地确定它在应用程序窗口中的布局了,即可以调用它的成员函数layout来设置它的位置和大小了,这刚好就是前面的Step 1所执行的操作。注意,如果当前正在布局的子视图child描述的也是一个视图容器,那么它又会重复执行Step 3的操作,直到它的所有子孙视图都布局完成为止。
转自:
http://blog.csdn.net/qinjuning/article/details/8051811
http://blog.csdn.net/qinjuning/article/details/8074262
- Android中View绘制流程:measure过程,layout过程
- 【Android】View绘制过程分析之measure
- 【Android View绘制之旅】Measure过程
- (六)VIew的绘制过程-measure、layout、draw
- View的绘制流程(二)--------view的measure过程
- View绘制之measure过程
- View 绘制流程梳理及 Measure 过程详解
- Android View measure过程
- 【Android】View绘制过程分析之layout
- 【Android View绘制之旅】Layout过程
- Android中View和ViewGroup的measure和layout过程分析
- Android View绘制:measure,layout和draw
- 【Android系列】View的绘制之measure过程
- Android中measure过程、view绘制原理和MeasureSpec介绍及使用详解
- View绘制之layout过程
- View绘制详解,layout过程
- Android中view绘制过程
- Android视图的绘制流程(下)——View的Layout与Draw过程
- opencv2.4.9 调用sift特征
- scala中用implicit实现依赖注入
- Android 自定义View (二)
- android选取照片并以其uri取得绝对路径
- Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框
- Android中View绘制流程:measure过程,layout过程
- Hive分区表
- 字符串专题:Manacher算法处理字符串回文
- ios8下面定位失败解决方案
- cc2530 PM2模式唤醒后无法接收数据解决方法
- Using Flashback Database and Restore Points
- Implementing Inheritance
- js-检索文字高亮显示
- Android查看内存使用的方式(Running services、MemoryInfo、getNativeHeapSize) 整理的不错