在上一篇文章(深入Jetpack Compose——布局原理与自定义布局(二)中,我们探索了Modifier
的本质和原理。这一次我们看看Compose体系中的一个重要特性:固有特性测量
。
固有特性测量
或许不少人已经知道,Compose为了提高测绘性能,强行规定了每个微件只能被测量一次。也就是说,我们不能写出类似下面这样的代码:
val placeables = measurables.map { it.measure(constrains) }
// 尝试测量第二次,直接报错
val placeablesSecond = measurables.map { it.measure(constrains) }
一个小问题
那么接下来我们看一个小例子。我们想实现一个菜单,菜单里面有几个菜单栏。于是我们写出了类似这样按的代码
但是效果不怎么样,因为每个Text
的宽度不一样。看起来有点丑
你可能会说,要解决这个问题很简单,为每个Text
添加修饰符fillMaxWidth
,让它占满即可。效果如下:
但是这样新的问题来了:由于每个Text
的Constraint
的maxWidth
都是最大值,于是咱们的Column
宽度也是最大值。于是这个菜单占满了全部屏幕空间。这可不妙!
要解决这个问题,我们只需要为Column
添加这样一个修饰符
Modifier.width(IntrinsicSize.Max)
它的宽度就是子微件宽度的最大值啦
有Max
应该就有Min
,咱们试试?
宽度变窄了!很神奇吗?这就是固有特性测量的功劳。
(如果你好奇为什么最小宽度是这个,因为子微件是文本,而文本的最小宽度是它每行能容纳一个词时的宽度。在这个例子中,就是Send Feedback分成 Send \n Feedback时Feedback这行字的宽度)
上面的例子中,Column
就适配了固有特性测量这一特性。接下来,我们把自己的实现的VerticalLayout
也来适应一下(VerticalLayout具体实现见第一篇)。
适配固有特性测量
让我们重新把目光转向Layout
@Composable inline fun Layout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
)
之前对于第三个参数,我们是写成了SAM的形式。我们现在再来看看这个MeasurePolicy
@Stable
fun interface MeasurePolicy {
fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult
/**
* The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth]. It represents
* the minimum width this layout can take, given a specific height, such that the content
* of the layout can be painted correctly.
*/
fun IntrinsicMeasureScope.minIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int
fun IntrinsicMeasureScope.minIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
): Int
fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int
fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
): Int
}
measure
方法是我们之前就用过的,而其余几个拓展函数就是我们要适配 固有特性测量
所需要重写的啦。举个栗子,使用 Modifier.width(IntrinsicSize.Max)
,则会调用 maxIntrinsicWidth
方法,其余同理。
接下来,咱们开干。先挑一个吧
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int {
TODO("Not yet implemented")
}
我们以子微件宽度的最大值作为最大约束
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int {
var width = 0
measurables.forEach {
val childWidth = it.maxIntrinsicWidth(height)
if(childWidth > width) width = childWidth
}
return width
}
效果如下:
min的情况也差不多,效果如下:
完整代码参见Github仓库
后续
关于固有特性测量我们就先看这些。下一篇,我们将探索ParentData
和其它特性,继续我们的布局之旅
本文参考:
- 聊一聊Compose的固有特性测量Intrinsic - 掘金 (juejin.cn)
- Android官方视频:Deep dive into Jetpack Compose layouts
本文所有代码见:此处