[译]Jetpack Compose 中的网格渐变

网格渐变

前言

最近,我开设了一个新的 YouTube 频道,并发布了一个关于网格渐变的视频。在视频中,我展示了如何在 Jetpack Compose 中使用 drawVertices() 函数创建平滑的渐变。你可以在 这里观看

正文

在这篇配套文章中,我们将介绍如何使用生成的 compose modifier 来创建和制作你自己的渐变动画。

要跟随本教程,请从这里获取 modifier 代码。

该 modifier 接收 points 作为参数,这是一个包含我们的点和颜色的二维数组。

每个点都使用 Offset 在二维空间中表示。这些点会响应我们应用渐变的视图尺寸。因此,范围 0f..1f 将对应于视图的边界。但在某些设计中,我们可以超出这个范围,定义一个具有视图边界之外点的渐变。每个点都与一个颜色配对。

让我们看一个代码示例。

Box(  
    Modifier  
        .padding(32.dp)  
        .size(600.dp)  
        .meshGradient(  
            points = listOf(  
                listOf(  
                    Offset(0f, 0f) to Teal950,  
                    Offset(.5f, 0f) to Teal950,  
                    Offset(1f, 0f) to Teal950,  
                ),  
                listOf(  
                    Offset(0f, .5f) to Indigo700,  
                    Offset(.5f, .5f) to Indigo700,  
                    Offset(1f, .5f) to Indigo700,  
                ),  
                listOf(  
                    Offset(0f, 1f) to Pink500,  
                    Offset(.5f, 1f) to Pink500,  
                    Offset(1f, 1f) to Pink500,  
                ),  
            ),  
        )  
)

在这里,我们传入一个 3x3 网格,其中同一行的点被分配相同的颜色。

如果我们运行这个,我们会得到一个熟悉的垂直渐变,如下所示:

基本网格渐变,类似于普通的垂直渐变

这个结果并不特别,因为我们已经可以用更少的代码制作垂直渐变。让我们通过向下移动中间点的 y 轴来让它更有趣。

listOf(  
    Offset(0f, .5f) to Indigo700,  
    Offset(.5f, .9f) to Indigo700,  
    Offset(1f, .5f) to Indigo700,  
)

这里,我们将最中间的点从 .5f 移动到 .9f。移动这个点会让我们的渐变看起来像这样:

一个点向下移动的网格渐变

我们成功地移动了这个点,但现在我们的渐变看起来有点不对齐,有点"块状",这看起来不太美观。相反,我们希望点之间的过渡更平滑。

为了解决这个问题,我们将利用 modifier 提供的另外两个参数:resolutionXresolutionY。 modifier 能够接收提供的点,并在每个点之间创建平滑的过渡路径。这两个参数定义了在这个路径上采样的点数。

尽量保持分辨率尽可能低,同时仍然保持你想要的设计。使用高分辨率会导致性能损失。 此外,分辨率按轴分离,让你能更好地控制以获得最佳性能。这是因为某些设计可能需要一个轴上的高分辨率,而另一个轴不需要。

例如,在我们的情况下,我们只需要增加 X 轴的分辨率,因为我们正在移动一条水平线。因此,让我们将 resolutionX 设置为 32,并保持 resolutionY 为默认值。

.meshGradient(  
    points = listOf(  
        ...
    ),  
    resolutionX = 32,  
)

这样会给我们一个带有斜坡曲线的渐变。

带有斜坡曲线的平滑网格渐变

你可以尝试移动其他点,看看还能创造出什么有趣的设计。

除了设置点的位置,我们还可以为它制作动画。

使用 Jetpack Compose 提供的动画工具可以轻松实现这一点。

在这个例子中,我将使用 Animatable 来让中心点沿 y 轴移动。

val animatedPoint = remember { Animatable(.8f) }  

