在之前一篇博文中<< Android中View绘制流程以及invalidate()等相关方法分析>> ,简单的阐述 了 Android View

绘制流程的三个步骤,即:

1、  measure过程 --- 测量过程

2、 layout 过程     --- 布局过程
                      3、 draw 过程      --- 绘制过程

要想对Android 中View这块深入理解,对这三个步骤地学习是必不可少的 。

今天,我着重讲解下如下三个内容:

1、 measure过程

2、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT属性的原理说明

3、xml布局文件解析成View树的流程分析。

希望对大家能有帮助。- -  分析版本基于Android 2.3

1、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT

初入Android殿堂的同学们,对这三个属性一定又爱又恨。爱的是使用起来挺爽地---照葫芦画瓢即可,恨的

却是时常混淆这几个属性地意义,需要三思而后行。在带着大家重温下这几个属性的用法吧(希望我没有啰嗦)。

这三个属性都用来适应视图的水平或垂直大小,一个以视图的内容或尺寸为基础的布局比精确地指定视图范围

更加方便。

①  fill_parent

设置一个视图的布局为fill_parent将强制性地使视图扩展至父元素大小。

② match_parent

Android 中match_parent和fill_parent意思一样,但match_parent更贴切,于是从2.2开始两个词都可以

用,但2.3版本后建议使用match_parent。

③ wrap_content

自适应大小,强制性地使视图扩展以便显示其全部内容。以TextView和ImageView控件为例,设置为

wrap_content将完整显示其内部的文本和图像。布局元素将根据内容更改大小。

可不要重复造轮子,以上摘自<<Android fill_parent、wrap_content和match_parent的区别>>。

当然,我们可以设置View的确切宽高,而不是由以上属性指定。

[java] view plaincopyprint?
  1. android:layout_weight="wrap_content"   //自适应大小
  2. android:layout_weight="match_parent"   //与父视图等高
  3. android:layout_weight="fill_parent"    //与父视图等高
  4. android:layout_weight="100dip"         //精确设置高度值为 100dip

接下来,我们需要转换下视角,看看ViewGroup.LayoutParams类及其派生类。

 2、ViewGroup.LayoutParams类及其派生类


2.1、  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。

路径:frameworks\base\core\java\android\view\View.java

[java] view plaincopyprint?
  1. public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
  2. ...
  3. /**
  4. * The layout parameters associated with this view and used by the parent
  5. * {@link android.view.ViewGroup} to determine how this view should be
  6. * laid out.
  7. * {@hide}
  8. */
  9. //该View拥有的 LayoutParams属性,父试图添加该View时,会为其赋值,特别注意,其类型为ViewGroup.LayoutParams。
  10. protected ViewGroup.LayoutParams mLayoutParams;
  11. ...
  12. }

2.2、  ViewGroup.LayoutParams源码分析

路径位于:frameworks\base\core\java\android\view\ViewGroup.java

[java] view plaincopyprint?
  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {
  2. ...
  3. public static class LayoutParams {
  4. /**
  5. * Special value for the height or width requested by a View.
  6. * FILL_PARENT means that the view wants to be as big as its parent,
  7. * minus the parent's padding, if any. This value is deprecated
  8. * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
  9. */
  10. @Deprecated
  11. public static final int FILL_PARENT = -1;  // 注意值为-1,Android2.2版本不建议使用
  12. /**
  13. * Special value for the height or width requested by a View.
  14. * MATCH_PARENT means that the view wants to be as big as its parent,
  15. * minus the parent's padding, if any. Introduced in API Level 8.
  16. */
  17. public static final int MATCH_PARENT = -1; // 注意值为-1
  18. /**
  19. * Special value for the height or width requested by a View.
  20. * WRAP_CONTENT means that the view wants to be just large enough to fit
  21. * its own internal content, taking its own padding into account.
  22. */
  23. public static final int WRAP_CONTENT = -2; // 注意值为-2
  24. /**
  25. * Information about how wide the view wants to be. Can be one of the
  26. * constants FILL_PARENT (replaced by MATCH_PARENT ,
  27. * in API Level 8) or WRAP_CONTENT. or an exact size.
  28. */
  29. public int width;  //该View的宽度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值
  30. /**
  31. * Information about how tall the view wants to be. Can be one of the
  32. * constants FILL_PARENT (replaced by MATCH_PARENT ,
  33. * in API Level 8) or WRAP_CONTENT. or an exact size.
  34. */
  35. public int height; //该View的高度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值
  36. /**
  37. * Used to animate layouts.
  38. */
  39. public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
  40. /**
  41. * Creates a new set of layout parameters. The values are extracted from
  42. * the supplied attributes set and context. The XML attributes mapped
  43. * to this set of layout parameters are:、
  44. */
  45. public LayoutParams(Context c, AttributeSet attrs) {
  46. TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
  47. setBaseAttributes(a,
  48. R.styleable.ViewGroup_Layout_layout_width,
  49. R.styleable.ViewGroup_Layout_layout_height);
  50. a.recycle();
  51. }
  52. /**
  53. * Creates a new set of layout parameters with the specified width
  54. * and height.
  55. */
  56. public LayoutParams(int width, int height) {
  57. this.width = width;
  58. this.height = height;
  59. }
  60. /**
  61. * Copy constructor. Clones the width and height values of the source.
  62. *
  63. * @param source The layout params to copy from.
  64. */
  65. public LayoutParams(LayoutParams source) {
  66. this.width = source.width;
  67. this.height = source.height;
  68. }
  69. /**
  70. * Used internally by MarginLayoutParams.
  71. * @hide
  72. */
  73. LayoutParams() {
  74. }
  75. /**
  76. * Extracts the layout parameters from the supplied attributes.
  77. *
  78. * @param a the style attributes to extract the parameters from
  79. * @param widthAttr the identifier of the width attribute
  80. * @param heightAttr the identifier of the height attribute
  81. */
  82. protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
  83. width = a.getLayoutDimension(widthAttr, "layout_width");
  84. height = a.getLayoutDimension(heightAttr, "layout_height");
  85. }
  86. }

