View MeasureSpec 和LayoutParams关系

来源:互联网 发布:移动硬盘 知乎 编辑:程序博客网 时间:2024/04/30 12:48

View MeasureSpec 和LayoutParams关系

系统内部是通过MeasureSpec来给View 进行测量工作的,但是我们实际却是只用LayoutParams来设置的.这里我们就是分析2者直接的联系.
其实View在测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后根据这个MeasureSpec来确定View测量之后的高和宽.也就是说MeasureSpec不是由LayoutParams唯一确定的,而是要和父容器一起来确定的.而且普通的View和页面的顶级View(DecorView)的MeasureSpec的转换过程是有些不同的.
DecorView的MeasureSpec是由窗口的尺寸和DecorView其自身的LayoutParams来共同决定的.
普通View的MeasureSpec是由父容器的MeasureSpec和普通View自身的LayoutParams来共同决定的.

在DecorView的measureHierarchy方法就是它的MeasureSpec创建的过程.
ViewRootImpl.java(这里删除了部分log和暂时不用关注的代码)
  1. private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
  2. final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
  3. int childWidthMeasureSpec;
  4. int childHeightMeasureSpec;
  5. boolean windowSizeMayChange = false;
  6. boolean goodMeasure = false;
  7. if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {//这个情况是弹窗,普通的页面窗口不是wrap content
  8. //..........
  9. }
  10. if (!goodMeasure) {//
  11. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
  12. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
  13. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  14. if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
  15. windowSizeMayChange = true;
  16. }
  17. }
  18. return windowSizeMayChange;
  19. }
上面的代码中childHeightMeasureSpec和childWidthMeasureSpec就是屏幕的尺寸lp就是DecorView的参数.所以我们需要现在看看getRootMeasureSpec方法
ViewRootImpl.java
  1. private static int getRootMeasureSpec(int windowSize, int rootDimension) {
  2. int measureSpec;
  3. switch (rootDimension) {
  4. case ViewGroup.LayoutParams.MATCH_PARENT:
  5. // Window can't resize. Force root view to be windowSize.
  6. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  7. break;
  8. case ViewGroup.LayoutParams.WRAP_CONTENT:
  9. // Window can resize. Set max size for root view.
  10. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  11. break;
  12. default:
  13. // Window wants to be an exact size. Force root view to be that size.
  14. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  15. break;
  16. }
  17. return measureSpec;
  18. }
根据上面的getRootMeasureSpec()可以知道:
如果DecorView的LayoutParams是match parent模式,窗口的大小就是DecorView的大小.
如果DecorView的LayoutParams是wrap content模式,DecorView就是最大状态,但是具体大小没有确定(不能超过窗口大小)
如果DecorView的LayoutParams是具体的大小,比如多少像素之类的,那么DecorView 就是它LayoutParams中的实际大小.
同时, DecorView的LayoutParams是match parent或者具体数值(dp,px)的时候,其直接的子View会是精确模式(EXACTLY),
         DecorView的LayoutParams是warp content的时候,其直接的子View会是最大模式(AT_MOST)
measureHierarchy获取DecorView的MeasureSpec之后就是通过performMeasure()来执行
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec) ,这样就是调用DecorView的自己的measure()方法.关于measure()方法这里暂时不分析.


对普通的View来说,它的measure过程是由它的ViewGroup在measureChildWithMargins()来调用的
ViewGroup.java
  1. protected void measureChildWithMargins(View child,
  2. int parentWidthMeasureSpec, int widthUsed,
  3. int parentHeightMeasureSpec, int heightUsed) {
  4. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  5. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  6. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
  7. + widthUsed, lp.width);
  8. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  9. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  10. + heightUsed, lp.height);
  11. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  12. }
