網格漸層會使用 2D 格線的修補程式,建立複雜的多向顏色轉換。與線性或放射漸層不同,網格漸層會在格線中平滑地插補顏色。使用網格漸層在使用者介面中建立流暢且自然的 美觀元素。
核心概念
如要建構網格漸層,請定義格線尺寸、頂點,以及點之間的顏色轉換:
- 格線尺寸:網格會沿著垂直和水平軸分割成修補程式。
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) )

使用特定貝茲曲線控制點
根據預設,網格產生器會處理複雜的計算,確保格線轉換順暢。不過,如果您想選擇性地推動、拉動或大幅收縮特定顏色區塊,可以明確自訂任何單一頂點的切線。
控制項偏移量是相對於主機頂點的位置測量。
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) )
建立進階格線
這個範例顯示 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) // ... )
以動畫呈現網格漸層
由於 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) )