我们发现FILL_PARENT/MATCH_PARENT值为 -1 ,WRAP_CONETENT值为-2,是不是有点诧异? 将值

设置为负值的目的是为了区别View的具体值(an exact size) 总是大于0的。

ViewGroup子类可以实现自定义LayoutParams,自定义LayoutParams提供了更好地扩展性,例如LinearLayout

就有LinearLayout. LayoutParams自定义类(见下文)。整个LayoutParams类家族还是挺复杂的。

ViewGroup.LayoutParams及其常用派生类的类图(部分类图)如下:

该类图是在太庞大了,大家有兴趣的去看看Android API吧。

前面我们说过,每个View都包含一个ViewGroup.LayoutParams类或者其派生类,下面我们的疑问是Android框架

中时如何为View设置其LayoutParams属性的。

有两种方法会设置View的LayoutParams属性:

 1、 直接添加子View时,常见于如下几种方法:ViewGroup.java

[java] view plaincopyprint?
  1. //Adds a child view.
  2. void addView(View child, int index)
  3. //Adds a child view with this ViewGroup's default layout parameters
  4. //and the specified width and height.
  5. void addView(View child, int width, int height)
  6. //Adds a child view with the specified layout parameters.
  7. void addView(View child, ViewGroup.LayoutParams params)

三个重载方法的区别只是添加View时构造LayoutParams对象的方式不同而已,稍后我们探寻一下它们的源码。

2、 通过xml布局文件指定某个View的属性为:android:layout_heigth=””以及android:layout_weight=”” 时。

总的来说,这两种方式都会设定View的LayoutParams属性值----指定的或者Default值。

方式1流程分析

直接添加子View时,比较容易理解,我们先来看看这种方式设置LayoutParams的过程:

路径:\frameworks\base\core\java\android\view\ViewGroup.java

[java] view plaincopyprint?
  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {
  2. ...
  3. /**
  4. * Adds a child view. If no layout parameters are already set on the child, the
  5. * default parameters for this ViewGroup are set on the child.
  6. *
  7. * @param child the child view to add
  8. *
  9. * @see #generateDefaultLayoutParams()
  10. */
  11. public void addView(View child) {
  12. addView(child, -1);
  13. }
  14. /**
  15. * Adds a child view. If no layout parameters are already set on the child, the
  16. * default parameters for this ViewGroup are set on the child.
  17. *
  18. * @param child the child view to add
  19. * @param index the position at which to add the child
  20. *
  21. * @see #generateDefaultLayoutParams()
  22. */
  23. public void addView(View child, int index) {
  24. LayoutParams params = child.getLayoutParams();
  25. if (params == null) {
  26. params = generateDefaultLayoutParams(); //返回默认地LayoutParams类,作为该View的属性值
  27. if (params == null) {//如果不能获取到LayoutParams对象,则抛出异常。
  28. throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
  29. }
  30. }
  31. addView(child, index, params);
  32. }
  33. /**
  34. * Adds a child view with this ViewGroup's default layout parameters and the
  35. * specified width and height.
  36. *
  37. * @param child the child view to add
  38. */
  39. public void addView(View child, int width, int height) {
  40. //返回默认地LayoutParams类,作为该View的属性值
  41. final LayoutParams params = generateDefaultLayoutParams();
  42. params.width = width;   //重新设置width值
  43. params.height = height; //重新设置height值
  44. addView(child, -1, params); //这儿,我们有指定width、height的大小了。
  45. }
  46. /**
  47. * Adds a child view with the specified layout parameters.
  48. *
  49. * @param child the child view to add
  50. * @param params the layout parameters to set on the child
  51. */
  52. public void addView(View child, LayoutParams params) {
  53. addView(child, -1, params);
  54. }
  55. /**
  56. * Adds a child view with the specified layout parameters.
  57. *
  58. * @param child the child view to add
  59. * @param index the position at which to add the child
  60. * @param params the layout parameters to set on the child
  61. */
  62. public void addView(View child, int index, LayoutParams params) {
  63. ...
  64. // addViewInner() will call child.requestLayout() when setting the new LayoutParams
  65. // therefore, we call requestLayout() on ourselves before, so that the child's request
  66. // will be blocked at our level
  67. requestLayout();
  68. invalidate();
  69. addViewInner(child, index, params, false);
  70. }
  71. /**
  72. * Returns a set of default layout parameters. These parameters are requested
  73. * when the View passed to {@link #addView(View)} has no layout parameters
  74. * already set. If null is returned, an exception is thrown from addView.
  75. *
  76. * @return a set of default layout parameters or null
  77. */
  78. protected LayoutParams generateDefaultLayoutParams() {
  79. //width 为 WRAP_CONTENT大小 , height 为WRAP_CONTENT
  80. //ViewGroup的子类可以重写该方法,达到其特定要求。稍后会以LinearLayout类为例说明。
  81. return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  82. }
  83. private void addViewInner(View child, int index, LayoutParams params,
  84. boolean preventRequestLayout) {
  85. if (!checkLayoutParams(params)) { //params对象是否为null
  86. params = generateLayoutParams(params); //如果params对象是为null,重新构造个LayoutParams对象
  87. }
  88. //preventRequestLayout值为false
  89. if (preventRequestLayout) {
  90. child.mLayoutParams = params; //为View的mLayoutParams属性赋值
  91. } else {
  92. child.setLayoutParams(params);//为View的mLayoutParams属性赋值,但会调用requestLayout()请求重新布局
  93. }
  94. //if else 语句会设置View为mLayoutParams属性赋值
  95. ...
  96. }
  97. ...
  98. }