对ViewGroup的每个child 都会被执行上面的measureChildWithMargins方法,先使用父容器ViewGroup的MeasureSpec和child view 自己的LayoutParams来获取child view自己的MeasureSpec,在调用child view自己本身的measure(). 
measure()方法后续分析.
上面的是使用getChildMeasureSpec来获取child view 的MeasureSpec的,下面我们分析一下这个方法.
ViewGroup.java
  1. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  2. int specMode = MeasureSpec.getMode(spec);
  3. int specSize = MeasureSpec.getSize(spec);
  4. int size = Math.max(0, specSize - padding);//父容器可用的大小
  5. int resultSize = 0;
  6. int resultMode = 0;
  7. switch (specMode) {
  8. // Parent has imposed an exact size on us
  9. case MeasureSpec.EXACTLY:
  10. if (childDimension >= 0) {//view 是固定宽高
  11. resultSize = childDimension;
  12. resultMode = MeasureSpec.EXACTLY;
  13. } else if (childDimension == LayoutParams.MATCH_PARENT) {//view 是match parent
  14. // Child wants to be our size. So be it.
  15. resultSize = size;
  16. resultMode = MeasureSpec.EXACTLY;
  17. } else if (childDimension == LayoutParams.WRAP_CONTENT) {//view 是wrap content
  18. // Child wants to determine its own size. It can't be
  19. // bigger than us.
  20. resultSize = size;
  21. resultMode = MeasureSpec.AT_MOST;
  22. }
  23. break;
  24. // Parent has imposed a maximum size on us
  25. case MeasureSpec.AT_MOST:
  26. if (childDimension >= 0) {//view 是固定宽高
  27. // Child wants a specific size... so be it
  28. resultSize = childDimension;
  29. resultMode = MeasureSpec.EXACTLY;
  30. } else if (childDimension == LayoutParams.MATCH_PARENT) {//view 是match parent
  31. // Child wants to be our size, but our size is not fixed.
  32. // Constrain child to not be bigger than us.
  33. resultSize = size;
  34. resultMode = MeasureSpec.AT_MOST;
  35. } else if (childDimension == LayoutParams.WRAP_CONTENT) {//view 是wrap content
  36. // Child wants to determine its own size. It can't be
  37. // bigger than us.
  38. resultSize = size;
  39. resultMode = MeasureSpec.AT_MOST;
  40. }
  41. break;
  42. // Parent asked to see how big we want to be
  43. case MeasureSpec.UNSPECIFIED://主要用于系统内部多次Measure,暂时不关注
  44. if (childDimension >= 0) {//view 是固定宽高
  45. // Child wants a specific size... let him have it
  46. resultSize = childDimension;
  47. resultMode = MeasureSpec.EXACTLY;
  48. } else if (childDimension == LayoutParams.MATCH_PARENT) {//view 是match parent
  49. // Child wants to be our size... find out how big it should
  50. // be
  51. resultSize = 0;
  52. resultMode = MeasureSpec.UNSPECIFIED;
  53. } else if (childDimension == LayoutParams.WRAP_CONTENT) {//view 是wrap content
  54. // Child wants to determine its own size.... find out how
  55. // big it should be
  56. resultSize = 0;
  57. resultMode = MeasureSpec.UNSPECIFIED;
  58. }
  59. break;
  60. }
  61. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  62. }
所以我们可以知道,对于普通的View,它的MeasureSpec有父容器的MeasureSpec和View自身的LayoutParams来一起决定的,那么不然的父容器的MeasureSpec和不同View 的LayoutParams,View 就可以有很多种不同的MeasureSpec.当是根据以上代码可以总结如下:
1.当View 是固定宽高的时候,不管父容器是什么模式,View都是精确模式(EXACTLY),并遵循使用LayoutParams里面的大小.
2.当View 的宽高是match parent,父容器是精确模式(EXACTLY),View也是精确模式(EXACTLY);父容器是最大模式(AT_MOST),View也是最大模式(AT_MOST).而且这2种情况View 的大小都是父容器剩余大小空间
3.当View 的宽高是wrap content,不管父容器是精确模式(EXACTLY)还是最大模式(AT_MOST),View始终都是最大模式(AT_MOST).并且大小都是父容器剩余的大小空间

注意:上面提到的精确模式和最大模式已经大小都在MeasureSpec里面有体现,可以参考其他笔记.

1 0