Gradienti mesh

Le sfumature mesh creano transizioni di colore complesse e multidirezionali utilizzando una griglia 2D di patch. A differenza delle sfumature lineari o radiali, le sfumature a griglia interpolano uniformemente i colori in una griglia. Utilizza le sfumature mesh per creare elementi estetici fluidi e organici nell'interfaccia utente.

Esempio di sfumatura a rete con la visualizzazione dei punti della sfumatura a rete corrente.
Figura 1. Un esempio di sfumatura a rete con la visualizzazione dei relativi punti di sfumatura a rete correnti.

Concetti fondamentali

Per creare una sfumatura a rete, definisci le dimensioni della griglia, i vertici e le transizioni di colore tra i punti:

  • Dimensioni griglia:la mesh viene suddivisa in patch lungo gli assi verticale e orizzontale. Una griglia di rows e columns contiene (righe+1)×(colonne+1) vertici. Ad esempio, una mesh 1×1 è costituita da 4 vertici che formano una patch.
  • Coordinate normalizzate: tutte le posizioni dei vertici utilizzano un sistema di coordinate normalizzate in cui (0f, 0f) rappresenta l'angolo in alto a sinistra e (1f, 1f) rappresenta l'angolo in basso a destra dei limiti del disegno.
  • Punti di controllo di Bézier (tangenti): ogni vertice contiene fino a quattro punti di controllo di Bézier facoltativi. Queste tangenti specificano la curvatura del bordo tra i vertici adiacenti. Se utilizzi Offset.Unspecified, Compose deduce le tangenti per garantire transizioni fluide tra le patch. Ogni cella della griglia formata da quattro vertici insieme ai relativi punti di controllo genera una patch di Bézier.
  • Interpolazione del colore:il framework calcola i colori tra i vertici principali. Imposta hasBicubicColor su true per l'interpolazione Catmull-Rom per transizioni di colore più fluide o false per l'interpolazione bilineare.

Disegna con MeshGradientPainter

In Jetpack Compose, utilizza MeshGradientPainter per eseguire il rendering di una sfumatura a rete. MeshGradientPainter disegna sul canvas.

Creare una sfumatura a mesh semplice

Per creare un gradiente a mesh statico di base, inizializza un MeshGradientPainter specificando le relative dimensioni e utilizzando la funzione setVertex all'interno del blocco di configurazione per posizionare i punti angolari e assegnare loro i colori.

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

Gradiente a rete di base con 4 colori definiti in ogni angolo
Figura 2. Un gradiente mesh di base con quattro colori, con ogni angolo impostato su uno dei quattro.

Utilizzare punti di controllo di Bézier specifici

Per impostazione predefinita, il generatore di mesh gestisce calcoli complessi per mantenere fluide le transizioni della griglia. Tuttavia, puoi personalizzare esplicitamente le tangenti su qualsiasi singolo vertice se vuoi spingere, tirare o pizzicare in modo selettivo determinate sezioni di colore.

Gli offset di controllo vengono misurati rispetto alla posizione del vertice host.

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

Gradiente mesh con punto curvo in alto a sinistra.
Figura 3. Curva il vertice in alto a sinistra con un punto di controllo di Bézier.

Creare griglie avanzate

Questo esempio mostra una griglia 3x3, il che significa che devono essere specificati 16 punti, con i punti centrali impostati con offset diversi:

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

Sfumatura a rete con punti di controllo di Bézier e colori delle onde, con i punti della rete illustrati sopra.
Figura 4. Sfumatura a rete con punti di controllo di Bézier e colori a onda, con i punti della rete illustrati sopra.

Animare una sfumatura a rete

Poiché il parametro lambda block di MeshGradientPainter viene eseguito all'interno di un DrawScope, può leggere e osservare lo stato modificabile. Puoi animare le posizioni o i colori nel tempo senza riassegnare shader o bitmap.

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

Figura 5. Gradiente mesh animato con punti per mostrare l'animazione.