主要功能就是在添加子View时为其构建了一个LayoutParams对象。但更重要的是,ViewGroup的子类可以重载

上面的几个方法,返回特定的LayoutParams对象,例如:对于LinearLayout而言,则是LinearLayout.LayoutParams

对象。这么做地目的是,能在其他需要它的地方,可以将其强制转换成LinearLayout.LayoutParams对象。

LinearLayout重写函数地实现为:

[java] view plaincopyprint?
  1. public class LinearLayout extends ViewGroup {
  2. ...
  3. @Override
  4. public LayoutParams generateLayoutParams(AttributeSet attrs) {
  5. return new LinearLayout.LayoutParams(getContext(), attrs);
  6. }
  7. @Override
  8. protected LayoutParams generateDefaultLayoutParams() {
  9. //该LinearLayout是水平方向还是垂直方向
  10. if (mOrientation == HORIZONTAL) {
  11. return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  12. } else if (mOrientation == VERTICAL) {
  13. return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
  14. }
  15. return null;
  16. }
  17. @Override
  18. protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
  19. return new LayoutParams(p);
  20. }
  21. /**
  22. * Per-child layout information associated with ViewLinearLayout.
  23. *
  24. * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
  25. * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
  26. */ //自定义的LayoutParams类
  27. public static class LayoutParams extends ViewGroup.MarginLayoutParams {
  28. /**
  29. * Indicates how much of the extra space in the LinearLayout will be
  30. * allocated to the view associated with these LayoutParams. Specify
  31. * 0 if the view should not be stretched. Otherwise the extra pixels
  32. * will be pro-rated among all views whose weight is greater than 0.
  33. */
  34. @ViewDebug.ExportedProperty(category = "layout")
  35. public float weight;      //  见于属性,android:layout_weight=""  ;
  36. /**
  37. * Gravity for the view associated with these LayoutParams.
  38. *
  39. * @see android.view.Gravity
  40. */
  41. public int gravity = -1;  // 见于属性, android:layout_gravity=""  ;
  42. /**
  43. * {@inheritDoc}
  44. */
  45. public LayoutParams(Context c, AttributeSet attrs) {
  46. super(c, attrs);
  47. TypedArray a =c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
  48. weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
  49. gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
  50. a.recycle();
  51. }
  52. /**
  53. * {@inheritDoc}
  54. */
  55. public LayoutParams(int width, int height) {
  56. super(width, height);
  57. weight = 0;
  58. }
  59. /**
  60. * Creates a new set of layout parameters with the specified width, height
  61. * and weight.
  62. *
  63. * @param width the width, either {@link #MATCH_PARENT},
  64. *        {@link #WRAP_CONTENT} or a fixed size in pixels
  65. * @param height the height, either {@link #MATCH_PARENT},
  66. *        {@link #WRAP_CONTENT} or a fixed size in pixels
  67. * @param weight the weight
  68. */
  69. public LayoutParams(int width, int height, float weight) {
  70. super(width, height);
  71. this.weight = weight;
  72. }
  73. public LayoutParams(ViewGroup.LayoutParams p) {
  74. super(p);
  75. }
  76. public LayoutParams(MarginLayoutParams source) {
  77. super(source);
  78. }
  79. }
  80. ...
  81. }

LinearLayout.LayoutParams类继承至ViewGroup.MarginLayoutParams类,添加了对android:layout_weight以及

android:layout_gravity这两个属性的获取和保存。而且它的重写函数返回的都是LinearLayout.LayoutParams

类型。这样,我们可以再对子View进行其他操作时,可以将将其强制转换成LinearLayout.LayoutParams对象进行

使用。

例如,LinearLayout进行measure过程,使用了LinearLayout.LayoutParam对象,有如下代码:

