自定义View中,如果View宽或者高设置wrap_content而我们不做任何处理,最终的效果是和match_parent相同的,本文主要是从源码的角度讲解下为什么会发生这种情况
1.自定义View假如不处理wrap_content
随便自定义一个View:
class CustomView @JvmOverloads constructor(
context: Context,
attributes: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attributes, defStyleAttr) {
}
我们在xml中分别设置math_parent
和wrap_content
看下效果:
<com.example.gitlinux.CustomView
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="100dp"/>
<com.example.gitlinux.CustomView
android:background="#ff0000"
android:layout_width="wrap_content"
android:layout_height="100dp"/>
分别对应:

可以看到,CustomView
的宽度设置math_parent
和wrap_content
结果相同,这是为什么呢,接下来让我们从源码中寻找答案
2.View
测量规格
在看源码之前,我们先了解下View
的测量规格MeasureSpec
:高2位代表Mode
,低30位代表Size
。
MeasureSpec
分为三种测量mode:
-
AT_MOST
父View当前剩余分配空间,大小就是Size
,子View的大小不能超过Size
,对应LayoutParams
中的wrap_content
模式 -
EXACTLY
父View检测出的子View的最终大小就是Size
,对应LayoutParams
中的match_parent
、具体数值 两种模式 -
UNSPECIFIED
父View对view不限制,要多大给多大,比较少使用,这个可以用来获取View真实的宽高,比如ScrollView
高度上就指定了这种测量模式
3.源码寻找为什么math_parent
和wrap_content
结果相同:
首先我们先有个确定的概念:View最终的宽高
MeasureSpec
是由父View的MeasureSpec
和子View的LayoutParam
来决定的。
我们以ViewGroup
的measureChildWithMargins()
方法作为切入口

LayoutParam
,调用了getChildMeasureSpec()
方法传入了子View的LayoutParam
和父View的宽高的MeasureSpec
:
getChildMeasureSpec()
方法比较长,我们主要截取下其中的关键逻辑:


当父View的测试模式为
EXACTLY
时,子View的LayoutParam
如果为具体数值和match_parent
,则子View的MeasureSpec
测量规格就为EXACTLY
,否则为AT_MOST

当父View的测试模式为
AT_MOST
时,子View的LayoutParam
如果为wrap_content
和match_parent
,则子View的MeasureSpec
测量规格就为AT_MOST
,否则为EXACTLY
。
我们用一个表格直观的看下子View的MeasureSpec
生成过程:

现在我们回到文章最开始的那个问题 :如果自定义View的宽度设置为wrap_content
,从上面的表格中可以看出不管父View的测量规格是AT_MOST
还是EXACTLY
,子View最终的宽度大小都是和宽度设置为match_parent
时的大小相同。
最终是怎么设置成子View的宽高大小的呢,具体的源码就不带着分析了,大体的调用逻辑就是:
通过刚才
measureChildWithMargins()
方法,在获取到子View宽和高的MeasureSpec
之后,就调用了子View的measure()
->onMeasure()
->setMeasuredDimension()
->setMeasuredDimensionRaw()
方法,最终完成子View宽高大小设置
3.如何解决math_parent
和wrap_content
结果相同
这个就得要求自定义View时,需要重写View的onMeasure()
方法,在这个方法中判断当前View的宽或者高的布局参数是否为wrap_content
,如果是就得手动计算所需真实的宽高,然后调用setMeasuredDimension()
设置
网友评论