12、修饰符

26 阅读3分钟

Compose 修饰符

修饰符允许您修饰或扩充可组合项。通过修饰符,您可以:

  • 更改可组合项的大小、布局、行为和外观
  • 添加信息,如无障碍标签
  • 处理用户输入
  • 添加高级互动,如使元素可点击、可滚动、可拖动或可缩放

基本使用

修饰符是标准的 Kotlin 对象,通过调用 Modifier 类函数创建:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

可以将多个修饰符函数链式连接:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

最佳实践是让所有可组合项接受 modifier 参数,并将其传递给第一个子项,以提高代码可重用性。

修饰符顺序很重要

修饰符函数的顺序非常重要,因为每个函数都会对上一个函数返回的 Modifier 进行更改:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // 整个区域(包括内边距)都是可点击的
    }
}

如果顺序相反,内边距区域将不会响应点击:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // 仅内容区域可点击,内边距不可点击
    }
}

常见内置修饰符

padding 和 size

默认情况下,Compose 布局会封装其子项,但您可以使用 size 修饰符设置尺寸:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

如果指定尺寸不符合父项约束,可以使用 requiredSize 修饰符强制设置:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

使用 fillMaxHeight 使子项填充父项的所有可用高度:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

在文本基线上方添加内边距:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

偏移

使用 offset 修饰符相对于原始位置放置布局:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Compose 中的作用域安全

某些修饰符只能应用于特定可组合项的子项。Compose 通过自定义作用域强制实施此安全机制。

matchParentSize(在 Box 中)

使子项与父项 Box 尺寸相同,而不影响 Box 尺寸:

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

Row 和 Column 中的 weight

使用 weight 修饰符使可组合项在父容器内按比例分配空间:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

提取和重复使用修饰符

将修饰符链提取到变量中,以便在多个可组合项中重复使用:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

在观察频繁变化的状态时提取修饰符

在动画等频繁重组的场景中,提取修饰符可避免不必要的分配:

// 不好的做法:每次重组都会重新分配修饰符
@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)
    LoadingWheel(
        // 每帧动画都会重新创建此修饰符!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}
// 好的做法:提取修饰符
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)
    LoadingWheel(
        // 重复使用同一实例,无额外分配
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

重复使用未限定作用域的修饰符

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

重复使用限定作用域的修饰符

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // align 修饰符需要 ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

进一步链接提取的修饰符

使用 .then() 函数将提取的修饰符与其他修饰符链接:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// 追加到可重用修饰符
reusableModifier.clickable { /*...*/ }

// 将可重用修饰符追加到其他修饰符
otherModifier.then(reusableModifier)

注意:修饰符的顺序很重要,会影响最终效果。