[java] view plaincopyprint?
  1. public class LinearLayout extends ViewGroup {
  2. ...
  3. @Override  //onMeasure方法。
  4. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  5. //判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向,
  6. if (mOrientation == VERTICAL) {
  7. measureVertical(widthMeasureSpec, heightMeasureSpec);
  8. } else {
  9. measureHorizontal(widthMeasureSpec, heightMeasureSpec);
  10. }
  11. }
  12. /**
  13. * Measures the children when the orientation of this LinearLayout is set
  14. * to {@link #VERTICAL}.
  15. *
  16. * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
  17. * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
  18. *
  19. * @see #getOrientation()
  20. * @see #setOrientation(int)
  21. * @see #onMeasure(int, int)
  22. */
  23. void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
  24. mTotalLength = 0;
  25. ...
  26. // See how tall everyone is. Also remember max width.
  27. for (int i = 0; i < count; ++i) {
  28. final View child = getVirtualChildAt(i); //获得索引处为i的子VIew
  29. ...
  30. //注意,我们将类型为 ViewGroup.LayoutParams的实例对象强制转换为了LinearLayout.LayoutParams,
  31. //即父对象转换为了子对象,能这样做的原因就是LinearLayout的所有子View的LayoutParams类型都为
  32. //LinearLayout.LayoutParams
  33. LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
  34. ...
  35. }
  36. ...
  37. }

超类ViewGroup.LayoutParams强制转换为了子类LinearLayout.LayoutParams,因为LinearLayout的每个

”直接“子View的LayoutParams属性都是LinearLayout.LayoutParams类型,因此可以安全转换。

PS : Android 2.3源码Launcher2中也实现了自定义的LayoutParams类,在IDLE界面的每个View至少包含如下

信息:所在X方向的单元格索引和高度、所在Y方向的单元格索引和高度等。

路径: packages\apps\Launcher2\src\com\android\launcher2\CellLayout.java

[java] view plaincopyprint?
  1. public class CellLayout extends ViewGroup {
  2. ...
  3. public static class LayoutParams extends ViewGroup.MarginLayoutParams {
  4. /**
  5. * Horizontal location of the item in the grid.
  6. */
  7. public int cellX;   //X方向的单元格索引
  8. /**
  9. * Vertical location of the item in the grid.
  10. */
  11. public int cellY;   //Y方向的单元格索引
  12. /**
  13. * Number of cells spanned horizontally by the item.
  14. */
  15. public int cellHSpan;  //水平方向所占高度
  16. /**
  17. * Number of cells spanned vertically by the item.
  18. */
  19. public int cellVSpan;  //垂直方向所占高度
  20. ...
  21. public LayoutParams(Context c, AttributeSet attrs) {
  22. super(c, attrs);
  23. cellHSpan = 1;  //默认为高度 1
  24. cellVSpan = 1;
  25. }
  26. public LayoutParams(ViewGroup.LayoutParams source) {
  27. super(source); //默认为高度 1
  28. cellHSpan = 1;
  29. cellVSpan = 1;
  30. }
  31. public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
  32. super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  33. this.cellX = cellX;
  34. this.cellY = cellY;
  35. this.cellHSpan = cellHSpan;
  36. this.cellVSpan = cellVSpan;
  37. }
  38. ...
  39. }
  40. ...
  41. }

对该自定义CellLayout.LayoutParams类的使用可以参考LinearLayout.LayoutParams类,我也不再赘述了。

方法2流程分析

使用属性android:layout_heigth=””以及android:layout_weight=”” 时,为某个View设置LayoutParams值。

其实这种赋值方法其实也如同前面那种,只不过它需要一个前期孵化过程---需要利用XML解析将布局文件

解析成一个完整的View树,可别小看它了,所有Xxx.xml的布局文件都需要解析成一个完整的View树。下面,

我们就来仔细走这个过程,重点关注如下两个方面

①、xml布局是如何解析成View树的 ;

②、android:layout_heigth=””和android:layout_weight=””的解析。

PS: 一直以来,我都想当然android:layout_heigth以及android:layout_weight这两个属性的解析过程是在

View.java内部完成的,但当我真正去找寻时,却一直没有在View.java类或者ViewGroup.java类找到。直到一位

网友的一次提问,才发现它们的藏身之地。

3、布局文件解析流程分析


解析布局文件时,使用的类为LayoutInflater。 关于该类的使用请参考如下博客:

<android中LayoutInflater的使用 >>

主要有如下API方法:

public View inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot)

public View inflate (int resource, ViewGroup root)

public View inflate (int resource, ViewGroup root, boolean attachToRoot)

这三个类主要迷惑之处在于地三个参数attachToRoot,即是否将该View树添加到root中去。具体可看这篇博客:

<<关于inflate的第3个参数>>

当然还有LayoutInflater的inflate()的其他重载方法,大家可以自行了解下。

我利用下面的例子给大家走走这个流程 :

[java] view plaincopyprint?
  1. public class MainActivity extends Activity {
  2. /** Called when the activity is first created. */
  3. @Override
  4. public void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. //1、该方法最终也会调用到 LayoutInflater的inflate()方法中去解析。
  7. setContentView(R.layout.main);
  8. //2、使用常见的API方法去解析xml布局文件,
  9. LayoutInflater layoutInflater = (LayoutInflater)getSystemService();
  10. View root = layoutInflater.inflate(R.layout.main, null);
  11. }
  12. }

Step 1、获得LayoutInflater的引用。

路径:\frameworks\base\core\java\android\app\ContextImpl.java

