Mesh-Farbverläufe

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.

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

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 rows und columns enthä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.Unspecified verwenden, 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 hasBicubicColor auf true für die Catmull-Rom-Interpolation für sanftere Farbverläufe oder auf false fü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)
)

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

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)
)

Mesh-Verlauf mit gekrümmtem Punkt oben links.
Abbildung 3. Die obere linke Ecke mit einem Bézier-Kontrollpunkt abrunden.

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-Verlauf mit Bézier-Steuerungspunkten und Wellenfarben. Die Mesh-Punkte sind darüber dargestellt.
Abbildung 4. Mesh-Farbverlauf mit Bézier-Steuerungspunkten und Wellenfarben. Die Mesh-Punkte sind darüber dargestellt.

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)
)

Abbildung 5. Animierter Mesh-Verlauf mit Punkten, die die Animation zeigen.