终于可以写写技术文了~ 最近忙着各种总结,想必大家也是一样的吧?今年年初的规划,现在完成的怎么样了呢?是不是也像我一样“虎头蛇尾”?哈哈!至少竹子比去年进步了不少,这是今年的最后一篇啦!希望2022年大家一起加油!一起进步!
这一篇是为了填上一篇学习笔记三中提到的 Compose 也可多次测量的“坑”,那就是固有特性测量。
Google 起的这名字个人感觉太不直观了,第一次看到这个官方的翻译真的让我一头雾水,这是个啥?其实,这个东西主要作用就是,调节需要展示的 Composable 组件的宽高大小。
固有特性测量的基本用法
前面文章中也提到了,Compose 有一项规则,即子元素只能测量一次,测量两次就会引发运行时异常。但是,有时又需要先收集一些关于子组件的信息,然后再测量父组件。那么,借助固有特性,就可以先查询子组件,然后再进行实际测量。下面是一个栗子。
假如需要像下面展示的那样:
根据之前讲的布局内容,我们很容易就可以写出如下代码:
// code 1 @Composable fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) { Row(modifier = modifier) { Text( modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.CenterHorizontally), text = text1 ) Divider(color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp)) Text( modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.CenterHorizontally), text = text2 ) } }
text1 和 text2 设置为 “Hello”、“World”。实际展示居然是这样的:
嗯??怎么中间的分割线“放飞自我”了?这是因为 Row 没有对它的子组件的测量做任何限制,而 Divider 的高度设置的是 fillMaxHeight
,它会尽可能撑大父布局。那么要达到我们想要的效果,就需要使用固有特性 IntrinsicSize
先规定一下测量的方式,这里需要将 Row 的 height 设置为 IntrinsicSize.Min
,即把 Row 的高度调整为尽可能小的固有高度。具体代码如下:
// code 2 @Composable fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) { Row(modifier = modifier.height(IntrinsicSize.Min)) { ... } }
具体是如何做到的呢?实际上,是因为 Row 父组件通过 IntrinsicSize
预先获取到了它左右两边的 Text 组件的高度信息了,然后计算出了两个 Text 组件的高度最大值作为它自己的高度值,最后将分割线的高度铺满整个父组件。
为了实现父组件能预先获得子组件宽高信息从而确定自身宽高信息,Compose 为开发者提供了固有特性测量机制,允许开发者在每个子组件正式测量前能获得各个子组件的宽高等信息。
那么,这玩意儿是怎么实现的呢?
很遗憾竹子没有翻到源码,哪位大神如果找到的话,欢迎一起交流~
虽然没有找到源码,但是也知道了一些关键点。下面是找源码未遂的过程,不感兴趣的同学可以跳过。。
固有特性测量实现的关键点
从使用的地方开始,从 code 2 的 height(IntrinsicSize.Min)
进入,到了 Intrinsic.kt 中的一个静态内部类中:
// code 3 private object MinIntrinsicHeightModifier : IntrinsicSizeModifier { override fun MeasureScope.calculateContentConstraints( measurable: Measurable, constraints: Constraints ): Constraints { val height = measurable.minIntrinsicHeight(constraints.maxWidth) return Constraints.fixedHeight(height) } override fun IntrinsicMeasureScope.maxIntrinsicHeight( measurable: IntrinsicMeasurable, width: Int ) = measurable.minIntrinsicHeight(width) }
这个静态内部类又是实现了 IntrinsicSizeModifier
这个接口,而这个 IntrinsicSizeModifier
接口实际上又是实现了 LayoutModifier
接口:
// code 4 private interface IntrinsicSizeModifier : LayoutModifier { val enforceIncoming: Boolean get() = true fun MeasureScope.calculateContentConstraints( measurable: Measurable, constraints: Constraints ): Constraints override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val contentConstraints = calculateContentConstraints(measurable, constraints) val placeable = measurable.measure( if (enforceIncoming) constraints.constrain(contentConstraints) else contentConstraints ) return layout(placeable.width, placeable.height) { placeable.placeRelative(IntOffset.Zero) } } override fun IntrinsicMeasureScope.minIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ) = measurable.minIntrinsicWidth(height) override fun IntrinsicMeasureScope.minIntrinsicHeight( measurable: IntrinsicMeasurable, width: Int ) = measurable.minIntrinsicHeight(width) override fun IntrinsicMeasureScope.maxIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ) = measurable.maxIntrinsicWidth(height) override fun IntrinsicMeasureScope.maxIntrinsicHeight( measurable: IntrinsicMeasurable, width: Int ) = measurable.maxIntrinsicHeight(width) }
综合 code 3 和 code 4 可以看出,核心的方法就是 measurable.minIntrinsicHeight()
这一类的方法。比如在 code 3 中,重写的 MeasureScope.calculateContentConstraints
方法和 IntrinsicMeasureScope.maxIntrinsicHeight
方法,最重要的部分都是调用了 measurable.minIntrinsicHeight()
方法。这个 minIntrinsicHeight
都会传一个 width 参数进去,点进去,发现是一个 IntrinsicMeasurable
接口,接口方法的说明如下所示:
// code 5 IntrinsicMeasurable.kt /** * Calculates the minimum height that the layout can be such that * the content of the layout will be painted correctly. */ fun minIntrinsicHeight(width: Int): Int
方法的注释写的很清楚:计算能正确绘制 layout 内容时的 layout 的最小高度。OK,因为每个 Composable 组件摆放子组件的方式不同,所以每个组件的 IntrinsicMeasurable
接口的实现方式就不同了,但是没有找到 Row 组件具体实现的源码。。此路不通,看有没有其他的路,在上篇笔记三中,我们知道 Composable 组件计算自身宽高是在 Layout 方法中进行的,那么从 Layout 处入手看会怎样呢?
从 Row 的 Layout 方法进入到 Layout.kt,测量的部分肯定是在 MeasurePolicy 类中:
// code 6 Layout.kt @Composable inline fun Layout( content: @Composable () -> Unit, modifier: Modifier = Modifier, measurePolicy: MeasurePolicy ) { ······ }