LaunchedEffect(Unit) {  
    while (true) {  
        animatedPoint.animateTo(  
            targetValue = .1f,  
            animationSpec = spring(stiffness = Spring.StiffnessVeryLow)  
        )  
        animatedPoint.animateTo(  
            targetValue = .9f,  
            animationSpec = spring(stiffness = Spring.StiffnessVeryLow)  
        )  
    }  
}

首先,我们创建一个初始值为 .8fAnimatable

接下来,在 LaunchedEffect 内,我们让 animatedPoint 在 .1f.9f 之间循环动画。

最后,我们只需要用 animatedPoint 的浮点数替换中心 Offset 的 y 值

.meshGradient(  
    points = listOf(  
        listOf(  
            Offset(0f, 0f) to Teal950,  
            Offset(.5f, 0f) to Teal950,  
            Offset(1f, 0f) to Teal950,  
        ),  
        listOf(  
            Offset(0f, .5f) to Indigo700,  
            Offset(.5f, animatedPoint.value) to Indigo700, 
            Offset(1f, .5f) to Indigo700,  
        ),  
        listOf(  
            Offset(0f, 1f) to Pink500,  
            Offset(.5f, 1f) to Pink500,  
            Offset(1f, 1f) to Pink500,  
        ),  
    ),  
    resolutionX = 32,  
)

这将使我们的中心点上下移动,为我们的渐变创建这样的动画。

中心点上下移动的网格渐变

除了点的位置,我们还可以让颜色产生动画。让我们为底部行的每个点创建 3 个 Animatable。

val leftColor = remember { Animatable(initialValue = Pink500) }  
val middleColor = remember { Animatable(initialValue = Pink500) }  
val rightColor = remember { Animatable(initialValue = Pink500) }  
LaunchedEffect(Unit) {  
    val colors = listOf(Pink500, Violet600, Fuchsia400, Emerald500)  
    fun animate(color: Animatable<Color, AnimationVector4D>) {  
        launch {  
            while (true) {  
                color.animateTo(  
                    targetValue = colors.random(),  
                    animationSpec = tween(  
                        durationMillis = Random.nextInt(300, 500)  
                    )  
                )  
            }  
        }  
    }  
    listOf(leftColor, middleColor, rightColor).map { animate(it) }  
}

所有三个的初始值都将是之前的亮粉色。但在 LaunchedEffect 内,我们将循环遍历随机选择的颜色。

要应用这个,我们只需要用这些 Animatable 的值替换静态颜色。

.meshGradient(  
    points = listOf(  
        listOf(  
            Offset(0f, 0f) to Teal950,  
            Offset(.5f, 0f) to Teal950,  
            Offset(1f, 0f) to Teal950,  
        ),  
        listOf(  
            Offset(0f, .5f) to Indigo700,  
            Offset(.5f, animatedPoint.value) to Indigo700,  
            Offset(1f, .5f) to Indigo700,  
        ),  
        listOf(  
            Offset(0f, 1f) to leftColor.value,  
            Offset(.5f, 1f) to middleColor.value,  
            Offset(1f, 1f) to rightColor.value,  
        ),  
    ),  
    resolutionX = 32,  
    resolutionY = 32,  
)

这将给我们这样的动画:

底部行颜色随机变化的网格渐变

现在,你已经了解了使用这个 modifier 的基础知识,你可以将它应用到许多其他用例中,而不仅仅是循环动画。例如,你可以让点和颜色的动画来指示状态变化,就像这个"登录"按钮:

带有根据状态变化的网格渐变的登录按钮示例

在这里查看这个按钮的代码和在线预览:网格渐变按钮

如果你有机会使用这个 modifier,欢迎给我一些反馈。在不久的将来,我计划创建一个多平台库,其中将包含这个功能,以及更多用于调整渐变的功能。

感谢阅读,祝你好运!

衍生

Quote

来自 @cisnco

受这篇文章的启发,我用 Compose Desktop 构建了一个小工具,可以更轻松地迭代构建精美的网格渐变,基于 @sinasamaki 的 Modifier 配方!

代码在这里 Mesh Tool,代码导出假设你使用相同的 Modifier 配方

文章目录