Mesh-Farbverläufe

Bei Mesh-Farbverläufen werden komplexe, multidirektionale Farbverläufe mithilfe eines 2D-Rasters aus Patches erstellt. Im Gegensatz zu linearen oder radialen Farbverläufen werden bei Mesh-Farbverläufen Farben gleichmäßig über ein Raster hinweg interpoliert. Mit Mesh-Farbverläufen können Sie fließende und organische ästhetische Elemente in Ihrer Benutzeroberfläche erstellen.

Ein Beispiel für einen Mesh-Verlauf mit einer Anzeige der aktuellen Mesh-Verlaufspunkte.
Abbildung 1. Ein Beispiel für einen Mesh-Farbverlauf mit einer Darstellung der aktuellen Mesh-Farbverlaufspunkte.

Wichtige Konzepte

Um einen Mesh-Farbverlauf zu erstellen, definieren Sie die Rasterabmessungen, die Eckpunkte und die Farbverläufe zwischen den Punkten:

  • Rasterabmessungen:Das Mesh wird entlang der vertikalen und horizontalen Achse in Patches unterteilt. Ein Raster aus rows und columns enthält (rows+1)×(columns+1) Eckpunkte. Ein 1×1-Mesh besteht beispielsweise aus vier Eckpunkten, die ein Patch bilden.
  • Normalisierte Koordinaten:Alle Eckpunktpositionen verwenden ein normalisiertes Koordinatensystem, wobei (0f, 0f) die obere linke und (1f, 1f) die untere rechte Ecke der Zeichenbegrenzungen darstellt.
  • Bezier-Kontrollpunkte (Tangenten) : Jeder Eckpunkt enthält bis zu vier optionale Bezier-Kontrollpunkte. Diese Tangenten geben die Kantenkrümmung zwischen benachbarten Eckpunkten an. Wenn Sie Offset.Unspecified verwenden, leitet Compose die Tangenten ab, um für reibungslose Übergänge zwischen den Patches zu sorgen. Jede Rasterzelle, die aus vier Eckpunkten und ihren Kontrollpunkten besteht, erzeugt ein Bezier-Patch.
  • Farbinterpolation:Das Framework berechnet die Farben zwischen den Haupteckpunkten. Legen Sie hasBicubicColor auf true für die Catmull-Rom Interpolation für sanftere Farbverläufe oder false für die bilineare Interpolation fest.

Mit MeshGradientPainter zeichnen

Verwenden Sie in Jetpack Compose MeshGradientPainter, um einen Mesh-Farbverlauf zu rendern. MeshGradientPainter zeichnet auf dem Canvas.

Einfachen Mesh-Farbverlauf erstellen

Um einen einfachen statischen Mesh-Farbverlauf zu erstellen, initialisieren Sie einen 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)
)

Einfacher Mesh-Verlauf mit 4 Farben, die an jeder Ecke definiert sind
Abbildung 2. Ein einfacher Mesh-Farbverlauf mit vier Farben, wobei jede Ecke auf eine der vier Farben festgelegt ist.

Bestimmte Bezier-Kontrollpunkte verwenden

Standardmäßig übernimmt der Mesh-Generator komplexe Berechnungen, um die Übergänge im Raster reibungslos zu gestalten. Sie können jedoch Tangenten für jeden einzelnen Eckpunkt explizit anpassen, wenn Sie bestimmte Farbabschnitte selektiv verschieben, ziehen oder scharf zusammenziehen möchten.

Kontroll-Offsets werden relativ zur Position des Host-Eckpunkts 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)
)

Mesh-Verlauf mit gekrümmtem Punkt oben links.
Abbildung 3. Krümmen Sie den oberen linken Eckpunkt mit einem Bezier-Kontrollpunkt.

Erweiterte Raster erstellen

In diesem Beispiel wird ein 3×3-Raster gezeigt. Das bedeutet, dass 16 Punkte angegeben werden müssen, wobei die Mittelpunkte 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-Verlauf mit Bézier-Steuerungspunkten und Wellenfarben. Die Mesh-Punkte sind darüber dargestellt.
Abbildung 4. Mesh-Farbverlauf mit Bezier-Kontrollpunkten und Wellenfarben, wobei die Mesh-Punkte darüber dargestellt sind.

Mesh-Farbverlauf animieren

Da der Lambda-Parameter block von MeshGradientPainter in einem DrawScope ausgeführt wird, kann er den 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)
)

Abbildung 5. Animierter Mesh-Farbverlauf mit Punkten zur Darstellung der Animation.