Mit Mesh-Farbverläufen werden komplexe, multidirektionale Farbübergänge mithilfe eines 2D-Gitters aus Patches erstellt. Im Gegensatz zu linearen oder radialen Farbverläufen werden bei Mesh-Farbverläufen Farben gleichmäßig über ein 2D-Raster interpoliert. Verwenden Sie Mesh-Farbverläufe, anstatt mehrere radialGradient- oder linearGradient-Pinsel zu stapeln, wenn Sie fließende, organische oder mehrpunktige Hintergründe in Ihrer Benutzeroberfläche erstellen.
Wichtige Konzepte
Wenn Sie einen Netzverlauf erstellen möchten, definieren Sie die Rasterdimensionen, die Eckpunkte und die Farbübergänge zwischen den Punkten:
- Rasterdimensionen: Das Mesh wird entlang der vertikalen und horizontalen Achse in Patches aufgeteilt. Ein Raster aus
rowsundcolumnsenthält (Zeilen + 1) ×(Spalten + 1) Eckpunkte. Ein 1×1-Mesh besteht beispielsweise aus 4 Eckpunkten, die einen Patch bilden. - Normalisierte Koordinaten: Alle Eckpunktpositionen verwenden ein normalisiertes Koordinatensystem, in dem
(0f, 0f)die obere linke Ecke und(1f, 1f)die untere rechte Ecke der Zeichnungsgrenzen darstellt. - Bézier-Kontrollpunkte (Tangenten): Jeder Eckpunkt enthält bis zu vier optionale Bézier-Kontrollpunkte. Diese Tangenten geben die Kantenkrümmung zwischen benachbarten Eckpunkten an. Wenn Sie
Offset.Unspecifiedverwenden, leitet Compose die Tangenten ab, um fließende Übergänge zwischen den Patches zu ermöglichen. Jede Gitterzelle, die aus vier Eckpunkten und ihren Kontrollpunkten besteht, erzeugt ein Bezier-Patch. - Farbinterpolation: Das Framework berechnet Farben zwischen den Hauptscheitelpunkten. Setzen Sie
hasBicubicColorauftruefür die Catmull-Rom-Interpolation für weichere Farbverschiebungen oder auffalsefür die bilineare Interpolation.
Mit MeshGradientPainter zeichnen
Verwenden Sie in Jetpack Compose MeshGradientPainter, um einen Mesh-Farbverlauf zu rendern. MeshGradientPainter zeichnet auf dem Canvas.
Einfachen Netzverlauf erstellen
Wenn Sie einen einfachen statischen Mesh-Farbverlauf erstellen möchten, initialisieren Sie ein MeshGradientPainter, indem Sie die 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) )
Spezifische Bezier-Kontrollpunkte verwenden
Standardmäßig übernimmt der Mesh-Generator komplexe Berechnungen, um die Übergänge im Raster flüssig zu halten. Sie können jedoch die Tangenten an jedem einzelnen Scheitelpunkt explizit anpassen, wenn Sie bestimmte Farbabschnitte selektiv verschieben, ziehen oder scharf zusammenziehen möchten.
Die Offsets der Steuerelemente 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
In diesem Beispiel wird ein 3 × 3-Raster gezeigt, d. h., es müssen 16 Punkte angegeben werden, wobei die mittleren Punkte mit unterschiedlichen Offsets festgelegt sind:
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 Lambda-Parameter block von MeshGradientPainter innerhalb einer DrawScope ausgeführt wird, kann er veränderliche Zustände lesen und beobachten. Sie können Positionen oder Farben im Laufe der Zeit animieren, ohne Shader oder Bitmaps neu zuweisen zu müssen.
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) )