Brush в Compose описывает, как что-то рисуется на экране: определяет цвета, которые рисуются в области рисования (например, круг, квадрат, путь). Для рисования можно использовать встроенные кисти, например LinearGradient, RadialGradient или обычную кисть SolidColor.
Кисти можно использовать с вызовами отрисовки Modifier.background(), TextStyle или DrawScope, чтобы применять стиль рисования к отрисовываемому контенту.
Например, горизонтальный градиент можно применить к рисованию круга в DrawScope:
val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue)) Canvas( modifier = Modifier.size(200.dp), onDraw = { drawCircle(brush) } )
Градиентные кисти
Встроенные кисти позволяют создавать различные эффекты градиента. Эти кисти позволяют указать список цветов, из которых вы хотите создать градиент.
Список доступных градиентных кистей и их выходные данные:
| Тип градиентной кисти | Результат |
|---|---|
Brush.horizontalGradient(colorList) |
|
Brush.linearGradient(colorList) |
|
Brush.verticalGradient(colorList) |
|
Brush.sweepGradient(colorList)
Примечание. Чтобы переход между цветами был плавным, в качестве последнего цвета выберите начальный. |
|
Brush.radialGradient(colorList) |
|
Изменить распределение цветов с помощью colorStops
Чтобы настроить отображение цветов в градиенте, вы можете изменить значение colorStops для каждого из них. Значение colorStops должно быть указано в виде дроби от 0 до 1. Если значение больше 1, то эти цвета не будут отображаться в градиенте.
Вы можете настроить контрольные точки цвета так, чтобы одного цвета было больше, а другого – меньше.
val colorStops = arrayOf( 0.0f to Color.Yellow, 0.2f to Color.Red, 1f to Color.Blue ) Box( modifier = Modifier .requiredSize(200.dp) .background(Brush.horizontalGradient(colorStops = colorStops)) )
Цвета распределяются по заданному смещению, как определено в паре colorStop, желтого меньше, чем красного и синего.
Повторить шаблон с помощью TileMode
Для каждой градиентной кисти можно задать TileMode. Если вы не задали начало и конец градиента, то можете не заметить TileMode, так как по умолчанию он заполнит всю область. TileMode будет повторять градиент, только если размер области больше размера кисти.
В следующем коде градиент повторяется четыре раза, поскольку для свойства endX задано значение 50.dp, а для размера – 200.dp:
val listColors = listOf(Color.Yellow, Color.Red, Color.Blue) val tileSize = with(LocalDensity.current) { 50.dp.toPx() } Box( modifier = Modifier .requiredSize(200.dp) .background( Brush.horizontalGradient( listColors, endX = tileSize, tileMode = TileMode.Repeated ) ) )
В таблице ниже показано, как разные режимы мозаики влияют на пример HorizontalGradient.
| TileMode | Результат |
|---|---|
TileMode.Repeated – край повторяется от последнего цвета к первому. |
|
TileMode.Mirror – край зеркально отражается от последнего цвета к первому. |
|
TileMode.Clamp – край ограничен конечным цветом. Затем он закрасит оставшуюся часть региона ближайшим цветом. |
|
TileMode.Decal – отрисовывать только до размера границ. TileMode.Decal использует прозрачный черный цвет для выборки контента за пределами исходных границ, а TileMode.Clamp – цвет краев. |
|
TileMode работает аналогично для других направленных градиентов, разница заключается в направлении повторения.
Изменить размер кисти
Если вы знаете размер области, в которой будет рисовать кисть, вы можете задать размер фрагмента endX, как описано в разделе TileMode. Если вы находитесь в DrawScope, то можете использовать свойство size, чтобы узнать размер области.
Если вы не знаете размер области рисования (например, если Brush назначен тексту), вы можете расширить Shader и использовать размер области рисования в функции createShader.
В этом примере разделите размер на 4, чтобы повторить узор 4 раза:
val listColors = listOf(Color.Yellow, Color.Red, Color.Blue) val customBrush = remember { object : ShaderBrush() { override fun createShader(size: Size): Shader { return LinearGradientShader( colors = listColors, from = Offset.Zero, to = Offset(size.width / 4f, 0f), tileMode = TileMode.Mirror ) } } } Box( modifier = Modifier .requiredSize(200.dp) .background(customBrush) )
Вы также можете изменить размер кисти любого другого градиента, например радиального. Если не указать размер и центр, градиальный градиент займет всю область DrawScope, а его центр по умолчанию будет совпадать с центром области DrawScope. В результате центр радиального градиента будет находиться в центре меньшего размера (ширины или высоты):
Box( modifier = Modifier .fillMaxSize() .background( Brush.radialGradient( listOf(Color(0xFF2be4dc), Color(0xFF243484)) ) ) )
Если изменить радиальный градиент, чтобы установить размер радиуса на максимальный размер, вы увидите, что это дает лучший эффект радиального градиента:
val largeRadialGradient = object : ShaderBrush() { override fun createShader(size: Size): Shader { val biggerDimension = maxOf(size.height, size.width) return RadialGradientShader( colors = listOf(Color(0xFF2be4dc), Color(0xFF243484)), center = size.center, radius = biggerDimension / 2f, colorStops = listOf(0f, 0.95f) ) } } Box( modifier = Modifier .fillMaxSize() .background(largeRadialGradient) )
Стоит отметить, что фактический размер, который передается при создании шейдера, определяется тем, откуда он вызывается. По умолчанию Brush перераспределяет Shader, если размер отличается от размера при последнем создании Brush или если объект состояния, используемый при создании шейдера, изменился.
В следующем коде шейдер создается три раза с разными размерами, поскольку размер области рисования меняется:
val colorStops = arrayOf( 0.0f to Color.Yellow, 0.2f to Color.Red, 1f to Color.Blue ) val brush = Brush.horizontalGradient(colorStops = colorStops) Box( modifier = Modifier .requiredSize(200.dp) .drawBehind { drawRect(brush = brush) // will allocate a shader to occupy the 200 x 200 dp drawing area inset(10f) { /* Will allocate a shader to occupy the 180 x 180 dp drawing area as the inset scope reduces the drawing area by 10 pixels on the left, top, right, bottom sides */ drawRect(brush = brush) inset(5f) { /* will allocate a shader to occupy the 170 x 170 dp drawing area as the inset scope reduces the drawing area by 5 pixels on the left, top, right, bottom sides */ drawRect(brush = brush) } } } )
Как использовать изображение в качестве кисти
Чтобы использовать ImageBitmap в качестве Brush, загрузите изображение как ImageBitmap и создайте кисть ImageShader:
val imageBrush = ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.dog))) // Use ImageShader Brush with background Box( modifier = Modifier .requiredSize(200.dp) .background(imageBrush) ) // Use ImageShader Brush with TextStyle Text( text = "Hello Android!", style = TextStyle( brush = imageBrush, fontWeight = FontWeight.ExtraBold, fontSize = 36.sp ) ) // Use ImageShader Brush with DrawScope#drawCircle() Canvas(onDraw = { drawCircle(imageBrush) }, modifier = Modifier.size(200.dp))
Кисть применяется к нескольким типам рисунков: фону, тексту и холсту. Вы получите следующий результат:
Обратите внимание, что теперь текст также отображается с помощью ImageBitmap для рисования пикселей текста.
Пример для продвинутых пользователей: пользовательская кисть
Кисть AGSL RuntimeShader
AGSL предлагает подмножество возможностей шейдеров GLSL. Шейдеры можно писать на AGSL и использовать с Brush в Compose.
Чтобы создать кисть Shader, сначала определите шейдер как строку шейдера AGSL:
@Language("AGSL") val CUSTOM_SHADER = """ uniform float2 resolution; layout(color) uniform half4 color; layout(color) uniform half4 color2; half4 main(in float2 fragCoord) { float2 uv = fragCoord/resolution.xy; float mixValue = distance(uv, vec2(0, 1)); return mix(color, color2, mixValue); } """.trimIndent()
Шейдер выше принимает два входных цвета, вычисляет расстояние от нижнего левого угла (vec2(0, 1)) области рисования и выполняет mix между двумя цветами на основе расстояния. В результате получается эффект градиента.
Затем создайте Shader Brush и задайте униформы для resolution – размера области рисования, а также color и color2, которые вы хотите использовать в качестве входных данных для своего градиента:
val Coral = Color(0xFFF3A397) val LightYellow = Color(0xFFF8EE94) @RequiresApi(Build.VERSION_CODES.TIRAMISU) @Composable @Preview fun ShaderBrushExample() { Box( modifier = Modifier .drawWithCache { val shader = RuntimeShader(CUSTOM_SHADER) val shaderBrush = ShaderBrush(shader) shader.setFloatUniform("resolution", size.width, size.height) onDrawBehind { shader.setColorUniform( "color", android.graphics.Color.valueOf( LightYellow.red, LightYellow.green, LightYellow .blue, LightYellow.alpha ) ) shader.setColorUniform( "color2", android.graphics.Color.valueOf( Coral.red, Coral.green, Coral.blue, Coral.alpha ) ) drawRect(shaderBrush) } } .fillMaxWidth() .height(200.dp) ) }
При запуске этого кода на экране появится следующее:
Стоит отметить, что с помощью шейдеров можно делать гораздо больше, чем просто градиенты, поскольку все это основано на математических вычислениях. Дополнительную информацию об AGSL можно найти в документации.
Дополнительные ресурсы
Дополнительные примеры использования Brush в Compose можно найти в следующих ресурсах:
- Анимация кисти для раскрашивания текста в Compose 🖌️
- Специальная графика и макеты в Compose – Саммит для разработчиков Android 2022
- JetLagged Sample - RuntimeShader Brush
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Модификаторы графики
- Графика в Compose
- Как форматировать текст