Gradientes de malla

Los gradientes de malla crean transiciones de color complejas y multidireccionales con una cuadrícula 2D de parches. A diferencia de los gradientes lineales o radiales, los gradientes de malla interpolan los colores de forma fluida en una cuadrícula 2D. Usa gradientes de malla en lugar de apilar varios pinceles radialGradient o linearGradient cuando crees fondos fluidos, orgánicos o de varios puntos en tu interfaz de usuario.

Ejemplo de un degradado de malla con una visualización de sus puntos de degradado de malla actuales.
Figura 1: Ejemplo de un gradiente de malla con una visualización de sus puntos de gradiente de malla actuales.

Conceptos clave

Para construir un gradiente de malla, define las dimensiones de la cuadrícula, los vértices y las transiciones de color entre los puntos:

  • Dimensiones de la cuadrícula: La malla se divide en parches a lo largo de los ejes vertical y horizontal. Una cuadrícula de rows y columns contiene (filas+1)×(columnas+1) vértices. Por ejemplo, una malla de 1 × 1 consta de 4 vértices que forman un parche.
  • Coordenadas normalizadas: Todas las posiciones de los vértices usan un sistema de coordenadas normalizado en el que (0f, 0f) representa la parte superior izquierda y (1f, 1f) representa la parte inferior derecha de los límites del dibujo.
  • Puntos de control de Bézier (tangentes): Cada vértice contiene hasta cuatro puntos de control de Bézier opcionales. Estas tangentes especifican la curvatura de la arista entre los vértices vecinos. Si usas Offset.Unspecified, Compose infiere las tangentes para garantizar transiciones fluidas entre los parches. Cada celda de la cuadrícula formada por 4 vértices junto con sus puntos de control genera un parche de Bézier.
  • Interpolación de colores: El framework calcula los colores entre los vértices principales. Establece hasBicubicColor en true para la interpolación de Catmull-Rom para obtener cambios de color más suaves o false para la interpolación bilineal.

Dibuja con MeshGradientPainter

En Jetpack Compose, usa MeshGradientPainter para renderizar un gradiente de malla. MeshGradientPainter dibuja en el lienzo.

Crea un degradado de malla simple

Para crear un gradiente de malla estático básico, inicializa un MeshGradientPainter especificando sus dimensiones y usando la función setVertex dentro del bloque de configuración para posicionar los puntos de esquina y asignarles colores.

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 malla básico con 4 colores definidos en cada esquina
Figura 2: Un gradiente de malla básico con cuatro colores, con cada esquina establecida en uno de los cuatro.

Usa puntos de control de Bézier específicos

De forma predeterminada, el generador de mallas controla los cálculos complejos para mantener las transiciones de la cuadrícula fluidas. Sin embargo, puedes personalizar de forma explícita las tangentes en cualquier vértice si quieres empujar, tirar o pellizcar de forma selectiva ciertas secciones de color.

Los desplazamientos de los controles se miden en relación con la posición del vértice del 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)
)

Degradado de malla con un punto superior izquierdo curvo.
Figura 3: Curva el vértice superior izquierdo con un punto de control de Bézier.

Cómo crear cuadrículas avanzadas

En este ejemplo, se muestra una cuadrícula de 3 por 3, lo que significa que hay 16 puntos que se deben especificar, con los puntos intermedios configurados con diferentes desplazamientos:

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 malla con puntos de control de Bézier y colores de onda, y los puntos de malla ilustrados sobre él.
Figura 4: Gradiente de malla con puntos de control de Bézier y colores de onda, y los puntos de malla ilustrados sobre él.

Cómo animar un gradiente de malla

Debido a que el parámetro lambda block de MeshGradientPainter se ejecuta dentro de un DrawScope, puede leer y observar el estado mutable. Puedes animar posiciones o colores con el tiempo sin volver a asignar sombreadores o mapas de bits.

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 malla animado con puntos para mostrar la animación.