Сетчатые градиенты создают сложные разнонаправленные переходы цветов с помощью двумерной сетки фрагментов. В отличие от линейных или радиальных градиентов, сетчатые градиенты плавно интерполируют цвета по двумерной сетке. Используйте сетчатые градиенты вместо наложения нескольких кистей radialGradient или linearGradient при создании плавных, органических или многоточечных фонов в пользовательском интерфейсе.
Основные понятия
Чтобы создать сетчатый градиент, определите размеры сетки, вершины и цветовые переходы между точками:
- Размеры сетки. Сетка делится на фрагменты по вертикальной и горизонтальной осям. Сетка
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) )
Использовать определенные контрольные точки Безье
По умолчанию генератор сетки выполняет сложные вычисления, чтобы переходы между сетками были плавными. Однако вы можете явно настроить касательные на любой отдельной вершине, если хотите выборочно нажимать, тянуть или резко сжимать определенные цветовые участки.
Смещения контрольных точек измеряются относительно положения вершины хоста.
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 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) // ... )
Анимация сетчатого градиента
Поскольку лямбда-параметр 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) )