[java] view plaincopyprint?
  1. /**
  2. * Common implementation of Context API, which provides the base
  3. * context object for Activity and other application components.
  4. */
  5. class ContextImpl extends Context {
  6. if (WINDOW_SERVICE.equals(name)) {
  7. return WindowManagerImpl.getDefault();
  8. } else if (LAYOUT_INFLATER_SERVICE.equals(name)) {
  9. synchronized (mSync) {
  10. LayoutInflater inflater = mLayoutInflater;
  11. //是否已经赋值,如果是,直接返回引用
  12. if (inflater != null) {
  13. return inflater;
  14. }
  15. //返回一个LayoutInflater对象,getOuterContext()指的是我们的Activity、Service或者Application引用
  16. mLayoutInflater = inflater = PolicyManager.makeNewLayoutInflater(getOuterContext());
  17. return inflater;
  18. }
  19. } else if (ACTIVITY_SERVICE.equals(name)) {
  20. return getActivityManager();
  21. }...
  22. }

继续去PolicyManager查询对应函数,看看内部实现。

路径:frameworks\base\core\java\com\android\internal\policy\PolicyManager.java

[java] view plaincopyprint?
  1. public final class PolicyManager {
  2. private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy";
  3. private static final IPolicy sPolicy;   // 这可不是Binder机制额,这只是是一个接口,别想多啦
  4. static {
  5. // Pull in the actual implementation of the policy at run-time
  6. try {
  7. Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
  8. sPolicy = (IPolicy)policyClass.newInstance();
  9. }
  10. ...
  11. }
  12. ...
  13. public static LayoutInflater makeNewLayoutInflater(Context context) {
  14. return sPolicy.makeNewLayoutInflater(context); //继续去实现类中去查找
  15. }
  16. }

IPolicy接口的实现对为Policy类。路径:/frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java

[java] view plaincopyprint?
  1. //Simple implementation of the policy interface that spawns the right
  2. //set of objects
  3. public class Policy implements IPolicy{
  4. ...
  5. public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
  6. //实际上返回的是PhoneLayoutInflater类。
  7. return new PhoneLayoutInflater(context);
  8. }
  9. }
  10. //PhoneLayoutInflater继承至LayoutInflater类
  11. public class PhoneLayoutInflater extends LayoutInflater {
  12. ...
  13. /**
  14. * Instead of instantiating directly, you should retrieve an instance
  15. * through {@link Context#getSystemService}
  16. *
  17. * @param context The Context in which in which to find resources and other
  18. *                application-specific things.
  19. *
  20. * @see Context#getSystemService
  21. */
  22. public PhoneLayoutInflater(Context context) {
  23. super(context);
  24. }
  25. ...
  26. }

LayoutInflater是个抽象类,实际上我们返回的是PhoneLayoutInflater类,但解析过程的操作基本上是在

LayoutInflater中完成地。

Step 2、调用inflate()方法去解析布局文件。

[java] view plaincopyprint?
  1. public abstract class LayoutInflater {
  2. ...
  3. public View inflate(int resource, ViewGroup root) {
  4. //继续看下个函数,注意root为null
  5. return inflate(resource, root, root != null);
  6. }
  7. public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
  8. //获取一个XmlResourceParser来解析XML文件---布局文件。
  9. //XmlResourceParser类以及xml是如何解析的,大家自己有兴趣找找。
  10. XmlResourceParser parser = getContext().getResources().getLayout(resource);
  11. try {
  12. return inflate(parser, root, attachToRoot);
  13. } finally {
  14. parser.close();
  15. }
  16. }
  17. }
  18. /**
  19. * The XML parsing interface returned for an XML resource.  This is a standard
  20. * XmlPullParser interface, as well as an extended AttributeSet interface and
  21. * an additional close() method on this interface for the client to indicate
  22. * when it is done reading the resource.
  23. */
  24. public interface XmlResourceParser extends XmlPullParser, AttributeSet {
  25. /**
  26. * Close this interface to the resource.  Calls on the interface are no
  27. * longer value after this call.
  28. */
  29. public void close();
  30. }

我们获得了一个当前应用程序环境的XmlResourceParser对象,该对象的主要作用就是来解析xml布局文件的。

XmlResourceParser类是个接口类,更多关于XML解析的,大家可以参考下面博客:

<<android之XmlResourceParser类使用实例>>

<<android解析xml文件的方式(其一)>>

<<android解析xml文件的方式(其二)>>

<<android解析xml文件的方式(其三)>>

Step 3 、真正地开始解析工作 。

