D09 / 为什麽我的按钮这麽长? - Intrinsic measurements

今天大概会聊到的范围

  • Intrinsic measurements

今天的标题可能会让人有点疑惑,但这是我写出这段 Code 时的第一反应。

Column(
        verticalArrangement = Arrangement.spacedBy(2.dp),
        modifier = Modifier
            .background(Color.White)
            .padding(4.dp)
    ) {

        Text(text = "Title", style = titleStyle)
        Text(text = "Looooooooooooooog Text")
        
        Spacer(modifier = Modifier.size(16.dp))
        
        Button(onClick = {}, modifier = Modifier.fillMaxWidth()) {
            Text(text = "GO")
        }
        
    }

这是一个 Dialog,在这个 Dialog 中,我希望整个 Dialog 和最宽的 Text 一样宽,底部的 Button 可以是一个横向、占据整个底部的按钮。直觉上,因为 Button 预期是最宽的状态,所以我写了 fillMaxWidth 。但我最後得到的画面却是这样:

https://ithelp.ithome.com.tw/upload/images/20210923/20141597kAFnQs1OFD.png

按钮就真的给我 fillMaxWidth 了,但因为没有任何 constraint ,所以按钮就一路延伸到和装置的画面一样宽。

要解决这个问题前,要先了解 Compose 在测量 child 的大小时只会测量一次。就如同上一次聊到的一样,每个 parent 在测量自己的大小前,都会先测量一次 child 的大小。都测量完之後,parent 才会知道自己多大。但这边就出现了一个问题,因为测量 Button 时,parent 并不知道自己多大,也没有特别的 constraint 传给 Button,因此 Button 就回给 parent 自己要要和 max width 一样大。

当然,我们可以在 parent ( Column 这一层 ) 写死固定的 width 来设定 Dialog 大小,但这就没办法符合动态和最宽的文字一样宽的需求。

Intrinsic measurements

这边就要介绍到 Intrinsic measurements。Compose 的系统中设计了一个可以让 parent 可以先知道 child 可能会多大。

每一个 Composable 都会有四个资料

IntrinsicMeasureScope.minIntrinsicWidth
IntrinsicMeasureScope.minIntrinsicHeight
IntrinsicMeasureScope.maxIntrinsicWidth
IntrinsicMeasureScope.maxIntrinsicHeight

( min | max )Intrinsic( Width | Height ) 都是在 InstrinsicMeasureScope 上,至於被使用的时机晚点会提到。因为在实际使用时,我们不会需要手动去取得这四个资料。而是透过 IntrinsicSize.(Max | Min) 来表示最大 or 最小

    Column(
        verticalArrangement = Arrangement.spacedBy(2.dp),
        modifier = Modifier
            .background(Color.White)
            .padding(4.dp)
            .width(IntrinsicSize.Max)  // 将 width 设定为测量前最大的 child 大小
    ) {
        Text(text = "Title", style = titleStyle)
        Text(text = "Looooooooooooooog Text")
        
        Spacer(modifier = Modifier.size(16.dp))
        
        Button(onClick = {}, modifier = Modifier.fillMaxWidth()) {
            Text(text = "GO")
        }        
    }

当你将 height/width 等设定成 IntrinsicSize.(Max | Min) 後,实际上会在 modifier chain 中增加一个 IntrinsicSizeModifier 。这个 Modifier 是一个 LayoutModifer,在计算 constraint 时会去参考 composable 提供的 intrinsic measurement。

// 设定 width(IntrinsicSize.Max) 时,就会在 modifier 中增加一个 MaxIntrinsicWidthModifier

private object MaxIntrinsicWidthModifier : IntrinsicSizeModifier {

    // modifier 中,会覆写 constraint 的计算逻辑
    override fun MeasureScope.calculateContentConstraints(
        measurable: Measurable,
        constraints: Constraints
    ): Constraints {

        // 计算时,会参考 measurable 的 intrinsic size
        // 就是上面提到的 ( min | max )Intrinsic( Width | Height )
        val width = measurable.maxIntrinsicWidth(constraints.maxHeight)   
        return Constraints.fixedWidth(width)
    }
}

最後我们就可以产出一个以内容为大小依据的 Dialog,包含一个填满 Dialog 的按钮。

https://ithelp.ithome.com.tw/upload/images/20210923/20141597wIaVaPpTf4.png


今天实际的 code 其实不会很复杂,逻辑也很好理解。只是研究时发现和之前聊到的 Layout 逻辑整个串在一起,觉得很有趣。Compose 有很好的 render 逻辑,让 measure 的次数降到最低。Intrinsic measurement 则是在这个结构下产生的好用工具。


Reference:


<<:  Day8 - 布署 GitHub 程序与串接聊天机器人 LINE Messaging API

>>:  Days11

【第19天】训练模型-验证与比较训练成果

摘要 Test资料集验证 1.1 单张图档预测 1.2 多张图档预测 五个模型的准确度对照表 心得 ...

Unity与Photon的新手相遇旅途 | Day6-粒子效果应用范例

今天介绍两个粒子效果应用范例,一个是透过按键控制粒子效果生成以及删除,第二个是制作炸弹炸物体产生的效...

[Day 02] Why MLOps — 从"地平说" 走向宇宙

Machine learning is now a product engineering dis...

今日爬山中

初版待更新(还是翻新),9月19日中秋节前夕,爬山旅行中。 网路收讯极差,慌张的寻找网路与讯号点。 ...

Day04 关於分散式系统的一些概念 (一)

今天开始要讲分散式系统的一些概念罗。 影片在此: Day04_关於分散式系统的一些概念 (一) ...