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 2D. Use gradientes de malha em vez de empilhar vários pincéis radialGradient ou linearGradient ao criar planos de fundo fluidos, orgânicos ou de vários pontos na interface do usuário.

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

Principais conceitos

Para criar 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 o canto superior esquerdo e (1f, 1f) representa o canto inferior direito dos limites do desenho.
  • Pontos de controle de Bezier (tangentes): cada vértice contém até quatro pontos de controle de Bezier opcionais. Essas tangentes especificam a curvatura da aresta 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 de 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 interpolação bilinear.

Desenhar com o 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ática 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, em que cada canto é definido como uma das quatro.

Usar pontos de controle de Bezier específicos

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

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 superior esquerdo curvado.
Figura 3. Curvar o vértice superior esquerdo com um ponto de controle de Bézier.

Criar grades avançadas

Este exemplo mostra uma grade de 3 por 3, o que significa que há 16 pontos que precisam ser especificados, com os pontos do meio 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, com os pontos de malha ilustrados por cima.
Figura 4. Gradiente de malha com pontos de controle de Bézier e cores de onda, com os pontos de malha ilustrados por 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.