标签:
本文原创, 转载请注明出处: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;
-
- private static final int MODE_MASK = 0x3 << MODE_SHIFT;
-
-
- public static final int UNSPECIFIED = 0 << MODE_SHIFT;
-
- public static final int EXACTLY = 1 << MODE_SHIFT;
-
- public static final int AT_MOST = 2 << MODE_SHIFT;
-
-
- 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 {
- ...
-
- View mView;
-
-
- private void performTraversals(){
- ...
-
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
-
-
-
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- ...
- }
- ...
- }
这儿,我并没有说出childWidthMeasureSpec和childHeightMeasureSpec类的来由(为了避免额外地开销,等到
第三部分时我们在来攻克它,现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。
Step 2 、调用measure()方法去做一些前期准备
measure()方法原型定义在View.java类中,final修饰符修饰,其不能被重载:
- public class View implements ... {
- ...
-
-
-
-
-
-
-
-
-
-
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
-
- if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
- widthMeasureSpec != mOldWidthMeasureSpec ||
- heightMeasureSpec != mOldHeightMeasureSpec) {
-
-
-
- mPrivateFlags &= ~MEASURED_DIMENSION_SET;
-
-
-
- onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-
-
- if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
- throw new IllegalStateException("onMeasure() did not set the"
- + " measured dimension by calling" + " setMeasuredDimension()");
- }
-
- mPrivateFlags |= 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的长宽值,其默认实现为:
-
-
-
-
-
-
-
-
-
-
-
-
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
-
-
-
-
-
-
-
-
-
-
-
- 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;
- }
-
- protected int getSuggestedMinimumWidth() {
- int suggestedMinWidth = mMinWidth;
-
- if (mBGDrawable != null) {
- final int bgMinWidth = mBGDrawable.getMinimumWidth();
- if (suggestedMinWidth < bgMinWidth) {
- suggestedMinWidth = bgMinWidth;
- }
- }
-
- return suggestedMinWidth;
- }
-
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
-
- mPrivateFlags |= MEASURED_DIMENSION_SET;
- }
主要功能就是根据该View属性(android:minWidth和背景图片大小)和父View对该子View的"测量要求",设置该 View的 mMeasuredWidth 和 mMeasuredHeight 值。
这儿只是一般的View类型地实现方法。一般来说,父View,也就是ViewGroup类型,都需要在重写onMeasure() 方法,遍历所有子View,设置每个子View的大小。基本思想如下:遍历所有子View,设置每个子View的大小。伪
代码表示为:
-
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- super.onMeasure(widthMeasureSpec , heightMeasureSpec)
-
-
-
-
- for(int i = 0 ; i < getChildCount() ; i++){
- View child = getChildAt(i);
-
- child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- }
Step 2、Step 3 代码也比较好理解,但问题是我们示例代码中widthMeasureSpec、heightMeasureSpec是如何
确定的呢?父View是如何设定其值的?
要想回答这个问题,我们看是去源代码里找找答案吧。在ViewGroup.java类中,为我们提供了三个方法,去设置
每个子View的大小,基本思想也如同我们之前描述的思想:遍历所有子View,设置每个子View的大小。
主要有如下方法:
-
-
-
-
-
-
-
-
- protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
- final int size = mChildrenCount;
- final View[] children = mChildren;
- for (int i = 0; i < size; ++i) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
- }
- }
- }
-
-
-
-
-
-
-
-
-
-
-
- protected void measureChild(View child, int parentWidthMeasureSpec,
- int parentHeightMeasureSpec) {
- final LayoutParams lp = child.getLayoutParams();
-
- final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight, lp.width);
-
- final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- mPaddingTop + mPaddingBottom, lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
measureChildren()方法:遍历所有子View,调用measureChild()方法去设置该子View的属性值。
measureChild() 方法 : 获取特定子View的widthMeasureSpec、heightMeasureSpec,调用measure()方法
设置子View的实际宽高值。
getChildMeasureSpec()就是获取子View的widthMeasureSpec、heightMeasureSpec值。
-
-
-
-
-
-
-
-
-
-
-
-
- public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
- int specMode = MeasureSpec.getMode(spec);
- int specSize = MeasureSpec.getSize(spec);
-
- int size = Math.max(0, specSize - padding);
-
- int resultSize = 0;
- int resultMode = 0;
-
- switch (specMode) {
-
-
- case MeasureSpec.EXACTLY:
-
- if (childDimension >= 0) {
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.MATCH_PARENT) {
-
- resultSize = size;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
-
- case MeasureSpec.AT_MOST:
-
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
-
- else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
-
- case MeasureSpec.UNSPECIFIED:
-
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = 0;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
-
- else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = 0;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
- break;
- }
-
- 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()原型:
-
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
-
- mPrivateFlags |= 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
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- if (mOrientation == VERTICAL) {
- measureVertical(widthMeasureSpec, heightMeasureSpec);
- } else {
- measureHorizontal(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
- mTotalLength = 0;
- float totalWeight = 0;
- int maxWidth = 0;
- ...
- final int count = getVirtualChildCount();
-
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- ...
-
- for (int i = 0; i < count; ++i) {
- final View child = getVirtualChildAt(i);
- ...
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
-
- totalWeight += lp.weight;
-
- if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
- ...
- } else {
- int oldHeight = Integer.MIN_VALUE;
-
- if (lp.height == 0 && lp.weight > 0) {
- oldHeight = 0;
- lp.height = LayoutParams.WRAP_CONTENT;
- }
-
-
-
-
-
- measureChildBeforeLayout(
- child, i, widthMeasureSpec, 0, heightMeasureSpec,
- totalWeight == 0 ? mTotalLength : 0);
-
-
-
-
-
- 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);
- ...
- }
-
- ...
- }
- void measureChildBeforeLayout(View child, int childIndex,
- int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
- int totalHeight) {
-
- measureChildWithMargins(child, widthMeasureSpec, totalWidth,
- heightMeasureSpec, totalHeight);
- }
- ...
继续看看measureChildWithMargins()方法,该方法定义在ViewGroup.java内,基本流程同于measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理。
measureChildWithMargins@ViewGroup.java
-
-
-
-
-
-
-
-
-
- protected void measureChildWithMargins(View child,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
-
-
- 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值的累加值)。
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:
-
- public Class MyView extends View {
-
-
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-
- int width = 0 ;
- int height = 0 ;
-
- 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
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
- if (mOrientation == VERTICAL) {
- layoutVertical();
- } else {
- layoutHorizontal();
- }
- }
-
- void layoutVertical() {
- ...
- final int count = getVirtualChildCount();
- ...
- for (int i = 0; i < count; i++) {
- final View child = getVirtualChildAt(i);
- if (child == null) {
- childTop += measureNullChild(i);
- } else if (child.getVisibility() != GONE) {
-
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
-
- ...
-
- setChildFrame(child, childLeft, childTop + getLocationOffset(child),
- childWidth, childHeight);
- childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
-
- i += getChildrenSkipCount(child, i);
- }
- }
- }
-
- private void setChildFrame(View child, int left, int top, int width, int height) {
-
- 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。如下代码所示:
-
- public class MultiViewGroup extends ViewGroup {
- private void init() {
-
- LinearLayout oneLL = new LinearLayout(mContext);
- oneLL.setBackgroundColor(Color.RED);
- addView(oneLL);
- ...
- }
- @Override
-
-
-
-
-
-
-
-
-
-
-
-
- }
-
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
- Log.i(TAG, "--- start onLayout --");
- int startLeft = 0;
- int startTop = 10;
- 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 ;
-
- }
- }
- }
更多关于自定义ViewGroup无须重写measure动作的,可以参考 Android API :
3、root View被添加至窗口时,UI框架是如何设置其LayoutParams值
老子道德经有言:“道生一,一生二,二生三,三生万物。” UI绘制也就是个递归过程。理解其基本架构后,
也就“掌握了一个中心点”了。在第一节中,我们没有说明开始UI绘制时 ,没有说明mView.measure()参数地由来,
参数也就是我们本节需要弄懂的“道” --- root View的 widthMeasureSpec和heightMeasureSpec 是如何确定的。
对于如下布局文件: main.xml
- <?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>
当使用LayoutInflater类解析成View时 ,LinearLayout对象的LayoutParams参数为null 。具体原因请参考上篇博文
任何一个View被添加至窗口时,都需要利用WindowManager类去操作。例如,如下代码:
-
- public void showView()
- {
-
- LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- View rootView = layoutInflater.inflate(R.layout.main, null);
-
- WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
-
- WindowManager.LayoutParams winparams = WindowManager.LayoutParams();
-
- winparams.x = 0;
- winparams.y = 0;
-
-
- winparams.width = WindowManager.LayoutParams.WRAP_CONTENT;;
- winparams.height = WindowManager.LayoutParams.WRAP_CONTENT;;
-
- windowManager.addView(rootView, winparams);
- }
关于WindowManager的使用请看如下博客 :
关于WindowManager.LayoutParams类说明请看如下博客:
下面,我们从获得WindowManager对象引用开始,一步步观察addView()做了一些什么事情。
Step 1 、获得WindowManager对象服务 ,具体实现类在ContextImpl.java内中
路径: /frameworks/base/core/java/android/app/ContextImpl.java
- @Override
- public Object getSystemService(String name) {
- if (WINDOW_SERVICE.equals(name)) {
- return WindowManagerImpl.getDefault();
- }
- ...
- }
WindowManager是个接口,具体返回对象则是WindowManagerImpl的单例对象。
Step 2 、 获得WindowManagerImpl的单例对象,以及部分源码分析
路径: /frameworks/base/core/java/android/view/WindowManagerImpl.java
- public class WindowManagerImpl implements WindowManager{
-
- public static WindowManagerImpl getDefault()
- {
- return mWindowManager;
- }
-
- public void addView(View view, ViewGroup.LayoutParams params)
- {
- addView(view, params, false);
- }
-
- private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
- { ...
- final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
-
- ViewRoot root;
- View panelParentView = null;
-
- synchronized (this) {
-
- ...
-
-
- root = new ViewRoot(view.getContext());
- root.mAddNesting = 1;
-
- view.setLayoutParams(wparams);
- ...
-
-
- mViews[index] = view;
- mRoots[index] = root;
- mParams[index] = wparams;
- }
-
-
- root.setView(view, wparams, panelParentView);
- }
- ...
-
- private View[] mViews;
- private ViewRoot[] mRoots;
- private WindowManager.LayoutParams[] mParams;
-
-
- private static WindowManagerImpl mWindowManager = new WindowManagerImpl();
- }
WindowManagerImpl类的三个数组集合保存了每个窗口相关属性,这样我们可以通过这些属性去操作特定的 窗口(例如,可以根据View去更新/销毁该窗口)。当参数检查成功时,构建一个ViewRoot对象,并且设置设置root
View 的LayoutParams为wparams,即WindowManager.LayoutParams类型。最后调用root.setView()方法去通知
系统需要创建该窗口。我们接下来往下看看ViewRoot类相关操作。
Step 3、
- public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {
-
- View mView;
- final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
-
- ...
-
-
-
- public void setView(View view, WindowManager.LayoutParams attrs,
- View panelParentView) {
- synchronized (this) {
- if (mView == null) {
- mView = view;
- mWindowAttributes.copyFrom(attrs);
- attrs = mWindowAttributes;
- ...
-
- mAdded = true;
- int res;
-
-
-
-
- requestLayout();
- mInputChannel = new InputChannel();
- try {
-
- res = sWindowSession.add(mWindow, mWindowAttributes,
- getHostVisibility(), mAttachInfo.mContentInsets,
- mInputChannel);
- }
- ...
- view.assignParent(this);
- ...
- }
- }
- }
- }
说明:ViewRoot类继承了Handler,实现了ViewParent接口
setView()方法地主要功能如下:
1、保存相关属性值,例如:mView、mWindowAttributes等;
2、调用requestLayout()方法请求UI绘制,由于ViewRoot是个Handler对象,异步请求;
3、通知WindowManagerService添加一个窗口;
4、注册一个事件监听管道,用来监听:按键(KeyEvent)和触摸(MotionEvent)事件。
我们这儿重点关注 requestLayout()方法请求UI绘制地流程。
Step 4、异步调用请求UI绘制
-
-
-
- public void requestLayout() {
- checkThread();
- mLayoutRequested = true;
- scheduleTraversals();
- }
-
- public void scheduleTraversals() {
- if (!mTraversalScheduled) {
- mTraversalScheduled = true;
- sendEmptyMessage(DO_TRAVERSAL);
- }
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case DO_TRAVERSAL:
- performTraversals();
- break;
- }
- }
由于performTraversals()方法比较复杂,我们侧重于第一次设置root View的widhtSpecSize以及
heightSpecSize值。
- private void performTraversals() {
-
- final View host = mView;
-
- mTraversalScheduled = false;
- boolean surfaceChanged = false;
- WindowManager.LayoutParams lp = mWindowAttributes;
-
- int desiredWindowWidth;
- int desiredWindowHeight;
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
-
- 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) {
- fullRedrawNeeded = true;
- mLayoutRequested = true;
-
- DisplayMetrics packageMetrics =
- mView.getContext().getResources().getDisplayMetrics();
-
- desiredWindowWidth = packageMetrics.widthPixels;
- desiredWindowHeight = packageMetrics.heightPixels;
- ...
- } else {
- desiredWindowWidth = frame.width();
- desiredWindowHeight = frame.height();
- ...
- }
- ...
- boolean insetsChanged = false;
-
- if (mLayoutRequested) {
- ...
- childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
-
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- ...
- final boolean didLayout = mLayoutRequested;
-
- boolean triggerGlobalLayoutListener = didLayout
- || attachInfo.mRecomputeGlobalAttributes;
- if (didLayout) {
- ...
- host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
- ...
- }
- ...
- if (!cancelDraw && !newSurface) {
- mFullRedrawNeeded = false;
- draw(fullRedrawNeeded);
- ...
- }
-
-
-
-
-
- private int getRootMeasureSpec(int windowSize, int rootDimension) {
- int measureSpec;
- switch (rootDimension) {
- case ViewGroup.LayoutParams.MATCH_PARENT:
-
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
- break;
- case ViewGroup.LayoutParams.WRAP_CONTENT:
-
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
- break;
- default:
-
- measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
- break;
- }
- return measureSpec;
- }
调用root View的measure()方法时,其参数是由getRootMeasureSpec()设置的,基本思路同我们前面描述的
差不多。贴出来的代码只是简简单单列出了measure 、layout 、 draw 过程的调用点,里面有很多逻辑处理,
阅读起来比较费劲,我也只能算是个囫囵吞枣水平。大家有兴趣地可以看看源码,加深理解。
Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下
标签:
原文地址:http://www.cnblogs.com/ywq-come/p/5927330.html