今天,我着重讲解下如下三个内容:
- measure过程
- WRAP_CONTENT、MATCH_PARENT/FILL_PARENT属性的原理说明
- 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的确切宽高,而不是由以上属性指定。
01.android:layout_weight="wrap_content" //自适应大小 02.android:layout_weight="match_parent" //与父视图等高 03.android:layout_weight="fill_parent" //与父视图等高 04.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
01.public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { 02. ... 03. /** 04. * The layout parameters associated with this view and used by the parent 05. * {@link android.view.ViewGroup} to determine how this view should be 06. * laid out. 07. * {@hide} 08. */ 09. //该View拥有的 LayoutParams属性,父试图添加该View时,会为其赋值,特别注意,其类型为ViewGroup.LayoutParams。 10. protected ViewGroup.LayoutParams mLayoutParams; 11. ... 12.} |
2.2、 ViewGroup.LayoutParams源码分析
路径位于:frameworks\base\core\java\android\view\ViewGroup.java
01.public abstract class ViewGroup extends View implements ViewParent, ViewManager { 02. ... 03. public static class LayoutParams { 04. /** 05. * Special value for the height or width requested by a View. 06. * FILL_PARENT means that the view wants to be as big as its parent, 07. * minus the parent's padding, if any. This value is deprecated 08. * starting in API Level 8 and replaced by {@link #MATCH_PARENT}. 09. */ 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. /** 54. * Creates a new set of layout parameters with the specified width 55. * and height. 56. */ 57. public LayoutParams(int width, int height) { 58. this.width = width; 59. this.height = height; 60. } 61. /** 62. * Copy constructor. Clones the width and height values of the source. 63. * 64. * @param source The layout params to copy from. 65. */ 66. public LayoutParams(LayoutParams source) { 67. this.width = source.width; 68. this.height = source.height; 69. } 70. /** 71. * Used internally by MarginLayoutParams. 72. * @hide 73. */ 74. LayoutParams() { 75. } 76. /** 77. * Extracts the layout parameters from the supplied attributes. 78. * 79. * @param a the style attributes to extract the parameters from 80. * @param widthAttr the identifier of the width attribute 81. * @param heightAttr the identifier of the height attribute 82. */ 83. protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { 84. width = a.getLayoutDimension(widthAttr, "layout_width"); 85. height = a.getLayoutDimension(heightAttr, "layout_height"); 86. } 87.} |
我们发现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
01.//Adds a child view. 02.void addView(View child, int index) 03.//Adds a child view with this ViewGroup's default layout parameters 04.//and the specified width and height. 05.void addView(View child, int width, int height) 06.//Adds a child view with the specified layout parameters. 07.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
01.public abstract class ViewGroup extends View implements ViewParent, ViewManager { 02. ... 03. /** 04. * Adds a child view. If no layout parameters are already set on the child, the 05. * default parameters for this ViewGroup are set on the child. 06. * 07. * @param child the child view to add 08. * 09. * @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. 86. if (!checkLayoutParams(params)) { //params对象是否为null 87. params = generateLayoutParams(params); //如果params对象是为null,重新构造个LayoutParams对象 88. } 89. //preventRequestLayout值为false 90. if (preventRequestLayout) { 91. child.mLayoutParams = params; //为View的mLayoutParams属性赋值 92. } else { 93. child.setLayoutParams(params);//为View的mLayoutParams属性赋值,但会调用requestLayout()请求重新布局 94. } 95. //if else 语句会设置View为mLayoutParams属性赋值 96. ... 97. } 98. ... 99.} |
主要功能就是在添加子View时为其构建了一个LayoutParams对象。但更重要的是,ViewGroup的子类可以重载上面的几个方法,返回特定的LayoutParams对象,例如:对于LinearLayout而言,则是LinearLayout.LayoutParams对象。这么做地目的是,能在其他需要它的地方,可以将其强制转换成LinearLayout.LayoutParams对象。
LinearLayout重写函数地实现为:
01.public class LinearLayout extends ViewGroup { 02. ... 03. @Override 04. public LayoutParams generateLayoutParams(AttributeSet attrs) { 05. return new LinearLayout.LayoutParams(getContext(), attrs); 06. } 07. @Override 08. protected LayoutParams generateDefaultLayoutParams() { 09. //该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. 51. a.recycle(); 52. } 53. /** 54. * {@inheritDoc} 55. */ 56. public LayoutParams(int width, int height) { 57. super(width, height); 58. weight = 0; 59. } 60. /** 61. * Creates a new set of layout parameters with the specified width, height 62. * and weight. 63. * 64. * @param width the width, either {@link #MATCH_PARENT}, 65. * {@link #WRAP_CONTENT} or a fixed size in pixels 66. * @param height the height, either {@link #MATCH_PARENT}, 67. * {@link #WRAP_CONTENT} or a fixed size in pixels 68. * @param weight the weight 69. */ 70. public LayoutParams(int width, int height, float weight) { 71. super(width, height); 72. this.weight = weight; 73. } 74. public LayoutParams(ViewGroup.LayoutParams p) { 75. super(p); 76. } 77. public LayoutParams(MarginLayoutParams source) { 78. super(source); 79. } 80. } 81. ... 82.} |
LinearLayout.LayoutParams类继承至ViewGroup.MarginLayoutParams类,添加了对android:layout_weight以及 android:layout_gravity这两个属性的获取和保存。而且它的重写函数返回的都是LinearLayout.LayoutParams类型。这样,我们可以再对子View进行其他操作时,可以将将其强制转换成LinearLayout.LayoutParams对象进行使用。
例如,LinearLayout进行measure过程,使用了LinearLayout.LayoutParam对象,有如下代码:
01.public class LinearLayout extends ViewGroup { 02. ... 03. @Override //onMeasure方法。 04. protected void onMea |