网格渐变

网格渐变使用补丁的二维网格创建复杂的多向颜色过渡。与线性渐变或径向渐变不同,网格渐变会在网格中平滑地插值颜色。使用网格渐变在界面中创建流畅且自然的审美元素。

网格渐变示例,显示了其当前的网格渐变点。
图 1. 网格渐变示例,其中显示了其当前的网格渐变点。

主要概念

如需构建网格渐变,请定义网格尺寸、顶点和点之间的颜色过渡:

  • 网格尺寸: 网格沿垂直轴和水平轴拆分为补丁。rowscolumns 的网格包含 (rows+1)×(columns+1) 个顶点。例如,1×1 网状网由 4 个顶点组成,形成一个补丁。
  • 归一化坐标: 所有顶点位置都使用归一化坐标系,其中 (0f, 0f) 表示绘制边界的左上角,(1f, 1f) 表示绘制边界的右下角。
  • 贝塞尔控制点(切线): 每个顶点最多包含四个可选的贝塞尔控制点。这些切线指定相邻顶点之间的边缘曲率。如果您使用 Offset.Unspecified,Compose 会推断切线,以确保跨补丁的平滑过渡。由 4 个顶点及其控制点形成的每个网格单元格都会生成一个贝塞尔补丁。
  • 颜色插值: 框架会计算主要顶点之间的颜色。将 hasBicubicColor 设置为 true 可进行 Catmull-Rom 插值 以实现 更平滑的颜色变化,或设置为 false 可进行双线性插值。

使用 MeshGradientPainter 绘制

在 Jetpack Compose 中,使用 MeshGradientPainter 渲染网格渐变。MeshGradientPainter 在画布上绘制。

创建简单的网格渐变

如需创建基本的静态网格渐变,请初始化 MeshGradientPainter,方法是指定其尺寸,并在配置块内使用 setVertex 函数来定位角点并为其分配颜色。

val rows = 1
val columns = 1

val gradientPainter = remember {
    MeshGradientPainter(rows, columns) {
        // Parameters: row, column, position, color
        setVertex(0, 0, Offset(0f, 0f), Color.Red)     // Top-Left
        setVertex(0, 1, Offset(1f, 0f), Color.Blue)    // Top-Right
        setVertex(1, 0, Offset(0f, 1f), Color.Green)   // Bottom-Left
        setVertex(1, 1, Offset(1f, 1f), Color.Yellow)  // Bottom-Right
    }
}

Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

基本网格渐变,每个角定义了 4 种颜色
图 2. 具有四种颜色的基本网格渐变,每个角都设置为四种颜色之一。

使用特定的贝塞尔控制点

默认情况下,网格生成器会处理复杂的计算,以保持网格过渡平滑。不过,如果您想有选择地推送、拉动或急剧收缩某些颜色部分,可以显式自定义任何单个顶点上的切线。

控制偏移量是相对于宿主顶点的位置来测量的。

val customTangentPainter = remember {
    MeshGradientPainter(rows = 1, columns = 1) {
        // Tweak the top-left vertex to curve outwards to the right and bottom
        setVertex(
            row = 0,
            column = 0,
            position = Offset(0f, 0f),
            color = Color.Magenta,
            rightControlPoint = Offset(0.4f, 0.1f),
            bottomControlPoint = Offset(0.1f, 0.4f)
        )

        // Other points can remain unspecified to use default inferred fallback tangents
        setVertex(0, 1, Offset(1f, 0f), Color.Cyan)
        setVertex(1, 0, Offset(0f, 1f), Color.Blue)
        setVertex(1, 1, Offset(1f, 1f), Color.Black)
    }
}
Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(customTangentPainter)
)

具有弯曲左上角的网格渐变。
图 3. 使用贝塞尔控制点弯曲左上角的顶点。

创建高级网格

此示例显示了一个 3x3 网格,这意味着需要指定 16 个点,并且中间点设置了不同的偏移量:

val points = remember {
    listOf(
        Offset(0.0f, 0.0f), Offset(0.3f, 0.0f), Offset(0.7f, 0.0f), Offset(1.0f, 0.0f),
        Offset(0.0f, 0.3f), Offset(0.2f, 0.4f), Offset(0.7f, 0.2f), Offset(1.0f, 0.3f),
        Offset(0.0f, 0.7f), Offset(0.3f, 0.8f), Offset(0.7f, 0.6f), Offset(1.0f, 0.7f),
        Offset(0.0f, 1.0f), Offset(0.3f, 1.0f), Offset(0.7f, 1.0f), Offset(1.0f, 1.0f)
    )
}

val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, points[0], yellow)
        setVertex(0, 1, points[1], orange)
        setVertex(0, 2, points[2], yellow)
        setVertex(0, 3, points[3], purple)

        // Row 1
        setVertex(1, 0, points[4], pink)
        setVertex(1, 1, points[5], yellow)
        setVertex(1, 2, points[6], pink)
        setVertex(1, 3, points[7], purple)

        // Row 2
        setVertex(2, 0, points[8], indigo)
        setVertex(2, 1, points[9], pink)
        setVertex(2, 2, points[10], purple)
        setVertex(2, 3, points[11], indigo)

        // Row 3
        setVertex(3, 0, points[12], purple)
        setVertex(3, 1, points[13], indigo)
        setVertex(3, 2, points[14], pink)
        setVertex(3, 3, points[15], yellow)
    }
}

Box(
    modifier = modifier.padding(32.dp)
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
        // ...
)

网格渐变,带有贝塞尔控制点和波浪颜色,顶部显示了网格点。
图 4. 具有贝塞尔控制点和波浪颜色的网格渐变,以及在其顶部绘制的网格点。

为网格渐变添加动画效果

由于 MeshGradientPainterblock lambda 形参在 DrawScope 中执行,因此它可以读取和观察可变状态。您可以随时间推移为位置或颜色添加动画效果,而无需重新分配着色器或位图。

val infiniteTransition = rememberInfiniteTransition(label = "meshMovement")
val animatedOffset by infiniteTransition.animateFloat(
    initialValue = -0.1f,
    targetValue = 0.1f,
    animationSpec = infiniteRepeatable(
        animation = tween(2500, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "offset"
)

val coral = Color(255, 90, 90)
val peach = Color(255, 139, 90)
val amber = Color(255, 169, 90)
val sunshine = Color(255, 212, 90)
val indigo = Color(0xFF5856D6)
val pink = Color(0xFFFF2D55)


val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, Offset(0.0f, 0.0f), indigo)
        setVertex(0, 1, Offset(0.3f, 0.0f), peach)
        setVertex(0, 2, Offset(0.7f, 0.0f), amber)
        setVertex(0, 3, Offset(1.0f, 0.0f), sunshine)
        // Row 1
        setVertex(1, 0, Offset(0.0f, 0.3f), pink)
        setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral)
        setVertex(1, 2, Offset(0.7f, 0.2f) + Offset(animatedOffset, animatedOffset), peach)
        setVertex(1, 3, Offset(1.0f, 0.3f), indigo)

        // Row 2
        setVertex(2, 0, Offset(0.0f, 0.7f), coral)
        setVertex(2, 1, Offset(0.3f, 0.8f) + Offset(animatedOffset, 0f), pink)
        setVertex(2, 2, Offset(0.7f, 0.6f) + Offset(animatedOffset, 0f), sunshine)
        setVertex(2, 3, Offset(1.0f, 0.7f), amber)

        // Row 3
        setVertex(3, 0, Offset(0.0f, 1.0f), sunshine)
        setVertex(3, 1, Offset(0.3f, 1.0f), amber)
        setVertex(3, 2, Offset(0.7f, 1.0f), pink)
        setVertex(3, 3, Offset(1.0f, 1.0f), indigo)
    }
}


Box(
    modifier = modifier.padding(32.dp)
        .safeContentPadding()
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

图 5. 具有用于显示动画的点动画网格渐变。