[java] view plaincopyprint?
  1. public abstract class LayoutInflater {
  2. ...
  3. /**
  4. * Inflate a new view hierarchy from the specified XML node. Throws
  5. * {@link InflateException} if there is an error.
  6. */
  7. //我们传递过来的参数如下: root 为null , attachToRoot为false 。
  8. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  9. synchronized (mConstructorArgs) {
  10. final AttributeSet attrs = Xml.asAttributeSet(parser);
  11. Context lastContext = (Context)mConstructorArgs[0];
  12. mConstructorArgs[0] = mContext;  //该mConstructorArgs属性最后会作为参数传递给View的构造函数
  13. View result = root;  //根View
  14. try {
  15. // Look for the root node.
  16. int type;
  17. while ((type = parser.next()) != XmlPullParser.START_TAG &&
  18. type != XmlPullParser.END_DOCUMENT) {
  19. // Empty
  20. }
  21. ...
  22. final String name = parser.getName();  //节点名,即API中的控件或者自定义View完整限定名。
  23. if (TAG_MERGE.equals(name)) { // 处理<merge />标签
  24. if (root == null || !attachToRoot) {
  25. throw new InflateException("<merge /> can be used only with a valid "
  26. + "ViewGroup root and attachToRoot=true");
  27. }
  28. //将<merge />标签的View树添加至root中,该函数稍后讲到。
  29. rInflate(parser, root, attrs);
  30. } else {
  31. // Temp is the root view that was found in the xml
  32. //创建该xml布局文件所对应的根View。
  33. View temp = createViewFromTag(name, attrs);
  34. ViewGroup.LayoutParams params = null;
  35. if (root != null) {
  36. // Create layout params that match root, if supplied
  37. //根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
  38. params = root.generateLayoutParams(attrs);
  39. if (!attachToRoot) { //重新设置temp的LayoutParams
  40. // Set the layout params for temp if we are not
  41. // attaching. (If we are, we use addView, below)
  42. temp.setLayoutParams(params);
  43. }
  44. }
  45. // Inflate all children under temp
  46. //添加所有其子节点,即添加所有字View
  47. rInflate(parser, temp, attrs);
  48. // We are supposed to attach all the views we found (int temp)
  49. // to root. Do that now.
  50. if (root != null && attachToRoot) {
  51. root.addView(temp, params);
  52. }
  53. // Decide whether to return the root that was passed in or the
  54. // top view found in xml.
  55. if (root == null || !attachToRoot) {
  56. result = temp;
  57. }
  58. }
  59. }
  60. ...
  61. return result;
  62. }
  63. }
  64. /*
  65. * default visibility so the BridgeInflater can override it.
  66. */
  67. View createViewFromTag(String name, AttributeSet attrs) {
  68. //节点是否为View,如果是将其重新赋值,形如 <View class="com.qin.xxxView"></View>
  69. if (name.equals("view")) {
  70. name = attrs.getAttributeValue(null, "class");
  71. }
  72. try {
  73. View view = (mFactory == null) ? null : mFactory.onCreateView(name,
  74. mContext, attrs);  //没有设置工厂方法
  75. if (view == null) {
  76. //通过这个判断是Android API的View,还是自定义View
  77. if (-1 == name.indexOf('.')) {
  78. view = onCreateView(name, attrs); //创建Android API的View实例
  79. } else {
  80. view = createView(name, null, attrs);//创建一个自定义View实例
  81. }
  82. }
  83. return view;
  84. }
  85. ...
  86. }
  87. //获得具体视图的实例对象
  88. public final View createView(String name, String prefix, AttributeSet attrs) {
  89. Constructor constructor = sConstructorMap.get(name);
  90. Class clazz = null;
  91. //以下功能主要是获取如下三个类对象:
  92. //1、类加载器  ClassLoader
  93. //2、Class对象
  94. //3、类的构造方法句柄 Constructor
  95. try {
  96. if (constructor == null) {
  97. // Class not found in the cache, see if it's real, and try to add it
  98. clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name);
  99. ...
  100. constructor = clazz.getConstructor(mConstructorSignature);
  101. sConstructorMap.put(name, constructor);
  102. } else {
  103. // If we have a filter, apply it to cached constructor
  104. if (mFilter != null) {
  105. ...
  106. }
  107. }
  108. //传递参数获得该View实例对象
  109. Object[] args = mConstructorArgs;
  110. args[1] = attrs;
  111. return (View) constructor.newInstance(args);
  112. }
  113. ...
  114. }
  115. }

这段代码的作用是获取xml布局文件的root View,做了如下两件事情

1、获取xml布局的View实例,通过createViewFromTag()方法获取,该方法会判断节点名是API 控件

还是自定义控件,继而调用合适的方法去实例化View。

2、判断root以及attachToRoot参数,重新设置root View值以及temp变量的LayoutParams值。

如果仔细看着段代码,不知大家心里有没有疑惑:当root为null时,我们的temp变量的LayoutParams值是为

null的,即它不会被赋值?有个View的LayoutParams值为空,那么,在系统中不会报异常吗?见下面部分

代码:

[java] view plaincopyprint?
  1. //我们传递过来的参数如下: root 为null , attachToRoot为false 。
  2. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  3. synchronized (mConstructorArgs) {
  4. ...
  5. try {
  6. ...
  7. if (TAG_MERGE.equals(name)) { // 处理<merge />标签
  8. ...
  9. } else {
  10. // Temp is the root view that was found in the xml
  11. //创建该xml布局文件所对应的根View。
  12. View temp = createViewFromTag(name, attrs);
  13. ViewGroup.LayoutParams params = null;
  14. //注意!!! root为null时,temp变量的LayoutParams属性不会被赋值的。
  15. if (root != null) {
  16. // Create layout params that match root, if supplied
  17. //根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
  18. params = root.generateLayoutParams(attrs);
  19. if (!attachToRoot) { //重新设置temp的LayoutParams
  20. // Set the layout params for temp if we are not
  21. // attaching. (If we are, we use addView, below)
  22. temp.setLayoutParams(params);
  23. }
  24. }
  25. ...
  26. }
  27. }
  28. ...
  29. }
  30. }

