熟悉view的测量与布局是自定义view的基础。
网上关于这部分的源码解析已经有很多了,本篇只当作一篇观后感。
测量--measure
测量就是量出view长宽,height和width。
子view的长宽依赖于父view的决策。 这一点是明显的,我们屏幕的尺寸是固定的,是往里面填view的,所以最上层最外面的父view的大小肯定是固定的,没记错的话是由window的layoutParam设置。
父view的决策在代码里就是两个spec,heightSpec和widthSpec。这两个参数会在view的递归测量的过程中一层层传递,由父view传递给子view的onMeasure函数。
那传递给子View的onMeasure函数干什么呢?
当然是决策,这两个参数就相当于父View告诉子View,父view是这样的,能给出的规格也是这样的(待遇),在子view的onMeasure中,获取子view的layoutParam,相当于子View向父view申请的待遇。在代码getChildMeasureSpec里面的呈现就是一块switch代码块,判断出各种情况,最后综合出一个结果(这部分求子view的spec的代码也可以自己自定义,getChildMeasureSpec是一个现成的方法)。
java
case MeasureSpec.EXACTLY:
//1.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST 。
}
---------------------------------------------------
case MeasureSpec.AT_MOST:
//2.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
//2.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
例如case为EXACTLY的情况:这是一种比较好理解的情况,父view的mode是EXACTLY意味着,父view的某个维度(dimension)是准确的一个px,例如size为500px。也就是说父view就只能给子view最多500px的待遇了,具体多少还要看子view的申请了。这个申请就是childDimension,包括了layoutParam的三种情况,准确值、match、warp。在权衡之下,最终给子view创造新的spec规格。
根据不同的自定义view,measure有不同的计算形式和计算过程,但都包含根据子view的layoutParam和父view的spec得到属于子view的spec和子view的尺寸大小并保存起来,在measureHeight和measureWidth中。 整个流程的伪代码如下:
java
parent.onMeasure(widthSpec, heightSpec) {
1、根据widthSpec, heightSpec和parent.layoutParam判断获取并返回parent.spec(即getChildMeasureSpec(xxxx))
2、for循环遍历parent所有的child
child.measure(parent.widthSpec, parent.heightSpec)
3、计算parent的尺寸,这一步根据自定义View的需求自行编写怎么计算尺寸, 并保存setMeasuredDimension
}
-------------
measure() {
xxxxx
onMeasure()
xxxxx
}
举个例子:三层嵌套的view布局,父view和子view都是viewGroup,孙view不是viewGroup只是个view,父view的layout_width为500px且specMode为EXACTLY,子view的layout_width为warp_content,孙view的layout_width依旧为warp_content,那么这种情况measure后每个view测量后的width会变成多少?
父view由于宽度给的是准确值(size==500)且specMode为EXACTLY,子view调用getChildMeasureSpec计算子view的spec,会将子view的spec.size赋值为500px,spec.mode为AT_MOST。孙view因为已经是最底层的view了,是可以分为两种情况的,一种是view.java,没有重写过onMeasure,其他的就是重写过onMeasure,例如textView.java
1、如果孙view是view.java,那么孙view的width就为500px,mode自然不会设定了,spec的出现是为了约束子view的,都到最底层了,自然就不需要spec。
2、如果孙view是textView.java,那么孙view的width就会在重写的onMeasure中设定,textView是取text字符的总宽度为孙view的width
上述的过程只是递归到最底层的孙view时,分情况讨论而已,此时还需要从最底层递归上去,完成每一层view的最后一步,计算每一层的尺寸,这一步根据自定义View的需求自行编写怎么计算尺寸,反正孙view的大小都明确了,只要符合本身的spec要求就行了最终setMeasuredDimension保存。假设,孙view的宽度确定为100px,已知子view的spec.size赋值为500px,spec.mode为AT_MOST,意思是子view最大可以500px,那么设置400px行不行?肯定可以。
布局--layout
layout的意思就是把view放在准确的位置,layout函数有四个参数,top、bottom、left、right。
layout函数的伪代码和measure相近,如下
java
layout() {
xxxx.setFrame()
onLayout()
}
------------------
onLayout() {
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
View child = getChildAt(i) ;
//整个layout()过程就是个递归过程
child.layout(l, t, r, b)
}
layout的流程就不过多赘述了,整体来说比measure要简单清晰些。
1、layout能改view的大小吗?
能,在layout的setFrame中会给view的成员变量top、bottom、left、right赋值。这四个参数代表着矩形view的左上角和右下角坐标,当然也就控制着大小。
2、只重写onlayout并在其中修改view的大小会出现问题吗?
可能会,因为onlayout中修改的值只会通知到当前view,对于他的父view是不知道这个改变的,可能会引起一些布局上的bug
深度好文: juejin.cn/post/696243...