- Android对View的测量是半协商半强制半模糊半具体的.
- 测量过程中的两套尺寸体系:
- [半强制] ParentView**约束ChildView: **MeasureSpec(通过measure方法传递给ChildView, MeasureSpec本身包含了两类信息: SpecMode和SpecSize):
- SpecMode = EXACTLY: 给ChildView指定了具体尺寸[半具体], 保存在SpecSize中,默认ChildView应该遵循,Android原生的View基本都遵循了这个约定,当然了,你自己实现时可以不遵循,后果自负.
- SpecMode = AT_MOST: 约束了ChildView的最大尺寸[半模糊], 保存在SpecSize中,默认ChildView应该遵循,Android原生的View基本都遵循了这个约定,当然了,你自己实现时可以不遵循,后果自负.
- SpecMode = UNSPECIFIED: 对ChildView的尺寸不做约束[半模糊], ChildView完全自由发挥.
- [半协商] ChildView向ParentView**表达自己的意愿: **LayoutParam(ChildView自己携带,ParentView在为ChildView生成强制性MeasureSpec时应该将ChildView的LayoutParam也考虑进去,这也是一个非强制性约定,你完全可以自己实现时不考虑ChildView的LayoutParam)
- MATCH_PARENT[半模糊]: ChildView想和ParentView一样大。
- WRAP_CONTENT[半模糊]: ChildView需要大到能够容纳自己的内容。
- 具体的尺寸[半具体]: ChildView已经被指定了具体的尺寸。
- [半强制] ParentView**约束ChildView: **MeasureSpec(通过measure方法传递给ChildView, MeasureSpec本身包含了两类信息: SpecMode和SpecSize):
- Margin和Padding在测量过程会被考虑进去,其中:
- Margin来自ChildView的LayoutParams, 属于ChildView。
- Padding来自ParentView的Padding属性, 属于ParentView。
Android有一套不成文的测量规范,体现在其定义的函数和原生复合View的源码中,View/ViewGroup提供了一套函数供使用者按照Android的测量规范进行测量,Android原生的复合View很多都会使用这套函数来履行契约,在实现自定义View时,没有特殊需要,也推荐使用这套函数:
- 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的测量约束
- 总结来看:
- 在ChildView通过LayoutParam**指定了自己具体尺寸的情况下,其诉求被无条件接受**。
- 在ChildView本身的诉求是模糊的情况下(MATCH_PARENT/WRAP_CONTENt), 会综合ParentView的测量约束和Padding/Margin进行综合考虑来实现约束[该约束可能是具体的,也可能是模糊的]。
- 约束很多时候是尽力而为,结合当前手头所有的尽量得到一个较为精准的约束(但是大多数时候得不到)。
- 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。
- ViewGroup: measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec):
- 基本同measureChildWithMargins
- 只考虑ChildView的Padding,不考虑ParentView的Margin.
- ViewGroup: measureChildren(int widthMeasureSpec, int heightMeasureSpec)
- 对所有非GONE的childView调用measureChild进行符合Android测量规范的测量。
- 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:
- 表示Parent对View没有约束,那么使用View自己希望的测量结果即可。
- Parent约束SpecMode是AT_MOST:
- 如果View的测量结果超过了Parent的限制, 那么使用Parent的限制值,不过State会附加上MEASURED_STATE_TOO_SMALL向上层(一般是ViewRootImpl)告知自己对测量结果不满意
- Parent约束SpecMode是EXACTLY:
- Parent为ChildView**规定了具体尺寸,ChildView不能反抗,只能遵循,并且也不能通过State向上反馈意见,是最强制性的措施**。
- 上面看似是Parent压倒了ChildView的意见,但是参见getChildMeasureSpec函数, 在规范流程下,只有ChildView自己在LayoutParam要求了具体尺寸或者MATCH_PARENT才会有Parent施加EXACTLY约束, 因此,其实这里还是ChildView自己的意愿。
- 当然了,不过遵循这套规范流程,上面的结论是不成立的。
- 上述流程得到不光是一个尺寸,还包括了state, ChildView的State也会被合并, 最终所有参与测量的View的State被合并传至最上层。
- 上述State信息要通过getMeasuredWidth/HeightAndState才能获得。
- combineMeasuredStates函数可以用来合并State
- View: resolveSize(int size, int measureSpec)
- 基本同resolveSizeAndState
- 返回的结果中只包含Size,不包含State。
- View: getDefaultSize(int size, int measureSpec)
- 可以看作是resolveSize的一个退化实现, View的onMeasure默认实现使用了这个函数。
- 在Parent约束为AT_MOST/EXACTLY时,使用SpecSize作为自己的测量结果。
- 在Parent约束为UNSPECIFIED时,使用自己的测量结果。
- ViewGroup: getChildMeasureSpec(int spec, int padding, int childDimension)
measureChildWithMargins等函数的存在不代表不能直接调用ChildView的measure函数,在measureChildWithMargins生成的MeasureSpec**不满足你的需求时**,完全可以自己生成MeasureSpec然后调用ChildView的measure函数。
最小宽度/高度: Android规范测量流程会建议在测量过程中考虑View的最小宽度/高度
- getSuggestedMinimumWidth/Height提供了View的最小宽度/高度
- 最小宽度/高度一方面取决于setMinimumWidth/Height设置的值
- 另一方面取决于Background(Drawable)的getMinimumWidth/Height
- View的默认实现是上面两者取最大者,自定义View有特殊需求可以重写这个函数。
- 最小宽度/高度是规范但不强制,如果你自定义一个根本不考虑这些的View, 也没关系,但是代价是ChildView的setMinimumWidth/Height()函数不能生效, Background也可能显示不全等等。
View在一次整个View体系测量历程中,可能会被测量复数次(measure函数被调用数次),这是由上层View来决定的,每种View都有不同的测量逻辑。
- 比如FrameLayout,在某些情况下,会measure两次指定了MATCH_PARENT的ChildView。
- 测量的起点是ViewRootImpl, 整个测量的简化流程(一个极度简单理想化的模型: View体系是 RootView -> P1 View -> P2 View -> Child View):
- ViewRootImpl: performTraversals() ->
- ViewRootImpl: measureHierarchy() ->
- ViewRootImpl: performMeasure() ->
- RootView: measure()被施加测量约束进行测量 ->
- P1 View: measure()被施加测量约束进行测量 ->
- P2 View: measure()被施加测量约束进行测量 ->
- Child View: measure()被施加测量约束进行测量 ->
- Child View: measure() 测量完毕,调用setMeasuredDimension()确定自己尺寸。
- P2 View: measure() 根据Child View的测量结果做进一步的处理运算,可能重新测量Child View, 最后得出一个适合自己的尺寸,调用setMeasuredDimension()确定自己尺寸。
- P1 View: measure() 根据P2 View的测量结果做进一步的处理运算,可能重新测量P2 View, 最后得出一个适合自己的尺寸,调用setMeasuredDimension()确定自己尺寸。
- RootView: measure() 根据P1 View的测量结果做进一步的处理运算,可能重新测量P1 View, 最后得出一个适合自己的尺寸,调用setMeasuredDimension()确定自己尺寸。
- ViewRootImpl: 整个View体系的测量完成,但不排除后续会重新发起测量(比如检查State发现有View不满足当前测量结果,或者和WMS协商后发现测量结果和窗口尺寸有冲突)
https://blog.csdn.net/fyfcauc/article/details/54288343