关于这个问题的详细答案,我会在后面讲到。这儿我简单说下,任何View树的顶层View被添加至窗口时,

一般调用WindowManager.addView()添加至窗口时,在这个方法中去做进一步处理。即使,LayoutParams

值为空,UI框架每次measure()时都忽略该View的LayoutParams值,而是直接传递MeasureSpec值至View树。

接下来,我们关注另外一个函数,rInflate(),该方法会递归调用每个View下的子节点,以当前View作为根View

形成一个View树。

[java] view plaincopyprint?
  1. /**
  2. * Recursive method used to descend down the xml hierarchy and instantiate
  3. * views, instantiate their children, and then call onFinishInflate().
  4. */
  5. //递归调用每个字节点
  6. private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
  7. throws XmlPullParserException, IOException {
  8. final int depth = parser.getDepth();
  9. int type;
  10. while (((type = parser.next()) != XmlPullParser.END_TAG ||
  11. parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
  12. if (type != XmlPullParser.START_TAG) {
  13. continue;
  14. }
  15. final String name = parser.getName();
  16. if (TAG_REQUEST_FOCUS.equals(name)) { //处理<requestFocus />标签
  17. parseRequestFocus(parser, parent);
  18. } else if (TAG_INCLUDE.equals(name)) { //处理<include />标签
  19. if (parser.getDepth() == 0) {
  20. throw new InflateException("<include /> cannot be the root element");
  21. }
  22. parseInclude(parser, parent, attrs);//解析<include />节点
  23. } else if (TAG_MERGE.equals(name)) { //处理<merge />标签
  24. throw new InflateException("<merge /> must be the root element");
  25. } else {
  26. //根据节点名构建一个View实例对象
  27. final View view = createViewFromTag(name, attrs);
  28. final ViewGroup viewGroup = (ViewGroup) parent;
  29. //调用generateLayoutParams()方法返回一个LayoutParams实例对象,
  30. final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
  31. rInflate(parser, view, attrs); //继续递归调用
  32. viewGroup.addView(view, params); //OK,将该View以特定LayoutParams值添加至父View中
  33. }
  34. }
  35. parent.onFinishInflate();  //完成了解析过程,通知....
  36. }

值得注意的是,每次addView前都调用了viewGroup.generateLayoutParams(attrs)去构建一个LayoutParams

实例,然后在addView()方法中为其赋值。参见如下代码:ViewGroup.java

[java] view plaincopyprint?
  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {
  2. ...
  3. public LayoutParams generateLayoutParams(AttributeSet attrs) {
  4. return new LayoutParams(getContext(), attrs);
  5. }
  6. public static class LayoutParams {
  7. ... //会调用这个构造函数
  8. public LayoutParams(Context c, AttributeSet attrs) {
  9. TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
  10. setBaseAttributes(a,
  11. R.styleable.ViewGroup_Layout_layout_width,
  12. R.styleable.ViewGroup_Layout_layout_height);
  13. a.recycle();
  14. }
  15. protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
  16. width = a.getLayoutDimension(widthAttr, "layout_width");
  17. height = a.getLayoutDimension(heightAttr, "layout_height");
  18. }
  19. }

好吧 ~~ 我们还是探寻根底,去TypeArray类的getLayoutDimension()看看。

路径:/frameworks/base/core/java/android/content/res/TypedArray.java

[java] view plaincopyprint?
  1. public class TypedArray {
  2. ...
  3. /**
  4. * Special version of {@link #getDimensionPixelSize} for retrieving
  5. * {@link android.view.ViewGroup}'s layout_width and layout_height
  6. * attributes.  This is only here for performance reasons; applications
  7. * should use {@link #getDimensionPixelSize}.
  8. *
  9. * @param index Index of the attribute to retrieve.
  10. * @param name Textual name of attribute for error reporting.
  11. *
  12. * @return Attribute dimension value multiplied by the appropriate
  13. * metric and truncated to integer pixels.
  14. */
  15. public int getLayoutDimension(int index, String name) {
  16. index *= AssetManager.STYLE_NUM_ENTRIES;
  17. final int[] data = mData;
  18. //获得属性对应的标识符 , Identifies,目前还没有仔细研究相关类。
  19. final int type = data[index+AssetManager.STYLE_TYPE];
  20. if (type >= TypedValue.TYPE_FIRST_INT
  21. && type <= TypedValue.TYPE_LAST_INT) {
  22. return data[index+AssetManager.STYLE_DATA];
  23. } else if (type == TypedValue.TYPE_DIMENSION) { //类型为dimension类型
  24. return TypedValue.complexToDimensionPixelSize(
  25. data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
  26. }
  27. //没有提供layout_weight和layout_height会来到此处 ,这儿会报异常!
  28. //因此布局文件中的View包括自定义View必须加上属性layout_weight和layout_height。
  29. throw new RuntimeException(getPositionDescription()
  30. + ": You must supply a " + name + " attribute.");
  31. }
  32. ...
  33. }

从上面得知,   我们将View的AttributeSet属性传递给generateLayoutParams()方法,让其构建合适地

LayoutParams对象,并且初始化属性值weight和height。同时我们也得知 布局文件中的View包括自定义View

必须加上属性layout_weight和layout_height,否则会报异常。

Step 3 主要做了如下事情:
       首先,获得了了布局文件地root View,即布局文件中最顶层的View。

其次,通过递归调用,我们形成了整个View树以及设置了每个View的LayoutParams对象。

总结:通过对布局文件的解析流程的学习,也就是转换为View树的过程,我们明白了解析过程的个中奥妙,以及

设置ViewLayoutParams对象的过程。但是,我们这儿只是简单的浮光掠影,更深层次的内容希望大家能深入学习。

本来是准备接下去往下写的,但无奈贴出来的代码太多,文章有点长而且自己也有点凌乱了,因此决定做两篇

博客发表吧。下篇内容包括如下方面:

1、MeasureSpec类说明 ;

2、measure过程中如何正确设置每个View的长宽 ;

3、UI框架正确设置顶层View的LayoutParams对象,对Activity而言,顶层View则是DecorView,

其他的皆是普通View了。

Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)相关推荐

  1. Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)

       本文原创, 转载请注明出处:http://blog.csdn.net/qinjuning 上篇文章<<Android中measure过程.WRAP_CONTENT详解以及xml布局文 ...

  2. Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析

    转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...

  3. android studio parcelable,Android中Parcelable的使用详解

    Parcelable与Serializable Serializable是Java为我们提供的一个标准化的序列化接口. Parcelable是Android为我们提供的序列化的接口. 对比: 1.Pa ...

  4. Android中的四大组件详解

    Android中的四大组件详解 我们都知道Android系统应用层框架中,为开发者提供了四大组件来便于应用的开发,它们是Activity.Service.BroadcastReceiver.Conte ...

  5. Android中LayoutParams类精炼详解

    一.前期基础知识储备 首先看几个使用LayoutParams的实例: 1.<Android开发艺术探索>第8章,Java代码中动态设置按钮时 通过LayoutParams参数设置按钮位置x ...

  6. RxJava操作符在android中的使用场景详解(一)

    转载请注明出处:http://www.wangxinarhat.com/2016/04/19/2016-04-19-rxjava-android-operate1/ 最近学习了RxJava在andro ...

  7. Android中图片压缩方案详解

    如感觉排版不舒服,可移步至此处查看 图片的展示可以说在我们任何一个应用中都避免不了,可是大量的图片就会出现很多的问题,比如加载大图片或者多图时的OOM问题,可以移步到Android高效加载大图.多图避 ...

  8. Android中的windowSoftInputMode属性详解

    如何实现软键盘不自动弹出,使用的方法是设置android:windowSoftInputMode属性.那么,这个属性到底是干什么的,他有什么作用呢?今天这篇文章,就是探索android:windowS ...

  9. android token机制_对Android 中的 ANR 进行详解

    前言 关于ANR,以前只知道Activity.BroadCastReceiver.Service三种组件的ANR时限.一般采用哪些方式避免ANR.以及通过data/anr/traces.txt去分析A ...

