Android View 工作流程

View的绘制流程经过measure、layout、draw三个过程。

measure:测量View的高宽,layout用来确定View在父容器存放的位置,draw负责将View绘制在屏幕上。

// 源码中 最先从ViewRoot的开始绘制,从ViewGroup开始,遍历里面的子View,完成绘制

View的大小:dx、wrap_content、match_parent ,跟模式有关:EXACTLY、AT_MOST、UNSPECIFIED。android将其集成为一体,即MeasureSpec,通过对MeasureSpec的解析,可以得到View的大小与其对应的宽高模式。

 
 
   
   
  1. class MeasureSpec {
  2. public static final int EXACTLY // match_parent、dp
  3. public static final int AT_MOST // wrap_content
  4. public static final int UNSPECIFIED // don't care
  5. if (sUseBrokenMakeMeasureSpec) {
  6. return size + mode;
  7. } else {
  8. return (size & ~MODE_MASK) | (mode & MODE_MASK);
  9. }
  10. // size 是view的大小,宽高。 mode是模式 返回一个int值,避免过多的内存分配

系统将View的size和mode封装到MeasureSpec中,通过对MeasureSpec解析可以得到size和mode。

MeasureSpec怎么来的?

对于顶级View(DecorView)来说,它没有父亲。所以它的MeasureSpec是由窗口的尺寸和其自身的LayoutParams共同决定的。

源码如下:

measureHierarchy方法中
 
 
   
   
  1. childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
  2. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
  3. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

再来看看getRootMeasureSpec:

 
 
   
   
  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.精确模式,大小为LayoutParams指定的大小
  14. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  15. break;
  16. }
  17. return measureSpec;
  18. }

非顶级的View的MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams决定的。

所有的View其measure过程都是由ViewGroup传递过来,因此先看ViewGroup是如何measure的。

ViewGroup本身没有onMeasure的方法,它也不需要,它包含子View,目的是View的绘制。所以它提供了measureChildren --> measureChild方法。

它会遍历所有的子View,执行子View的measure方法。

 
 
   
   
  1. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
  2. final int size = mChildrenCount;
  3. final View[] children = mChildren;
  4. for (int i = 0; i < size; ++i) {
  5. final View child = children[i];
  6. if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
  7. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  8. }
  9. }
  10. }
  11. protected void measureChild(View child, int parentWidthMeasureSpec,
  12. int parentHeightMeasureSpec) {
  13. final LayoutParams lp = child.getLayoutParams();
  14. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  15. mPaddingLeft + mPaddingRight, lp.width);
  16. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  17. mPaddingTop + mPaddingBottom, lp.height);
  18. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  19. }

很显然,measureChild的思想就是取出子元素的LayoutParams,然后通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec传递给View去绘制。

所以具体看看getChildMeasureSpec是个什么样的逻辑。

  
  
  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) {
  11. resultSize = childDimension;
  12. resultMode = MeasureSpec.EXACTLY;
  13. } else if (childDimension == LayoutParams.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) {
  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) {
  27. // Child wants a specific size... so be it
  28. resultSize = childDimension;
  29. resultMode = MeasureSpec.EXACTLY;
  30. } else if (childDimension == LayoutParams.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) {
  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:
  44. if (childDimension >= 0) {
  45. // Child wants a specific size... let him have it
  46. resultSize = childDimension;
  47. resultMode = MeasureSpec.EXACTLY;
  48. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  49. // Child wants to be our size... find out how big it should
  50. // be
  51. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  52. resultMode = MeasureSpec.UNSPECIFIED;
  53. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  54. // Child wants to determine its own size.... find out how
  55. // big it should be
  56. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  57. resultMode = MeasureSpec.UNSPECIFIED;
  58. }
  59. break;
  60. }
  61. //noinspection ResourceType
  62. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  63. }
上述方法不难理解,根据父MesureSpec和自设LayoutParams确定子View的MeasureSpec,参数padding指父容器中已占用的空间,因为子View会减去

int size =Math.max(0, specSize - padding);

普通View -MeasureSpec的创建规则

pSpecMode / cLayoutParams EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY/childSize EXACTLY/childSize EXACTLY/childSize
match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0
wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0

parentSize指父容器可以用的大小

注:这里容易出现问题,当父为EXACtLY时,即match_parent,子View的wrap_content会不生效,因为根据表,它的size是parentSize,所以这个改法是将高/宽进行指定dp

直接继承View和ViewGroup的控件,padding是默认无法生效的,需要自己处理。需要在onDraw方法中自己处理下:

final int paddingLeft = getPaddingLeft();

.....

int width = getWidth() - paddingLeft - paddingRight;

.....

最后再来看View的measure方法:

 
 
   
   
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  4. }

onMeasure里就进行了setMeasureDimension,这个方法会去设置View的宽/高的测量值,即getDefaultSize:

 
 
   
   
  1. public static int getDefaultSize(int size, int measureSpec) {
  2. int result = size;
  3. int specMode = MeasureSpec.getMode(measureSpec);
  4. int specSize = MeasureSpec.getSize(measureSpec);
  5. switch (specMode) {
  6. case MeasureSpec.UNSPECIFIED:
  7. result = size;
  8. break;
  9. case MeasureSpec.AT_MOST:
  10. case MeasureSpec.EXACTLY:
  11. result = specSize;
  12. break;
  13. }
  14. return result;
  15. }

specSize即为View测量后的大小。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值