android界面的measure详解

举报
程序员历小冰 发表于 2021/08/28 00:49:55 2021/08/28
【摘要】 想必大家都遇到过在onCreate中调用getMeasuredHeight和getMeasuredHeight这两个函数得到的返回值是0,0的情况吧,这是为什么呢?当然是android界面初始化的相关机制导致的这个“问题”啦,我们现在就来看一下android的view和viewgroup两个类在初始化中measure相关的机制吧。 我...

想必大家都遇到过在onCreate中调用getMeasuredHeight和getMeasuredHeight这两个函数得到的返回值是0,0的情况吧,这是为什么呢?当然是android界面初始化的相关机制导致的这个“问题”啦,我们现在就来看一下android的view和viewgroup两个类在初始化中measure相关的机制吧。

我们都知道视图的绘制过程要经历三个过程,分别是onMeasure(确定大小),onLayout(确定位置)和onDraw三个方法,我们先从View类中的ViewRootImpl类中开始的,ViewRootImpl对象是view的静态内部类AttachInfo的一个成员变量,上述的绘制过程的三个方法都会在它的performTraversals()中被调用。

那我们就先分步看一下这个函数,由于这个函数比较长,这里就只给出部分代码啦。


  
  1. int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  2. int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  3. //TODO:measureSpec!!!!第一步啊,下一步是OnLayout
  4. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

看一下getRootMeasureSpec方法,那么我们就要先介绍一下View类中的另一个静态内部类MeasureSpec啦,MeasureSpec类封装啦从父节点传递到子节点的布局的需求,每个MeasureSpec既代表了width的需求也代表了height的需求,每个measureSpec有size和mode组成,一共有三种mode:

  1. UNSPECIFIED,代表父节点不对子节点做任何限制,子节点想多大就多大
  2. EXACTLY,代表父节点已经决定了准确的大小,子节点必须遵守
  3. AT_MOST,代表子节点的大小要小于特定的大小,但是具体大小自己决定。


  
  1. private static int getRootMeasureSpec(int windowSize, int rootDimension) {
  2. int measureSpec;
  3. switch (rootDimension) {
  4. //TODO:Match_Parent,Wrap_content和exactly,at_most的对应关系
  5. case ViewGroup.LayoutParams.MATCH_PARENT:
  6. // Window can't resize. Force root view to be windowSize.
  7. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  8. break;
  9. case ViewGroup.LayoutParams.WRAP_CONTENT:
  10. // Window can resize. Set max size for root view.
  11. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  12. break;
  13. default:
  14. // Window wants to be an exact size. Force root view to be that size.
  15. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  16. break;
  17. }
  18. return measureSpec;
  19. }

在performMeasure中其实并没有神马复杂的逻辑,只是调用了对象的一个成员变量mView的measure方法,将childWidthMeasureSpec,childHeightMeasureSpec传递过去,也就是MeasureSpec类中所说的一样,将父节点对子节点的布局要求传递过去。

