其实 MeasurePolicy 不是一个类,而是一个接口,在这个接口中,可以看到实现的 IntrinsicMeasureScope.minIntrinsicHeight()
方法:
// code 7 MeasurePolicy.kt /** * The function used to calculate [IntrinsicMeasurable.minIntrinsicHeight]. It represents * defines the minimum height this layout can take, given a specific width, such * that the content of the layout will be painted correctly. */ fun IntrinsicMeasureScope.minIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int ): Int { val mapped = measurables.fastMap { DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Height) } val constraints = Constraints(maxWidth = width) val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection) val layoutResult = layoutReceiver.measure(mapped, constraints) return layoutResult.height }
注释写的比较清楚:这个方法是用来计算 IntrinsicMeasurable.minIntrinsicHeight
,它定义了在给定宽度的情况下,该布局在正确绘制布局内容的情况下,可以获得的最小高度。
怎么获得的呢?看代码是先通过 DefaultIntrinsicMeasurable 类求出每个子组件的最小高度,最小高度的计算还是调用的 measurable.minIntrinsicHeight(constraints.maxWidth)
方法。。。呃。。又回到了之前的 IntrinsicMeasurable 接口中的 fun minIntrinsicHeight(width: Int): Int
方法(笑Cry.jpg)。虽然没有找到真正实现这个接口的代码,但是通过上面的源码跟踪,竹子也得知了两个关键点。
关键点一就是 IntrinsicMeasurable 这个接口,不光是 minIntrinsicHeight
方法,同样的还有 maxIntrinsicHeight
、minIntrinsicWidth
、maxIntrinsicWidth
这一类的方法都是在 IntrinsicMeasurable 接口,真正实现了这四个方法的地方就是真正实现了固有特性测量的地方。
再来看这些方法的参数,都是对应的另一个尺寸的极值,这些都在 DefaultIntrinsicMeasurable 类中所有体现:
// code 8 LayoutModifier.kt DefaultIntrinsicMeasurable class override fun measure(constraints: Constraints): Placeable { if (widthHeight == IntrinsicWidthHeight.Width) { val width = if (minMax == IntrinsicMinMax.Max) { measurable.maxIntrinsicWidth(constraints.maxHeight) } else { measurable.minIntrinsicWidth(constraints.maxHeight) } return EmptyPlaceable(width, constraints.maxHeight) } val height = if (minMax == IntrinsicMinMax.Max) { measurable.maxIntrinsicHeight(constraints.maxWidth) } else { measurable.minIntrinsicHeight(constraints.maxWidth) } return EmptyPlaceable(constraints.maxWidth, height) }
比如之前是传入的 widthHeight = IntrinsicWidthHeight.Height
,minMax = IntrinsicMinMax.Min
,所以就是调用的 measurable.minIntrinsicHeight(constraints.maxWidth)
,也就是将 约束条件中 width 最大值传给了方法。
那之前我们仅使用 Modifier.height(IntrinsicSize.Min)
为 Row 的高度设置了固有特性测量并没有设置宽度啊?那是因为会将约束条件的 width 最大值作为默认值传进去,如 code 3 中的代码,这里的最大值其实就是不限制宽度的大小,所以 Modifier.height(IntrinsicSize.Min)
所表达的意思就是,当宽度不限时通过子组件预先测量的宽高信息所能计算的 Row 最小高度是多少。当然也可以自己设置一个宽度,那么子组件就可以根据你设置的 Row 宽度以及预先测量的宽高信息得出 Row 的最小高度是多少。这就是关键点二。
宽度受限会影响高度的例子很常见的就是 TextView 中显示长文本的情况。显示内容不变时,宽度越小高度自然会越大,可看参考文献2 中的例子。
上面说的都是在 Compose 官方提供的 Composable 组件中的情况,那么在自定义 Layout 中呢?很遗憾,如果我们要在自定义 Layout 中使用固有特性测量,则必须自己实现,否则会有问题。
实现自定义layout中的固有特性测量
由之前的 学习笔记三 可知,自定义 Layout 主要还是重写了 Layout()
方法,如果我们要适配自己写的自定义 Layout 的固有特性测量,就需要对 Layout()
方法中的 MeasurePolicy 接口进行重写了。
之前的自定义 Layout 主要是重写了 MeasurePolicy 接口的 measure
方法,如果要实现固有特性测量,则还需要重写相应的 Intrinsic 方法,具体来说一共有四个:
override fun IntrinsicMeasureScope.minIntrinsicHeight(measurables: List<IntrinsicMeasurable>, width: Int) override fun IntrinsicMeasureScope.maxIntrinsicHeight(measurables: List<IntrinsicMeasurable>, width: Int) override fun IntrinsicMeasureScope.minIntrinsicWidth(measurables: List<IntrinsicMeasurable>, height: Int) override fun IntrinsicMeasureScope.maxIntrinsicWidth(measurables: List<IntrinsicMeasurable>, height: Int)
不一定全部都要实现,根据具体的需求,需要用到哪种固有特性测量,实现哪种方法即可。例如竹子这里实现了一个自定义的 Column 组件,实现了 minIntrinsicWidth
方法:
// code 9 // 自定义 Column 组件 @Composable fun MyColumn( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Layout( modifier = modifier, content = content, measurePolicy = object: MeasurePolicy { override fun MeasureScope.measure( measurables: List<Measurable>, constraints: Constraints ): MeasureResult { // 自定义 Layout 实现测量与摆放的逻辑 var height = 0 // 自定义 Layout 高度 var width = 0 // 自定义 Layout 宽度 val placeables = measurables.map { val placeable = it.measure(constraints) height += placeable.height width = max(width, placeable.width) placeable } return layout(width, height) { var lastHeight = 0 placeables.map { it.placeRelative(0, lastHeight) lastHeight += it.height } } } override fun IntrinsicMeasureScope.minIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int ): Int { TODO("Not yet implemented") } override fun IntrinsicMeasureScope.maxIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int ): Int { TODO("Not yet implemented") } override fun IntrinsicMeasureScope.minIntrinsicWidth( measurables: List<IntrinsicMeasurable>, height: Int ): Int { var maxWidth = 0 measurables.forEach { maxWidth = max(maxWidth, it.minIntrinsicWidth(height)) } return maxWidth } override fun IntrinsicMeasureScope.maxIntrinsicWidth( measurables: List<IntrinsicMeasurable>, height: Int ): Int { TODO("Not yet implemented") } }) }
这样就可以在 MyColumn 组件的 Modifier 中使用 IntrinsicSize 了,具体使用及显示效果如下:
// code 10 MyColumn(modifier = Modifier.width(IntrinsicSize.Min)) { Text(text = "watermelon") Text("apple") Divider(color = Color.Black, modifier = Modifier.height(2.dp).fillMaxWidth()) Text("orange") }
如果不使用 Modifier.width(IntrinsicSize.Min)
固有特性测量,则显示的效果就会是这样的:
所以如果需要自定义 Layout 适配固有特性测量,则需要实现相应的方法,个人觉得还是挺麻烦的。。。此外,在 code 9 中 minIntrinsicWidth
和 measure
方法中分别打上断点,可以发现,Compose 确实是在 measure 父组件前,就先调用了 minIntrinsicWidth
方法去获取了子组件的宽高。
而且这里还有个 bug,如果设置的文案既有中文又有英文,则会换行。。。包含空格符也会换行,有兴趣的同学可以试一下。不知道 Compose 何时才能修复这个 bug~
总结
Compose 为了避免传统 View 体系重复测量导致的性能问题,规定了只能测量一次子组件的规则,否则会出现运行时异常。但是在有些需要多次测量的使用场景,Compose 提出了设置固有特性测量的解决方案。固有特性测量的设置,就是允许父组件在正式测量自身宽高前,去获取子组件的宽高信息,从而确定自己的宽高。从以上的例子可以看出,子组件可以根据自己的宽高信息来决定父组件的宽高信息,从而影响其他子组件的布局和宽高信息。
好了,固有特性测量就介绍到这里,欢迎关注我,解锁更多 Android 开发新知识!
参考文献
- developer.android.google.cn/codelabs/je…
- mp.weixin.qq.com/s/ESJHNzXXj…