網格漸層

網格漸層會使用 2D 格線的修補程式,建立複雜的多向顏色轉換。與線性或放射漸層不同,網格漸層會在格線中平滑地插補顏色。使用網格漸層在使用者介面中建立流暢且自然的 美觀元素。

網格漸層範例,顯示目前的網格漸層點。
圖 1. 顯示目前網格漸層點的網格漸層範例。

核心概念

如要建構網格漸層,請定義格線尺寸、頂點,以及點之間的顏色轉換:

  • 格線尺寸:網格會沿著垂直和水平軸分割成修補程式。rowscolumns 的格線包含 (列 + 1) ×(欄 + 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. 動畫網格漸層,附有顯示動畫的點。