一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
自定义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()方法作为切入口
首先获取子View的
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生成过程:
图片来自于Android进阶基础系列:View的工作原理 全面理解!
现在我们回到文章最开始的那个问题 :如果自定义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()设置