网格渐变

网格渐变使用二维色块网格创建复杂的多向颜色过渡。与线性或径向渐变不同,网格渐变可在 2D 网格中平滑地插值颜色。在界面中创建流畅、自然或多点背景时,请使用网格渐变,而不是堆叠多个 radialGradient 或 linearGradient 笔刷。

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

主要概念

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

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

使用 MeshGradientPainter 绘制

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

创建简单的网格渐变

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

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. 带有贝塞尔控制点和波浪颜色的网格渐变,以及在网格渐变上显示的网格点。

为网格渐变添加动画效果

由于 MeshGradientPainter 的 block 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. 带有动画效果的网格渐变,其中包含用于显示动画的点。