網格漸層

網格漸層會使用 2D 網格色塊,建立複雜的多向色彩轉換。與線性或放射狀漸層不同,網格漸層會在 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 會在畫布上繪製。

建立簡單的網格漸層

如要建立基本的靜態網格漸層,請初始化 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. 貝茲控制點和波浪色彩的網格漸層,以及網格點在上方。

為網格漸層加上動畫

由於 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. 動畫網格漸層,並以點顯示動畫。