Gradienti mesh

I gradienti mesh creano transizioni di colore complesse e multidirezionali utilizzando una griglia 2D di patch. A differenza delle sfumature lineari o radiali, le sfumature mesh interpolano i colori in modo fluido su una griglia 2D. Utilizza i gradienti mesh anziché impilare più pennelli radialGradient o linearGradient quando crei sfondi fluidi, organici o multipunto nella tua interfaccia utente.

Un esempio di sfumatura mesh con una visualizzazione dei punti della sfumatura mesh corrente.
Figura 1. Esempio di sfumatura a maglia con visualizzazione dei punti della sfumatura a maglia corrente.

Concetti fondamentali

Per creare un gradiente mesh, definisci le dimensioni della griglia, i vertici e le transizioni di colore tra i punti:

  • Dimensioni della griglia: la mesh è 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 1x1 è costituita da 4 vertici che formano una patch.
  • Coordinate normalizzate: tutte le posizioni dei vertici utilizzano un sistema di coordinate normalizzato 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 4 vertici insieme ai relativi punti di controllo genera una patch di Bézier.
  • Interpolazione dei colori:il framework calcola i colori tra i vertici principali. Imposta hasBicubicColor su true per l'interpolazione Catmull-Rom per variazioni di colore più fluide o su false per l'interpolazione bilineare.

Disegna con MeshGradientPainter

In Jetpack Compose, utilizza MeshGradientPainter per eseguire il rendering di un gradiente mesh. MeshGradientPainter disegna sul canvas.

Crea una sfumatura a trama semplice

Per creare un gradiente di mesh statico di base, inizializza un MeshGradientPainter specificandone le dimensioni e utilizza la funzione setVertex all'interno del blocco di configurazione per posizionare i punti d'angolo 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 di maglia 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.

Utilizza 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 dei controlli vengono misurati in base 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 in alto a sinistra curvo.
Figura 3. Curva il vertice in alto a sinistra con un punto di controllo Bézier.

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

Gradiente mesh con punti di controllo di Bézier e colori delle onde, con i punti mesh illustrati sopra.
Figura 4. Gradiente mesh con punti di controllo di Bézier e colori delle onde, con i punti mesh illustrati sopra.

Anima un gradiente mesh

Poiché il parametro lambda block di MeshGradientPainter viene eseguito all'interno di un DrawScope, può leggere e osservare lo stato modificabile. Puoi animare posizioni o colori nel tempo senza riallocare 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.