Mit Mesh-Verläufen lassen sich komplexe, multidirektionale Farbübergänge mithilfe eines 2D-Rasters aus Feldern erstellen. Im Gegensatz zu linearen oder radialen Farbverläufen werden bei Mesh-Farbverläufen Farben gleichmäßig über ein Raster hinweg interpoliert. Mit Mesh-Verläufen lassen sich fließende und organische ästhetische Elemente in der Benutzeroberfläche erstellen.
Wichtige Konzepte
Um einen Mesh-Verlauf zu erstellen, definieren Sie die Rasterabmessungen, die Eckpunkte und die Farbübergänge zwischen den Punkten:
- Rasterabmessungen:Das Mesh wird entlang der vertikalen und horizontalen Achse in Patches unterteilt. Ein Raster mit
rowsundcolumnsenthält (rows+1)×(columns+1) Eckpunkte. Ein 1×1-Mesh besteht beispielsweise aus 4 Eckpunkten, die ein Patch bilden. - Normalisierte Koordinaten:Für alle Eckpunktpositionen wird ein normalisiertes Koordinatensystem verwendet, in dem
(0f, 0f)die obere linke und(1f, 1f)die untere rechte Ecke der Zeichenbegrenzungen darstellt. - Bezier-Kontrollpunkte (Tangenten): Jeder Eckpunkt kann bis zu vier optionale Bezier-Kontrollpunkte enthalten. Diese Tangenten geben die Kantenkrümmung zwischen benachbarten Knoten an. Wenn Sie
Offset.Unspecifiedverwenden, leitet Compose die Tangenten ab, um für einen reibungslosen Übergang zwischen den Patches zu sorgen. Jede Rasterzelle, die aus 4 Eckpunkten und ihren Kontrollpunkten besteht, generiert ein Bézier-Patch. - Farbinterpolation:Das Framework berechnet Farben zwischen den Haupt-Vertices. Setzen Sie
hasBicubicColorauftruefür die Catmull-Rom-Interpolation für sanftere Farbverläufe oder auffalsefür die bilineare Interpolation.
Mit MeshGradientPainter zeichnen
Verwenden Sie in Jetpack Compose MeshGradientPainter, um einen Mesh-Verlauf zu rendern. MeshGradientPainter wird auf dem Canvas gezeichnet.
Einfachen Mesh-Verlauf erstellen
Um einen einfachen statischen Mesh-Verlauf zu erstellen, initialisieren Sie ein MeshGradientPainter, indem Sie seine Abmessungen angeben und die Funktion setVertex im Konfigurationsblock verwenden, um die Eckpunkte zu positionieren und ihnen Farben zuzuweisen.
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) )
Bestimmte Bézier-Kontrollpunkte verwenden
Standardmäßig übernimmt der Mesh-Generator komplexe Berechnungen, um die Übergänge im Raster fließend zu gestalten. Sie können jedoch Tangenten für jeden einzelnen Scheitelpunkt explizit anpassen, wenn Sie bestimmte Farbabschnitte selektiv verschieben, ziehen oder scharf zusammenziehen möchten.
Die Steuerungsoffsets werden relativ zur Position des Host-Vertex gemessen.
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) )
Erweiterte Raster erstellen
Dieses Beispiel zeigt ein 3 × 3-Raster. Das bedeutet, dass 16 Punkte angegeben werden müssen. Die mittleren Punkte werden mit unterschiedlichen Offsets festgelegt:
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) // ... )
Mesh-Farbverlauf animieren
Da der block-Lambdaparameter von MeshGradientPainter in einem DrawScope ausgeführt wird, kann er veränderlichen Status lesen und beobachten. Sie können Positionen oder Farben im Zeitverlauf animieren, ohne Shader oder Bitmaps neu zuzuweisen.
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) )