Сетчатые градиенты

Сетчатые градиенты создают сложные разнонаправленные переходы цветов с помощью двумерной сетки фрагментов. В отличие от линейных или радиальных градиентов, сетчатые градиенты плавно интерполируют цвета по двумерной сетке. Используйте сетчатые градиенты вместо наложения нескольких кистей radialGradient или linearGradient при создании плавных, органических или многоточечных фонов в пользовательском интерфейсе.

Пример сетчатого градиента с отображением его текущих точек.
Рисунок 1. Пример сетчатого градиента с отображением его текущих точек.

Основные понятия

Чтобы создать сетчатый градиент, определите размеры сетки, вершины и цветовые переходы между точками:

  • Размеры сетки. Сетка делится на фрагменты по вертикальной и горизонтальной осям. Сетка rows на columns содержит (строки + 1) ×(столбцы + 1) вершин. Например, сетка 1 × 1 состоит из четырех вершин, образующих один фрагмент.
  • Нормализованные координаты. Для всех вершин используется нормализованная система координат, в которой (0f, 0f) – это верхний левый угол, а (1f, 1f) – нижний правый угол границ рисунка.
  • Контрольные точки Безье (касательные). Каждая вершина содержит до четырех необязательных контрольных точек Безье. Эти касательные определяют кривизну ребра между соседними вершинами. Если вы используете Offset.Unspecified, Compose выводит касательные, чтобы обеспечить плавные переходы между фрагментами. Каждая ячейка сетки, образованная четырьмя вершинами и их контрольными точками, генерирует фрагмент Безье.
  • Интерполяция цветов. Фреймворк рассчитывает цвета между основными вершинами. Задайте для параметра hasBicubicColor значение true, чтобы использовать интерполяцию Катмулла – Рома для более плавных переходов цвета, или false для билинейной интерполяции.

Рисуйте с MeshGradientPainter

В Jetpack Compose используйте MeshGradientPainter, чтобы отрисовать сетчатый градиент. MeshGradientPainter рисует на холсте.

Как создать простой сетчатый градиент

Чтобы создать базовый статический сетчатый градиент, инициализируйте MeshGradientPainter, указав его размеры и используя функцию setVertex внутри блока конфигурации, чтобы расположить угловые точки и назначить им цвета.

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

Простой сетчатый градиент с четырьмя цветами, заданными в каждом углу
Рисунок 2. Базовый сетчатый градиент с четырьмя цветами, каждый из которых расположен в одном из углов.

Использовать определенные контрольные точки Безье

По умолчанию генератор сетки выполняет сложные вычисления, чтобы переходы между сетками были плавными. Однако вы можете явно настроить касательные на любой отдельной вершине, если хотите выборочно нажимать, тянуть или резко сжимать определенные цветовые участки.

Смещения контрольных точек измеряются относительно положения вершины хоста.

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

Сетчатый градиент с изогнутой верхней левой точкой.
Рисунок 3. Скруглите верхнюю левую вершину с помощью контрольной точки Безье.

Как создавать расширенные сетки

В этом примере показана сетка 3 x 3, то есть нужно указать 16 точек, причем средние точки заданы с разными смещениями:

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

Сетчатый градиент с контрольными точками Безье и цветами волн, а также точками сетки, показанными поверх него.
Рисунок 4. Сетчатый градиент с контрольными точками Безье и цветами волн, а также точками сетки, показанными поверх него.

Анимация сетчатого градиента

Поскольку лямбда-параметр block функции MeshGradientPainter выполняется в DrawScope, он может считывать и отслеживать изменяемое состояние. Вы можете анимировать позиции или цвета с течением времени без перераспределения шейдеров или растровых изображений.

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

Рисунок 5. Анимированный сетчатый градиент с точками, показывающими анимацию.