Mesh-Farbverläufe

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.

Beispiel für einen Mesh-Farbverlauf mit einer Anzeige der aktuellen Mesh-Farbverlaufspunkte.
Abbildung 1: Beispiel für einen Mesh-Farbverlauf mit einer Anzeige der aktuellen Mesh-Farbverlaufspunkte.

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

Einfacher Netzverlauf mit 4 Farben, die an jeder Ecke definiert sind
Abbildung 2: Ein einfacher Mesh-Farbverlauf mit vier Farben, wobei jede Ecke eine der vier Farben hat.

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

Mesh-Farbverlauf mit gebogenem Punkt oben links.
Abbildung 3: Runden Sie den Scheitelpunkt oben links mit einem Bezier-Kontrollpunkt ab.

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 mit Bezier-Steuerungspunkten und Wellenfarben, darüber die Mesh-Punkte.
Abbildung 4: Mesh-Farbverlauf mit Bezier-Steuerungspunkten und Wellenfarben, darüber die Mesh-Punkte.

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

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