Gradientes de malha

Os gradientes de malha criam transições de cores complexas e multidirecionais usando uma grade 2D de patches. Ao contrário dos gradientes lineares ou radiais, os gradientes de malha interpolam cores de maneira suave em uma grade. Use gradientes de malha para criar elementos estéticos fluidos e orgânicos na interface do usuário.

Um exemplo de gradiente de malha com uma exibição dos pontos atuais.
Figura 1. Um exemplo de gradiente de malha com uma exibição dos pontos de gradiente de malha atuais.

Principais conceitos

Para construir um gradiente de malha, defina as dimensões da grade, os vértices e as transições de cor entre os pontos:

  • Dimensões da grade:a malha é dividida em patches ao longo dos eixos vertical e horizontal. Uma grade de rows e columns contém (linhas + 1) ×(colunas + 1) vértices. Por exemplo, uma malha 1 × 1 consiste em quatro vértices que formam um patch.
  • Coordenadas normalizadas:todas as posições de vértice usam um sistema de coordenadas normalizado em que (0f, 0f) representa a parte de cima à esquerda e (1f, 1f) representa a parte de baixo à direita dos limites de desenho.
  • Pontos de controle de Bézier (tangentes) : cada vértice contém até quatro pontos de controle de Bézier opcionais. Essas tangentes especificam a curvatura da borda entre vértices vizinhos. Se você usar Offset.Unspecified, o Compose vai inferir as tangentes para garantir transições suaves entre os patches. Cada célula da grade formada por quatro vértices com os pontos de controle gera um patch de Bézier.
  • Interpolação de cores:o framework calcula as cores entre os vértices principais. Defina hasBicubicColor como true para a interpolação Catmull-Rom para mudanças de cor mais suaves ou false para a interpolação bilinear.

Desenhar com MeshGradientPainter

No Jetpack Compose, use MeshGradientPainter para renderizar um gradiente de malha. MeshGradientPainter desenha na tela.

Criar um gradiente de malha simples

Para criar um gradiente de malha estático básico, inicialize um MeshGradientPainter especificando as dimensões dele e usando a função setVertex dentro do bloco de configuração para posicionar os pontos de canto e atribuir cores a eles.

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 de malha básico com quatro cores definidas em cada canto
Figura 2. Um gradiente de malha básico com quatro cores, com cada canto definido como uma das quatro.

Usar pontos de controle de Bézier específicos

Por padrão, o gerador de malha processa cálculos complexos para manter as transições de grade suaves. No entanto, é possível personalizar explicitamente as tangentes em qualquer vértice único se você quiser empurrar, puxar ou apertar seletivamente determinadas seções de cores.

Os deslocamentos de controle são medidos em relação à posição do vértice do 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 de malha com ponto curvo na parte de cima à esquerda.
Figura 3. Curve o vértice superior esquerdo com um ponto de controle de Bézier.

Criar grades avançadas

Este exemplo mostra uma grade 3 por 3, o que significa que há 16 pontos que precisam ser especificados, com os pontos intermediários definidos com deslocamentos diferentes:

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 de malha com pontos de controle de Bézier e cores de onda, e os pontos de malha ilustrados na parte de cima.
Figura 4. Gradiente de malha com pontos de controle de Bézier e cores de onda, e os pontos de malha ilustrados na parte de cima.

Animar um gradiente de malha

Como o parâmetro lambda block de MeshGradientPainter é executado em um DrawScope, ele pode ler e observar o estado mutável. É possível animar posições ou cores ao longo do tempo sem realocar sombreadores ou bitmaps.

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 de malha animado com pontos para mostrar a animação.