在上一篇文章中,我们了解了 Modifier 的基本概念和使用方式。Modifier 为我们提供了定制 UI 的强大能力,但是官方提供的 Modifier 毕竟是有限的,难免无法满足所有场景的需求。幸运的是,Jetpack Compose 允许我们自定义 Modifier,实现更加个性化的 UI 效果。本篇博客将带你一步步学习如何创建自己的 Modifier。
自定义Modifier的基本步骤
要自定义一个 Modifier,我们需要创建一个 Lambda 表达式,该表达式接收当前的 Composable 实例作为参数,并非返回一个新的 Composable 实例。在这个过程中,我们可以对原始的 Composable 进行任何想要的修改和封装。
以下是自定义 Modifier 的基本步骤:
- 创建一个扩展函数,该函数接收一个 Lambda 表达式作为参数。
- 在 Lambda 表达式中,获取当前的 Composable 实例。
- 根据需求,对 Composable 进行修改和包装,生成一个新的 Composable 实例。
- 将新的 Composable 实例返回。
下面是一个简单的示例,我们将创建一个名为MyBorderModifier
的 Modifier,为 Composable 添加一个边框:
kotlin
fun Modifier.myBorder(
width: Dp = 1.dp,
color: Color = Color.Black,
) = this.drawBehind {
val strokeWidth = width.toPx()
val rect = Rect(
left = strokeWidth / 2,
top = strokeWidth / 2,
right = size.width - strokeWidth / 2,
bottom = size.height - strokeWidth / 2
)
drawRect(
color = color,
topLeft = rect.topLeft,
size = rect.size,
style = Stroke(width = strokeWidth)
)
}
让我们逐步解释这段代码:
-
fun Modifier.myBorder(...)
: 这是一个扩展函数,用于为Modifier
添加一个名为myBorder
的新方法。width
和color
是该方法的两个参数,分别用于指定边框的宽度和颜色,并提供了默认值。 -
this.drawBehind { ... }
:drawBehind
是 Compose UI 框架中Modifier
的一个方法,它允许你在指定的组件的背后绘制一些内容。在这里,我们将在组件的背后绘制一个边框。drawBehind
方法接受一个 lambda 表达式作为参数,该 lambda 表达式中的代码将在绘制过程中执行。 -
val strokeWidth = width.toPx()
: 将边框宽度从Dp
转换为实际的像素值,因为绘制操作需要使用实际的像素值。 -
val rect = Rect(...)
: 定义了一个Rect
对象,表示边框的实际绘制区域。左上角坐标为(strokeWidth / 2, strokeWIdth / 2)
、右下角坐标为(size.width - strokeWidth / 2, size.height - strokeWidth / 2)
。这样做是为了确保边框绘制在组件内部,而不是超出组件的边界。size.width
和size.height
表示组件的实际大小。 -
drawRect(...)
: 调用DrawScope
接口的drawRect
方法来绘制矩形边框。color
参数指定边框的颜色topLeft
参数指定矩形的左上角坐标,使用rect.topLeft
来确保边框位于正确的位置。size
参数指定矩形的大小,使用rect.size
来确保边框的大小正确。style
餐食指定绘制的样式,这里使用Stroke(width = strokeWidth)
来绘制一个空心的矩形边框,而不是填充的矩形。strokeWidth
指定了边框的宽度。
使用自定义 Modifier 非常简单,只需要像使用其他 Modifier 一样,将它链接到 Composable 上即可:
kotlin
@Preview(showBackground = true)
@Composable
private fun TestModifierBorder() {
Box(
modifier = Modifier
.myBorder(width = 4.dp, color = Color.Red)
.padding(16.dp)
) {
Text("Hello Modifier")
}
}
通过上面的代码,我们为 Box 添加了一个看都为
自定义Modifier的高级用法
虽然上面的实例比较简单,但是自定义 Modifier 的能力远不止于此。事实上,我们可以在 Modifier 中执行任何想要的逻辑,包括状态管理、动画效果、事件处理等等。下面是一些高级用法的示例:
1. 状态管理
我们可以在 Modifier 中维护一个状态变量,并根据状态的变化来改变 UI 的效果。例如,下面的代码可以实现了一个可点击的边框效果:
kotlin
fun Modifier.clickableBorder(
initialColor: Color = Color.Gray,
clickedColor: Color = Color.Blue,
): Modifier = composed {
var clicked by remember { mutableStateOf(false) }
val borderColor by animateColorAsState(
label = "borderColor",
targetValue = if (clicked) clickedColor else initialColor
)
this.border(2.dp, borderColor)
.clickable {
clicked = !clicked
}
}
这段代码中,我们创建了一个clicked
,它维护了一个borderColor
状态,当用户点击时,边框的颜色会通过动画过渡到clickedColor
。在 Modifier 中管理装填,可以让我们实现各种复杂的 UI 效果。
2、动画效果
Modifier 中不仅可以管理状态,还可以实现动画效果。例如,下面的代码实现了一个呼吸灯效果:
kotlin
fun Modifier.breathingLight(
color: Color = Color.Red,
duration: Int = 1000
): Modifier = composed {
val infiniteTransition = rememberInfiniterTransition()
val alpha by infiniteTransition.animateFloat(
initialValue = 0.3f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = duration, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
), label = "alpha"
)
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 2f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = duration),
repeatMode = RepeatMode.Reverse
), label = "scale"
)
this.graphicsLayer {
scaleX = scale
scaleY = scale
}.background(color.copy(alpha = alpha))
}
在这个示例中,我们使用了rememberInfiniteTransition
来创建一个无限循环的动画。通过animateFloat
函数,我们可以获得一个在 1f 到 2f 之间循环变化的缩放值,然后我们在graohicsLayout
中应用这个缩放值,一个在0.3f 到 1f 之间循环变化的透明度值,然后我们在background
中应用这个透明度值,从而实现了上面的呼吸效果。