1. Android对View的测量是半协商半强制半模糊半具体的.
  2. 测量过程中的两套尺寸体系:
    1. [半强制] ParentView**约束ChildView: **MeasureSpec(通过measure方法传递给ChildView, MeasureSpec本身包含了两类信息: SpecMode和SpecSize):
      1. SpecMode = EXACTLY: 给ChildView指定了具体尺寸[半具体], 保存在SpecSize中,默认ChildView应该遵循,Android原生的View基本都遵循了这个约定,当然了,你自己实现时可以不遵循,后果自负.
      2. SpecMode = AT_MOST: 约束了ChildView的最大尺寸[半模糊], 保存在SpecSize中,默认ChildView应该遵循,Android原生的View基本都遵循了这个约定,当然了,你自己实现时可以不遵循,后果自负.
      3. SpecMode = UNSPECIFIED: 对ChildView的尺寸不做约束[半模糊], ChildView完全自由发挥.
    2. [半协商] ChildView向ParentView**表达自己的意愿: **LayoutParam(ChildView自己携带,ParentView在为ChildView生成强制性MeasureSpec时应该将ChildView的LayoutParam也考虑进去,这也是一个非强制性约定,你完全可以自己实现时不考虑ChildView的LayoutParam)
      1. MATCH_PARENT[半模糊]: ChildView想和ParentView一样大。
      2. WRAP_CONTENT[半模糊]: ChildView需要大到能够容纳自己的内容。
      3. 具体的尺寸[半具体]: ChildView已经被指定了具体的尺寸。
  3. Margin和Padding在测量过程会被考虑进去,其中:
    • Margin来自ChildView的LayoutParams, 属于ChildView
    • Padding来自ParentView的Padding属性, 属于ParentView
  4. Android有一套不成文的测量规范,体现在其定义的函数和原生复合View的源码中,View/ViewGroup提供了一套函数供使用者按照Android的测量规范进行测量,Android原生的复合View很多都会使用这套函数来履行契约,在实现自定义View时,没有特殊需要,也推荐使用这套函数:

    1. ViewGroup: getChildMeasureSpec(int spec, int padding, int childDimension)
      • getChildMeasureSpec综合考虑了ChildView的LayoutParam(ChildView的自我诉求,在这个函数中是childDimension,及ChildView的LayoutParam在某个维度的尺寸参数: width/height)ParentView的所受到的约束性MeasureSpec(ParentView的Parent施加的),以及Padding/Margin, 生成测量ChildView用的MeasureSpec: 在下面的流程中,得到SpecMode和SpecSize,然后组合成一个MeasureSpec
      • spec = EXACTLY(ParentView已经被其Parent指定了具体尺寸):
        • childDimension >= 0: ChildView表示自己希望某个具体尺寸无条件尊重ChildView的意愿, SpecMode设置为EXACTLY,SpecSize设置为childDimension的数值
        • childDimension == MATCH_PARENT: ChildView表示希望和ParentView一样大, 正好现在有了ParentView的具体尺寸(因为SpecMode是EXACTLY, MeasureSpec的SpecMode设置为EXACTLYSpecSize设置为ParentView的size
        • childDimension == WRAP_CONTENT: ChildView表示需要大到可以容纳自己的内容,这个需求是模糊的,因为这个时候ChildView没有measure,是不知道其内容具体的尺寸的,ParentView能做的只是施加一个约束: ChildView不能比ParentView大, 因此SpecMode设置为AT_MOST, SpecSize设置为ParentView的size
      • spec = AT_MOST(ParentView的size没有被具体指定,只是被告知一个最大值)
        • childDimension >= 0: ChildView表示自己希望某个具体尺寸, 无条件尊重ChildView的意愿, SpecMode设置为EXACTLY,SpecSize设置为childDimension的数值
        • childDimension == MATCH_PARENT: ChildView表示希望和ParentView一样大,因为ParentView此时的size是不确定的,因此也只能为ChildView施加一个约束: 既然希望和ParentView一样大,那么也需要遵循ParentView的最大尺寸约束,SpecMode设置为AT_MOST, SpecSize设置为ParentView的最大尺寸
        • childDimension == WRAP_CONTENT: ChildView表示需要大到可以容纳自己的内容,类似上面的情况,这种情况下,唯一能施加的约束是ChildView不能大于ParentView的最大尺寸(和ChildView不能大于ParentView比,打了个折扣,因为ParentView尺寸不确定,只能这样约束), SpecMode设置为AT_MOST, SpecSize设置为ParentView的最大尺寸.
      • spec = UNSPECIFIED(ParentView的尺寸没有任何约束,自由发挥):
        • childDimension >= 0: ChildView表示自己希望某个具体尺寸无条件尊重ChildView的意愿, SpecMode设置为EXACTLY,SpecSize设置为childDimension的数值
        • childDimension == MATCH_PARENT: ChildView表示希望和ParentView一样大, 这种情况下,ParentView自己的尺寸是模糊的,只能也告知ChildView自由发挥: SpecMode设置为AUNSPECIFIED
        • childDimension == WRAP_CONTENT: ChildView表示需要大到可以容纳自己的内容, 同样这种条件下做不了什么约束,只能告知ChildView自由发挥: SpecMode设置为AUNSPECIFIED
      • 最后将SpecMode和SpecSize组合为MeasureSpec作为对ChildView的测量约束
      • 总结来看:
        1. 在ChildView通过LayoutParam**指定了自己具体尺寸的情况下,其诉求被无条件接受**。
        2. 在ChildView本身的诉求是模糊的情况下(MATCH_PARENT/WRAP_CONTENt), 会综合ParentView的测量约束和Padding/Margin进行综合考虑来实现约束[该约束可能是具体的,也可能是模糊的]
        3. 约束很多时候是尽力而为,结合当前手头所有的尽量得到一个较为精准的约束(但是大多数时候得不到)。
    2. ViewGroup: measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed):
      • ParentView基于ChildView的Padding自己的Margin自己被Parent施加的MeasureSpec测量约束ChildView的LayoutParam尺寸, 调用getChildMeasureSpec函数得到一个在当前条件下遵循Android测量规范的MeasureSpec测量约束
      • ChildView在Width和Height两个维度的MeasureSpec都按照这样的流程的得到。
      • 将得到的MeasureSpec传递给ChildView的measure函数,开始测量ChildView
    3. ViewGroup: measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec):
      • 基本同measureChildWithMargins
      • 只考虑ChildView的Padding,不考虑ParentView的Margin.
    4. ViewGroup: measureChildren(int widthMeasureSpec, int heightMeasureSpec)
      • 对所有非GONE的childView调用measureChild进行符合Android测量规范的测量。
    5. View: resolveSizeAndState(int size, int measureSpec, int childMeasuredState)
      • 在测量阶段的最终,需要调用setMeasuredDimension设定View最终的测量尺寸
      • resolveSizeAndState会综合View自己的意愿(View在经过一系列测量后想为自己设置的尺寸)Parent为其施加的测量约束 和 其ChildView在测量过程中产生的附加State信息(比如MEASURED_STATE_TOO_SMALL), 得到一个包含了state信息和尺寸的值,作为setMeasuredDimension的参数。
      • Parent约束SpecMode是UNSPECIFIED:
        1. 表示Parent对View没有约束,那么使用View自己希望的测量结果即可。
      • Parent约束SpecMode是AT_MOST:
        1. 如果View的测量结果超过了Parent的限制, 那么使用Parent的限制值,不过State会附加上MEASURED_STATE_TOO_SMALL向上层(一般是ViewRootImpl)告知自己对测量结果不满意
      • Parent约束SpecMode是EXACTLY:
        1. Parent为ChildView**规定了具体尺寸,ChildView不能反抗,只能遵循,并且也不能通过State向上反馈意见,是最强制性的措施**。
        2. 上面看似是Parent压倒了ChildView的意见,但是参见getChildMeasureSpec函数, 在规范流程下,只有ChildView自己在LayoutParam要求了具体尺寸或者MATCH_PARENT才会有Parent施加EXACTLY约束, 因此,其实这里还是ChildView自己的意愿。
        3. 当然了,不过遵循这套规范流程,上面的结论是不成立的。
      • 上述流程得到不光是一个尺寸,还包括了state, ChildView的State也会被合并, 最终所有参与测量的View的State被合并传至最上层。
      • 上述State信息要通过getMeasuredWidth/HeightAndState才能获得。
      • combineMeasuredStates函数可以用来合并State
    6. View: resolveSize(int size, int measureSpec)
      • 基本同resolveSizeAndState
      • 返回的结果中只包含Size,不包含State
    7. View: getDefaultSize(int size, int measureSpec)
      • 可以看作是resolveSize的一个退化实现, View的onMeasure默认实现使用了这个函数。
      • 在Parent约束为AT_MOST/EXACTLY时,使用SpecSize作为自己的测量结果。
      • 在Parent约束为UNSPECIFIED时,使用自己的测量结果。
  5. measureChildWithMargins等函数的存在不代表不能直接调用ChildView的measure函数,在measureChildWithMargins生成的MeasureSpec**不满足你的需求时**,完全可以自己生成MeasureSpec然后调用ChildView的measure函数。

  6. 最小宽度/高度: Android规范测量流程会建议在测量过程中考虑View的最小宽度/高度

    1. getSuggestedMinimumWidth/Height提供了View的最小宽度/高度
    2. 最小宽度/高度一方面取决于setMinimumWidth/Height设置的值
    3. 另一方面取决于Background(Drawable)的getMinimumWidth/Height
    4. View的默认实现是上面两者取最大者,自定义View有特殊需求可以重写这个函数。
    5. 最小宽度/高度是规范但不强制,如果你自定义一个根本不考虑这些的View, 也没关系,但是代价是ChildView的setMinimumWidth/Height()函数不能生效, Background也可能显示不全等等。
  7. View在一次整个View体系测量历程中,可能会被测量复数次(measure函数被调用数次),这是由上层View来决定的,每种View都有不同的测量逻辑。

    1. 比如FrameLayout,在某些情况下,会measure两次指定了MATCH_PARENT的ChildView。
  8. 测量的起点是ViewRootImpl, 整个测量的简化流程(一个极度简单理想化的模型: View体系是 RootView -> P1 View -> P2 View -> Child View):
    1. ViewRootImpl: performTraversals() ->
    2. ViewRootImpl: measureHierarchy() ->
    3. ViewRootImpl: performMeasure() ->
    4. RootView: measure()被施加测量约束进行测量 ->
    5. P1 View: measure()被施加测量约束进行测量 ->
    6. P2 View: measure()被施加测量约束进行测量 ->
    7. Child View: measure()被施加测量约束进行测量 ->
    8. Child View: measure() 测量完毕,调用setMeasuredDimension()确定自己尺寸。
    9. P2 View: measure() 根据Child View的测量结果做进一步的处理运算,可能重新测量Child View, 最后得出一个适合自己的尺寸,调用setMeasuredDimension()确定自己尺寸。
    10. P1 View: measure() 根据P2 View的测量结果做进一步的处理运算,可能重新测量P2 View, 最后得出一个适合自己的尺寸,调用setMeasuredDimension()确定自己尺寸。
    11. RootView: measure() 根据P1 View的测量结果做进一步的处理运算,可能重新测量P1 View, 最后得出一个适合自己的尺寸,调用setMeasuredDimension()确定自己尺寸。
    12. ViewRootImpl: 整个View体系的测量完成,但不排除后续会重新发起测量(比如检查State发现有View不满足当前测量结果,或者和WMS协商后发现测量结果和窗口尺寸有冲突)

更多相关文章

  1. C语言函数以及函数的使用
  2. android 里面的测量单位
  3. Android屏幕尺寸适配注意事项
  4. Android设计尺寸规范--Android Design Guidelines
  5. Android屏幕尺寸、标题栏高度、状态栏高度、当前View尺寸
  6. android NDK JNI设置自己的log输出函数
  7. Android build/envsetup.sh 脚本分析(lunch函数)
  8. Android Hook学习之ptrace函数的使用
  9. Android中回调函数的理解---本人Android纯新手

随机推荐

  1. 如何在android 系统 C/C++ 层中添加 log
  2. 转 Android中进入系统设置界面
  3. ImageView的属性android:scaleType作用
  4. Android如何解析json数组对象
  5. android-HandlerThread、IntentServer
  6. Android(安卓)progressbar实现带底部指示
  7. Android SQLite数据库中的表详解
  8. Anroid camera + mediacodec
  9. android的MediaPlayer的简介
  10. android底部标签页的tab实现