然后我们就来到了VIew.measure(int,int)这个方法啦,我们可以注意到这是个final方法,也就是说我们不能重载这个方法,在文档中也说明啦,这个方法是为了得出这个view需要多大,而且父节点给出了宽和高的限制信息,但是实际的measure工作是onMeasure方法完成的,子类可以重载这个方法。这就是说明,view的设计师不希望用户重载measure方法,而是重载onMeasure方法。


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

  
  1. protected int getSuggestedMinimumWidth() {
  2. return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
  3. }


  
  1. public static int getDefaultSize(int size, int measureSpec) {
  2. //TODO:onMeasure真正做事情的地方
  3. int result = size;
  4. int specMode = MeasureSpec.getMode(measureSpec);
  5. int specSize = MeasureSpec.getSize(measureSpec);
  6. switch (specMode) {
  7. case MeasureSpec.UNSPECIFIED:
  8. result = size; //这是原size
  9. break;
  10. case MeasureSpec.AT_MOST:
  11. case MeasureSpec.EXACTLY:
  12. result = specSize; //
  13. break;
  14. }
  15. return result;
  16. }


  
  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
  2. //TODO: getMeasuredWith==0的原因啊,在这个方法调用之前都是为0的。
  3. mMeasuredWidth = measuredWidth;
  4. mMeasuredHeight = measuredHeight;
  5. mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
  6. }



  
  1. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
  2. //这是测量子view的大小的一个方法
  3. final int size = mChildrenCount; //用于for循环啊
  4. final View[] children = mChildren;
  5. for (int i = 0; i < size; ++i) {
  6. final View child = children[i];
  7. if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { //针对VISIBILITY的view
  8. measureChild(child, widthMeasureSpec, heightMeasureSpec); //所有的spec都是一样的
  9. }
  10. }
  11. }

  
  1. protected void measureChild(View child, int parentWidthMeasureSpec,
  2. int parentHeightMeasureSpec) {
  3. final LayoutParams lp = child.getLayoutParams(); //获得view的params
  4. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  5. mPaddingLeft + mPaddingRight, lp.width);//用Spec和viewGroup的paddingleft,right
  6. //和子view的width来计算
  7. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  8. mPaddingTop + mPaddingBottom, lp.height);
  9. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  10. }

在getChildMeasureSeec中和RootViewImpl中的getRootMeasureSpec方法类似,只不过是要考虑布局的实际大小,我在代码中有注释,大家可以自己研究。


  
  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. //通过viewGroup的Spec和子view的Spec和设定的w,h来决定子view的size和Spec
  8. switch (specMode) {
  9. // Parent has imposed an exact size on us
  10. case MeasureSpec.EXACTLY: //if is exactly
  11. if (childDimension >= 0) { //这是自己设定了大小
  12. //这是结果的
  13. resultSize = childDimension;
  14. resultMode = MeasureSpec.EXACTLY;
  15. } else if (childDimension == LayoutParams.MATCH_PARENT) { //values is -1
  16. // Child wants to be our size. So be it.
  17. resultSize = size;
  18. resultMode = MeasureSpec.EXACTLY;
  19. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  20. // Child wants to determine its own size. It can't be
  21. // bigger than us.
  22. resultSize = size;
  23. resultMode = MeasureSpec.AT_MOST;
  24. }
  25. break;
  26. // Parent has imposed a maximum size on us
  27. case MeasureSpec.AT_MOST:
  28. if (childDimension >= 0) { //只要是子view设定了specific的值,那么就一定是EXACTLY
  29. // Child wants a specific size... so be it
  30. resultSize = childDimension;
  31. resultMode = MeasureSpec.EXACTLY;
  32. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  33. // Child wants to be our size, but our size is not fixed.
  34. // Constrain child to not be bigger than us.
  35. resultSize = size;
  36. resultMode = MeasureSpec.AT_MOST; //继承viewgroup的
  37. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  38. // Child wants to determine its own size. It can't be
  39. // bigger than us.
  40. resultSize = size;
  41. resultMode = MeasureSpec.AT_MOST;
  42. }
  43. break;
  44. // Parent asked to see how big we want to be
  45. case MeasureSpec.UNSPECIFIED: //完全让子view决定
  46. if (childDimension >= 0) {
  47. // Child wants a specific size... let him have it
  48. resultSize = childDimension;
  49. resultMode = MeasureSpec.EXACTLY;
  50. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  51. // Child wants to be our size... find out how big it should
  52. // be
  53. resultSize = 0;
  54. resultMode = MeasureSpec.UNSPECIFIED;
  55. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  56. // Child wants to determine its own size.... find out how
  57. // big it should be
  58. resultSize = 0;
  59. resultMode = MeasureSpec.UNSPECIFIED;
  60. }
  61. break;
  62. }
  63. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  64. }


文章来源: blog.csdn.net,作者:程序员历小冰,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/u012422440/article/details/44706055

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。