最新文章

  1. 不删除文件,清空文件内容命令
  2. 如何查看tensorflow源代码
  3. wxWidgets:Sizer 概览
  4. 运行pip报错:Fatal error in launcher: Unable to create process using '’路径’'
  5. windows下面的txt在linux下面显示为乱码
  6. java忽略引号中的分隔符_java – 令牌化但忽略引号内的分隔符
  7. windows下环境变量配置后没生效,不重启电脑的解决办法
  8. Bootcamp Mac 安装Win10 教程
  9. 961计算机组成原理,2017年华中科技大学附属协和医院961计算机组成原理考研强化模拟题...
  10. 霍夫曼编码PHP,数据结构:哈夫曼编码(php版)
  11. 科技驱动未来:飞康如何赢得尤尼克斯的青睐?
  12. Ubuntu 16.04 安装opencv3及其扩展模块
  13. 【锂知道】锂电池基本原理解析:充电及放电机制
  14. ESXi5安装vib格式驱动
  15. linux下安装mysql问题:mysqld_safe mysqld from pid file /usr/local/mysql/data/mysql.pid ended
  16. 【转贴】龙芯生态产品和解决方案巡展(第二篇)——笔记本电脑
  17. 全国智能制造(中国制造2025)创新创业大赛华北赛区决赛完美收官
  18. APP被苹果App Store拒绝的N个原因
  19. 如何在 Mac 上强制退出应用
  20. net stop mysql 发生系统错误5

热门文章

  1. 【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )
  2. 【错误记录】Android Studio 编译报错 ( Invalid Gradle JDK configuration found )
  3. 【计算理论】计算理论总结 ( 泵引理 Pumping 证明 ) ★★
  4. 【Android 性能优化】应用启动优化 ( 安卓应用启动分析 | Launcher 应用启用普通安卓应用 | 应用进程分析 )
  5. 【Android NDK 开发】JNI 动态注册 ( 动态注册流程 | JNI_OnLoad 方法 | JNINativeMethod 结构体 | GetEnv | RegisterNatives )
  6. python -yield理解
  7. 使对象具有ES6中Iterator接口的实现方法
  8. (剑指Offer)面试题19:二叉树的镜像
  9. 网络游戏服务器端架构设计(转载)
  10. 使用Nginx的proxy_cache缓存功能取代Squid[原创]