网格渐变使用补丁的二维网格创建复杂的多向颜色过渡。与线性渐变或径向渐变不同,网格渐变会在网格中平滑地插值颜色。使用网格渐变在界面中创建流畅且自然的审美元素。
主要概念
如需构建网格渐变,请定义网格尺寸、顶点和点之间的颜色过渡:
- 网格尺寸: 网格沿垂直轴和水平轴拆分为补丁。
rows和columns的网格包含 (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) )
使用特定的贝塞尔控制点
默认情况下,网格生成器会处理复杂的计算,以保持网格过渡平滑。不过,如果您想有选择地推送、拉动或急剧收缩某些颜色部分,可以显式自定义任何单个顶点上的切线。
控制偏移量是相对于宿主顶点的位置来测量的。
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) )