Градиенты сетки

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

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

Ключевые понятия

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

  • Размеры сетки: Сетка разбивается на фрагменты вдоль вертикальной и горизонтальной осей. Сетка из rows и columns содержит (строки+1)×(столбцы+1) вершин. Например, сетка 1×1 состоит из 4 вершин, образующих один фрагмент.
  • Нормализованные координаты: Все позиции вершин используют нормализованную систему координат, где (0f, 0f) представляет верхний левый угол, а (1f, 1f) — нижний правый угол границ отрисовки.
  • Контрольные точки Безье (касательные): Каждая вершина содержит до четырех необязательных контрольных точек Безье. Эти касательные определяют кривизну ребер между соседними вершинами. Если вы используете Offset.Unspecified , Compose автоматически определяет касательные для обеспечения плавных переходов между участками. Каждая ячейка сетки, образованная 4 вершинами вместе с их контрольными точками, генерирует участок Безье.
  • Интерполяция цвета: фреймворк вычисляет цвета между основными вершинами. Установите 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 на 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. Анимированный градиент сетки с точками для демонстрации анимации.