今天大概会聊到的范围
- layout modifier
上一次讨论到 Modifier 时,觉得自己其实对物件如何绘制到画面上其实一知半解。今天打算继续研究负责调整布局的 Layout Modifier。
这次我们一样讲 padding
这个 Modifier,padding 是一个 LayoutModifier
。他做的事情是在物件与其他物件之间保留空间。如果 jetpack compose 没有内建 padding
,而是要我们自己实作的话,可以怎麽办呢?
首先,我们先将我们预想的 Preview 写出来
@Preview
@Composable
fun `Preview Modifier pad`(){
Box() {
Text("test", modifier = Modifier.padTop(4.dp))
}
}
因为
padding
要写四个方位,为了简化今天的说明,先以 padding top 为范例。
当然啦,Modifier.padTop()
目前是无法 compile 的,我们需要替 Modifier 写一个 extension function
fun Modifier.padTop(top: Dp = 0.dp) {}
这边提到的 layout modifier 和之前提到的 LayoutModifier
不同,这边提到的是 Modifier 的 layout()
function。Modifier.layout
这个 function 可以用来创造一个客制化的布局(最终仍是产生一个 LayoutModifier
) 。
fun Modifier.padTop(top: Dp = 0.dp) = layout { measurable, constraints ->
// MeasureScope
}
让我们来看看 layout 长什麽样子
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this.then(
LayoutModifierImpl(
measureBlock = measure,
inspectorInfo = debugInspectorInfo {
name = "layout"
properties["measure"] = measure
}
)
)
layout
本身会将 this
与新产生的 LayoutModifier
透过 then
串接起来,因此我们不需要额外做串接。
另外,我们可以看到 layout
接受的参数是一个以 MeasureScope
为 receiver 的 lambda,这个 lambda 需要回传 MeasureResult
。并且,在这个 lambda 中,可以拿到 Measurable
与 Constraints
两个参数。
这些都和之前聊的一样:
Modifier.layout
会先取得一个 Measurable
,这是一个还不知道大小的 child (通常是正在 modify 的物件)Constraints
, 这个是前面各种运算後留下来,这个 layout 自己可以占的空间Measurable
在执行 measure()
後,可以知道大小,变成一个知道大小但是还没摆放的 Placable
Placable
在 PlacementScope
中可以摆放Placeable
後,就可以知道自身的大小 ( MeasureResult
)了解这样的概念後,我们就可以来创造自己的 layout modifier 了!
首先,我们先做完基本的流程,并且什麽都不做调整:
fun Modifier.padTop(top: Dp = 0.dp) = this.then(
layout { measurable, constraints ->
// MeasureScope
// 1.
val placeable = measurable.measure(constraints)
// 2.
layout(placeable.width, placeable.height) {
// 3.
// layout function 中,
placeable.placeRelative(0, 0)
}
}
)
第一步( 1. ),我们要测量 child 的大小。在 Compose 的系统中,每个 Child 都会被测量一次(而且只会量一次),测量时需要提供 constraints
让 measurable
知道自己最大可以到多大 。测量完後变成 placeable
可以摆放。
第二步( 2. ),框出自己的大小。在 MeasureScope
中,又有另一个 layout function,需要提供这个 function 预期的长、宽。
第三部( 3. ),实际摆放。 MeasureScope.layout
里是 PlacementScope
,也就是可以摆放 Placable
的地方。在这边可以呼叫 Placable.place
( or placeRelative
) 来进行摆放。
最後 MeasureScope.layout
会回传 MeasureResult
,也是 Modifier.layout
trailing lambda 所需要的 return 值。
到目前为止,我们应该可以写出一个完全无用的 Modifier。接下来,我们要将 top padding 纳入考量。
因此,调整後我们的 Modifier 会变成这样
fun Modifier.padTop(top: Dp = 0.dp) = this.then(
layout { measurable, constraints ->
// MeasureScope
// 0.
val topPx = top.roundToPx()
// 1.
val placeable = measurable.measure(constraints)
// 2.
layout(placeable.width, placeable.height + topPx) {
// 3.
placeable.placeRelative(0, topPx)
}
}
)
我们顺利的建立了一个客制化的 top padding modifier。但是假设今天我们的 Modifier 很特别,只在特殊的场景适用,不希望其他人在其他场景误用的话,该怎麽办呢?
这边要使用讲到之前说过的 Modifier Scope 概念。建立一个 Scope 并且 Modifier 只在这个范围内能呼叫的到、能产生作用。
在那之前,我们先定义一个 Composable,作为我们限缩 Modifier 的 container。
@Composable
fun MyLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Surface(modifier = modifier) {
content()
}
}
很单纯的 Composable,基本上只是将 content 包装在 Surface 内而已。
接下来才是有趣的部分,我们需创造一个 Scope 来限缩 modifier。
interface MyScope
// ---
@Composable
fun MyLayout(
modifier: Modifier = Modifier,
content: @Composable MyScope.() -> Unit // << 改成以 MyScope 做 receiver
) {
将 Modifier 的定义搬进 MyScope 中:
interface MyScope {
fun Modifier.padTop(top: Dp = 0.dp): Modifier
}
因为是 interface,所以这边还不会有实作。但是将 Modifier.padTop 放在 MyScope 中,我们就可以确保今天不是在 MyLayout 的 composable 都不会有这个 Modifier。
接下来,我们将 MyScope 实作,并且将 padTop 的实作也放进 MyScope 实作中。
object MyScopeInstance: MyScope {
override fun Modifier.padTop(top: Dp) = layout { measurable, constraints ->
// ...
}
}
最後,因为 MyLayout 的 content 需要一个 receiver。因此我们用 MyScopeInstance 作 receiver 呼叫他
@Composable
fun MyLayout(
modifier: Modifier = Modifier,
content: @Composable MyScope.() -> Unit
) {
Surface(modifier = modifier) {
MyScopeInstance.content() // << 改以 MyScopeInstance 呼叫
}
}
这次模仿实作一次,其实大多都是跟着官方文件实作的。但这个实作等於是将之前试图了解的 Layout 流程与 Modifier Scope 观念全部串在一起,算是不错的练习。
本文内容 接续,Day27 的内容,纪录阅读有关 Angular Route 的 pathMatch...
更新资料常⽤的有 save 、 update 、 update_attribute 及 update...
昨天,我们把分类函数算法算完,那今天,我打算建立决策树: 有了第一个最佳分类点和数值後,接下来就要找...
今天是铁人赛的第一天,这是我第一次参赛,之前听了很多同学说这是个需要有毅力的比赛,我相信我一定能够撑...
本篇文章同步发表在 HKT 线上教室 部落格,线上影音教学课程已上架至 Udemy 和 Youtu...