Android Measure 体系简单总结

 

    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设置为EXACTLY,SpecSize设置为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协商后发现测量结果和窗口尺寸有冲突)

https://blog.csdn.net/fyfcauc/article/details/54288343

 

posted @ 2018-09-10 19:25  zzfx  阅读(194)  评论(0编辑  收藏  举报