В дополнение к Canvas
composable, Compose имеет несколько полезных графических Modifiers
, которые помогают рисовать пользовательский контент. Эти модификаторы полезны, потому что их можно применять к любому composable.
Модификаторы рисования
Все команды рисования выполняются с помощью модификатора рисования в Compose. В Compose есть три основных модификатора рисования:
Базовым модификатором для рисования является drawWithContent
, где вы можете определить порядок рисования вашего Composable и команды рисования, выдаваемые внутри модификатора. drawBehind
— это удобная оболочка для drawWithContent
, в которой порядок рисования установлен позади содержимого composable. drawWithCache
вызывает внутри себя либо onDrawBehind
, либо onDrawWithContent
— и предоставляет механизм для кэширования объектов, созданных в них.
Modifier.drawWithContent
: выбрать порядок рисования
Modifier.drawWithContent
позволяет выполнять операции DrawScope
до или после содержимого компонуемого объекта. Обязательно вызовите drawContent
, чтобы затем отобразить фактическое содержимое компонуемого объекта. С помощью этого модификатора вы можете определить порядок операций, если хотите, чтобы ваше содержимое было отрисовано до или после ваших пользовательских операций рисования.
Например, если вы хотите отобразить радиальный градиент поверх своего контента, чтобы создать эффект замочной скважины фонарика в пользовательском интерфейсе, вы можете сделать следующее:
var pointerOffset by remember { mutableStateOf(Offset(0f, 0f)) } Column( modifier = Modifier .fillMaxSize() .pointerInput("dragging") { detectDragGestures { change, dragAmount -> pointerOffset += dragAmount } } .onSizeChanged { pointerOffset = Offset(it.width / 2f, it.height / 2f) } .drawWithContent { drawContent() // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI. drawRect( Brush.radialGradient( listOf(Color.Transparent, Color.Black), center = pointerOffset, radius = 100.dp.toPx(), ) ) } ) { // Your composables here }
Modifier.drawBehind
: Рисование позади компонуемого объекта
Modifier.drawBehind
позволяет выполнять операции DrawScope
за компонуемым содержимым, которое рисуется на экране. Если вы посмотрите на реализацию Canvas
, вы можете заметить, что это просто удобная оболочка вокруг Modifier.drawBehind
.
Чтобы нарисовать скругленный прямоугольник позади Text
:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
Что дает следующий результат:

Modifier.drawWithCache
: Рисование и кэширование объектов рисования
Modifier.drawWithCache
сохраняет объекты, созданные внутри него, в кэше. Объекты кэшируются до тех пор, пока размер области рисования остается прежним или любые считываемые объекты состояния не изменились. Этот модификатор полезен для повышения производительности вызовов рисования, поскольку он позволяет избежать необходимости перераспределять объекты (такие как: Brush, Shader, Path
и т. д.), созданные при рисовании.
В качестве альтернативы вы также можете кэшировать объекты, используя remember
, вне модификатора. Однако это не всегда возможно, поскольку у вас не всегда есть доступ к композиции. Использование drawWithCache
может быть более производительным, если объекты используются только для рисования.
Например, если вы создаете Brush
для рисования градиента позади Text
, использование drawWithCache
кэширует объект Brush
до тех пор, пока не изменится размер области рисования:
Text( "Hello Compose!", modifier = Modifier .drawWithCache { val brush = Brush.linearGradient( listOf( Color(0xFF9E82F0), Color(0xFF42A5F5) ) ) onDrawBehind { drawRoundRect( brush, cornerRadius = CornerRadius(10.dp.toPx()) ) } } )

Графические модификаторы
Modifier.graphicsLayer
: Применяет преобразования к компонуемым объектам
Modifier.graphicsLayer
— это модификатор, который превращает содержимое компонуемого чертежа в слой чертежа. Слой предоставляет несколько различных функций, таких как:
- Изоляция для его инструкций по рисованию (аналогично
RenderNode
). Инструкции по рисованию, захваченные как часть слоя, могут быть эффективно повторно выпущены конвейером рендеринга без повторного выполнения кода приложения. - Преобразования, применяемые ко всем инструкциям рисования, содержащимся в слое.
- Растеризация для возможностей композиции. Когда слой растеризован, выполняются его инструкции по рисованию, а вывод захватывается в буфер за пределами экрана. Составление такого буфера для последующих кадров происходит быстрее, чем выполнение отдельных инструкций, но он будет вести себя как битовая карта, когда применяются преобразования, такие как масштабирование или поворот.
Трансформации
Modifier.graphicsLayer
обеспечивает изоляцию для своих инструкций по рисованию; например, различные преобразования могут быть применены с помощью Modifier.graphicsLayer
. Их можно анимировать или изменять без необходимости повторного выполнения лямбда-функции рисования.
Modifier.graphicsLayer
не изменяет измеренный размер или размещение вашего компонуемого элемента, поскольку он влияет только на фазу рисования. Это означает, что ваш компонуемый элемент может перекрывать другие, если он в конечном итоге будет рисоваться за пределами границ своего макета.
С помощью этого модификатора можно применять следующие преобразования:
Масштаб - увеличить размер
scaleX
и scaleY
увеличивают или уменьшают содержимое в горизонтальном или вертикальном направлении соответственно. Значение 1.0f
указывает на отсутствие изменения масштаба, значение 0.5f
означает половину размера.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.scaleX = 1.2f this.scaleY = 0.8f } )
Перевод
translationX
и translationY
можно изменять с помощью graphicsLayer
, translationX
перемещает компонуемый объект влево или вправо. translationY
перемещает компонуемый объект вверх или вниз.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Вращение
Установите rotationX
для вращения по горизонтали, rotationY
для вращения по вертикали и rotationZ
для вращения по оси Z (стандартное вращение). Это значение указывается в градусах (0-360).
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Источник
Можно указать transformOrigin
. Затем он используется как точка, из которой происходят преобразования. Во всех примерах до сих пор использовался TransformOrigin.Center
, который находится в точке (0.5f, 0.5f)
. Если указать начало координат в точке (0f, 0f)
, преобразования начнутся с верхнего левого угла составного объекта.
Если изменить начало координат с помощью преобразования rotationZ
, можно увидеть, что элемент вращается вокруг верхнего левого угла компонуемого объекта:
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.transformOrigin = TransformOrigin(0f, 0f) this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Клип и форма
Shape определяет контур, по которому обрезается содержимое, когда clip = true
. В этом примере мы устанавливаем два поля для двух разных обрезов — один с помощью переменной clip graphicsLayer
, а другой — с помощью удобной оболочки Modifier.clip
.
Column(modifier = Modifier.padding(16.dp)) { Box( modifier = Modifier .size(200.dp) .graphicsLayer { clip = true shape = CircleShape } .background(Color(0xFFF06292)) ) { Text( "Hello Compose", style = TextStyle(color = Color.Black, fontSize = 46.sp), modifier = Modifier.align(Alignment.Center) ) } Box( modifier = Modifier .size(200.dp) .clip(CircleShape) .background(Color(0xFF4DB6AC)) ) }
Содержимое первого поля (текст «Hello Compose») обрезается по форме круга:

Если затем применить translationY
к верхнему розовому кругу, то вы увидите, что границы Composable остаются прежними, но круг рисуется под нижним кругом (и за его пределами).

Чтобы обрезать компонуемый объект до области, в которой он нарисован, можно добавить еще один Modifier.clip(RectangleShape)
в начале цепочки модификаторов. После этого содержимое останется внутри исходных границ.
Column(modifier = Modifier.padding(16.dp)) { Box( modifier = Modifier .clip(RectangleShape) .size(200.dp) .border(2.dp, Color.Black) .graphicsLayer { clip = true shape = CircleShape translationY = 50.dp.toPx() } .background(Color(0xFFF06292)) ) { Text( "Hello Compose", style = TextStyle(color = Color.Black, fontSize = 46.sp), modifier = Modifier.align(Alignment.Center) ) } Box( modifier = Modifier .size(200.dp) .clip(RoundedCornerShape(500.dp)) .background(Color(0xFF4DB6AC)) ) }

Альфа
Modifier.graphicsLayer
можно использовать для установки alpha
(непрозрачности) для всего слоя. 1.0f
— полная непрозрачность, 0.0f
— невидимость.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )

Стратегия композиции
Работа с альфа-каналом и прозрачностью может быть не такой простой, как изменение одного альфа-значения. Помимо изменения альфа-канала, есть также возможность задать CompositingStrategy
для graphicsLayer
. CompositingStrategy
определяет, как содержимое компонуемого объекта компонуется (собирается вместе) с другим содержимым, уже нарисованным на экране.
Различные стратегии:
Авто (по умолчанию)
Стратегия компоновки определяется остальными параметрами graphicsLayer
. Он рендерит слой в буфер за пределами экрана, если альфа меньше 1.0f или установлен RenderEffect
. Всякий раз, когда альфа меньше 1f, автоматически создается слой компоновки для рендеринга содержимого, а затем отрисовки этого буфера за пределами экрана в месте назначения с соответствующей альфой. Установка RenderEffect
или overscroll всегда рендерит содержимое в буфер за пределами экрана независимо от установленного CompositingStrategy
.
Закадровый
Содержимое компонуемого всегда растрируется в закадровую текстуру или битовую карту перед рендерингом в место назначения. Это полезно для применения операций BlendMode
для маскирования содержимого и для производительности при рендеринге сложных наборов инструкций рисования.
Пример использования CompositingStrategy.Offscreen
— BlendModes
. Взгляните на пример ниже. Допустим, вы хотите удалить части Image
, которое можно составить, выполнив команду рисования, которая использует BlendMode.Clear
. Если вы не установите compositingStrategy
на CompositingStrategy.Offscreen
, BlendMode
будет взаимодействовать со всем содержимым под ним.
Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .size(120.dp) .aspectRatio(1f) .background( Brush.linearGradient( listOf( Color(0xFFC5E1A5), Color(0xFF80DEEA) ) ) ) .padding(8.dp) .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } .drawWithCache { val path = Path() path.addOval( Rect( topLeft = Offset.Zero, bottomRight = Offset(size.width, size.height) ) ) onDrawWithContent { clipPath(path) { // this draws the actual image - if you don't call drawContent, it wont // render anything this@onDrawWithContent.drawContent() } val dotSize = size.width / 8f // Clip a white border for the content drawCircle( Color.Black, radius = dotSize, center = Offset( x = size.width - dotSize, y = size.height - dotSize ), blendMode = BlendMode.Clear ) // draw the red circle indication drawCircle( Color(0xFFEF5350), radius = dotSize * 0.8f, center = Offset( x = size.width - dotSize, y = size.height - dotSize ) ) } } )
Устанавливая CompositingStrategy
на Offscreen
, он создает внеэкранную текстуру для выполнения команд (применяя BlendMode
только к содержимому этого компонуемого объекта). Затем он визуализирует его поверх того, что уже визуализируется на экране, не влияя на уже нарисованное содержимое.

Если вы не использовали CompositingStrategy.Offscreen
, результаты применения BlendMode.Clear
очищают все пиксели в месте назначения, независимо от того, что уже было установлено, оставляя буфер рендеринга окна (черный) видимым. Многие из BlendModes
, которые включают альфа, не будут работать так, как ожидается, без буфера offscreen. Обратите внимание на черное кольцо вокруг красного кругового индикатора:

Чтобы понять это немного глубже: если бы у приложения был полупрозрачный фон окна, и вы не использовали CompositingStrategy.Offscreen
, BlendMode
взаимодействовал бы со всем приложением. Он бы очистил все пиксели, чтобы показать приложение или обои под ним, как в этом примере:

Стоит отметить, что при использовании CompositingStrategy.Offscreen
создается и отображается на экране внеэкранная текстура, имеющая размер области рисования. Любые команды рисования, выполняемые с помощью этой стратегии, по умолчанию обрезаются до этой области. Приведенный ниже фрагмент кода иллюстрирует различия при переключении на использование внеэкранных текстур:
@Composable fun CompositingStrategyExamples() { Column( modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) { // Does not clip content even with a graphics layer usage here. By default, graphicsLayer // does not allocate + rasterize content into a separate layer but instead is used // for isolation. That is draw invalidations made outside of this graphicsLayer will not // re-record the drawing instructions in this composable as they have not changed Canvas( modifier = Modifier .graphicsLayer() .size(100.dp) // Note size of 100 dp here .border(2.dp, color = Color.Blue) ) { // ... and drawing a size of 200 dp here outside the bounds drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx())) } Spacer(modifier = Modifier.size(300.dp)) /* Clips content as alpha usage here creates an offscreen buffer to rasterize content into first then draws to the original destination */ Canvas( modifier = Modifier // force to an offscreen buffer .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) .size(100.dp) // Note size of 100 dp here .border(2.dp, color = Color.Blue) ) { /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the content gets clipped */ drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx())) } } }

ModulateAlpha
Эта стратегия композиции модулирует альфу для каждой из инструкций рисования, записанных в graphicsLayer
. Она не будет создавать внеэкранный буфер для альфы ниже 1.0f, если не установлен RenderEffect
, поэтому она может быть более эффективной для альфа-рендеринга. Однако она может предоставлять разные результаты для перекрывающегося контента. Для случаев использования, когда заранее известно, что контент не перекрывается, это может обеспечить лучшую производительность, чем CompositingStrategy.Auto
со значениями альфа меньше 1.
Ниже приведен еще один пример различных стратегий композиции — применение различных альфа-каналов к разным частям компонуемых объектов и применение стратегии Modulate
:
@Preview @Composable fun CompositingStrategy_ModulateAlpha() { Column( modifier = Modifier .fillMaxSize() .padding(32.dp) ) { // Base drawing, no alpha applied Canvas( modifier = Modifier.size(200.dp) ) { drawSquares() } Spacer(modifier = Modifier.size(36.dp)) // Alpha 0.5f applied to whole composable Canvas( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = 0.5f } ) { drawSquares() } Spacer(modifier = Modifier.size(36.dp)) // 0.75f alpha applied to each draw call when using ModulateAlpha Canvas( modifier = Modifier .size(200.dp) .graphicsLayer { compositingStrategy = CompositingStrategy.ModulateAlpha alpha = 0.75f } ) { drawSquares() } } } private fun DrawScope.drawSquares() { val size = Size(100.dp.toPx(), 100.dp.toPx()) drawRect(color = Red, size = size) drawRect( color = Purple, size = size, topLeft = Offset(size.width / 4f, size.height / 4f) ) drawRect( color = Yellow, size = size, topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f) ) } val Purple = Color(0xFF7E57C2) val Yellow = Color(0xFFFFCA28) val Red = Color(0xFFEF5350)

Записать содержимое компонуемого объекта в битовую карту
Распространенный вариант использования — создание Bitmap
из composable. Чтобы скопировать содержимое вашего composable в Bitmap
, создайте GraphicsLayer
с помощью rememberGraphicsLayer()
.
Перенаправьте команды рисования на новый слой с помощью drawWithContent()
и graphicsLayer.record{}
. Затем нарисуйте слой на видимом холсте с помощью drawLayer
:
val coroutineScope = rememberCoroutineScope() val graphicsLayer = rememberGraphicsLayer() Box( modifier = Modifier .drawWithContent { // call record to capture the content in the graphics layer graphicsLayer.record { // draw the contents of the composable into the graphics layer this@drawWithContent.drawContent() } // draw the graphics layer on the visible canvas drawLayer(graphicsLayer) } .clickable { coroutineScope.launch { val bitmap = graphicsLayer.toImageBitmap() // do something with the newly acquired bitmap } } .background(Color.White) ) { Text("Hello Android", fontSize = 26.sp) }
Вы можете сохранить битовую карту на диск и поделиться ею. Для получения более подробной информации см. полный фрагмент примера . Обязательно проверьте разрешения на устройстве перед попыткой сохранения на диск.
Пользовательский модификатор чертежа
Чтобы создать свой собственный модификатор, реализуйте интерфейс DrawModifier
. Это дает вам доступ к ContentDrawScope
, который является тем же самым, что отображается при использовании Modifier.drawWithContent()
. Затем вы можете извлечь общие операции рисования в пользовательские модификаторы рисования, чтобы очистить код и предоставить удобные оболочки; например, Modifier.background()
— это удобный DrawModifier
.
Например, если вы хотите реализовать Modifier
, который переворачивает содержимое по вертикали, вы можете создать его следующим образом:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Затем используйте этот перевернутый модификатор, примененный к Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )

Дополнительные ресурсы
Дополнительные примеры использования graphicsLayer
и пользовательского рисования см. на следующих ресурсах:
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Графика в Compose
- Настроить изображение {:#customize-image}
- Kotlin для Jetpack Compose
В дополнение к Canvas
composable, Compose имеет несколько полезных графических Modifiers
, которые помогают рисовать пользовательский контент. Эти модификаторы полезны, потому что их можно применять к любому composable.
Модификаторы рисования
Все команды рисования выполняются с помощью модификатора рисования в Compose. В Compose есть три основных модификатора рисования:
Базовым модификатором для рисования является drawWithContent
, где вы можете определить порядок рисования вашего Composable и команды рисования, выдаваемые внутри модификатора. drawBehind
— это удобная оболочка для drawWithContent
, в которой порядок рисования установлен позади содержимого composable. drawWithCache
вызывает внутри себя либо onDrawBehind
, либо onDrawWithContent
— и предоставляет механизм для кэширования объектов, созданных в них.
Modifier.drawWithContent
: выбрать порядок рисования
Modifier.drawWithContent
позволяет выполнять операции DrawScope
до или после содержимого компонуемого объекта. Обязательно вызовите drawContent
, чтобы затем отобразить фактическое содержимое компонуемого объекта. С помощью этого модификатора вы можете определить порядок операций, если хотите, чтобы ваше содержимое было отрисовано до или после ваших пользовательских операций рисования.
Например, если вы хотите отобразить радиальный градиент поверх своего контента, чтобы создать эффект замочной скважины фонарика в пользовательском интерфейсе, вы можете сделать следующее:
var pointerOffset by remember { mutableStateOf(Offset(0f, 0f)) } Column( modifier = Modifier .fillMaxSize() .pointerInput("dragging") { detectDragGestures { change, dragAmount -> pointerOffset += dragAmount } } .onSizeChanged { pointerOffset = Offset(it.width / 2f, it.height / 2f) } .drawWithContent { drawContent() // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI. drawRect( Brush.radialGradient( listOf(Color.Transparent, Color.Black), center = pointerOffset, radius = 100.dp.toPx(), ) ) } ) { // Your composables here }
Modifier.drawBehind
: Рисование позади компонуемого объекта
Modifier.drawBehind
позволяет выполнять операции DrawScope
за компонуемым содержимым, которое рисуется на экране. Если вы посмотрите на реализацию Canvas
, вы можете заметить, что это просто удобная оболочка вокруг Modifier.drawBehind
.
Чтобы нарисовать скругленный прямоугольник позади Text
:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
Что дает следующий результат:

Modifier.drawWithCache
: Рисование и кэширование объектов рисования
Modifier.drawWithCache
сохраняет объекты, созданные внутри него, в кэше. Объекты кэшируются до тех пор, пока размер области рисования остается прежним или любые считываемые объекты состояния не изменились. Этот модификатор полезен для повышения производительности вызовов рисования, поскольку он позволяет избежать необходимости перераспределять объекты (такие как: Brush, Shader, Path
и т. д.), созданные при рисовании.
В качестве альтернативы вы также можете кэшировать объекты, используя remember
, вне модификатора. Однако это не всегда возможно, поскольку у вас не всегда есть доступ к композиции. Использование drawWithCache
может быть более производительным, если объекты используются только для рисования.
Например, если вы создаете Brush
для рисования градиента позади Text
, использование drawWithCache
кэширует объект Brush
до тех пор, пока не изменится размер области рисования:
Text( "Hello Compose!", modifier = Modifier .drawWithCache { val brush = Brush.linearGradient( listOf( Color(0xFF9E82F0), Color(0xFF42A5F5) ) ) onDrawBehind { drawRoundRect( brush, cornerRadius = CornerRadius(10.dp.toPx()) ) } } )

Графические модификаторы
Modifier.graphicsLayer
: Применяет преобразования к компонуемым объектам
Modifier.graphicsLayer
— это модификатор, который превращает содержимое компонуемого чертежа в слой чертежа. Слой предоставляет несколько различных функций, таких как:
- Изоляция для его инструкций по рисованию (аналогично
RenderNode
). Инструкции по рисованию, захваченные как часть слоя, могут быть эффективно повторно выпущены конвейером рендеринга без повторного выполнения кода приложения. - Преобразования, применяемые ко всем инструкциям рисования, содержащимся в слое.
- Растеризация для возможностей композиции. Когда слой растеризован, выполняются его инструкции по рисованию, а вывод захватывается в буфер за пределами экрана. Составление такого буфера для последующих кадров происходит быстрее, чем выполнение отдельных инструкций, но он будет вести себя как битовая карта, когда применяются преобразования, такие как масштабирование или поворот.
Трансформации
Modifier.graphicsLayer
обеспечивает изоляцию для своих инструкций по рисованию; например, различные преобразования могут быть применены с помощью Modifier.graphicsLayer
. Их можно анимировать или изменять без необходимости повторного выполнения лямбда-функции рисования.
Modifier.graphicsLayer
не изменяет измеренный размер или размещение вашего компонуемого элемента, поскольку он влияет только на фазу рисования. Это означает, что ваш компонуемый элемент может перекрывать другие, если он в конечном итоге будет рисоваться за пределами границ своего макета.
С помощью этого модификатора можно применять следующие преобразования:
Масштаб - увеличить размер
scaleX
и scaleY
увеличивают или уменьшают содержимое в горизонтальном или вертикальном направлении соответственно. Значение 1.0f
указывает на отсутствие изменения масштаба, значение 0.5f
означает половину размера.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.scaleX = 1.2f this.scaleY = 0.8f } )
Перевод
translationX
и translationY
можно изменять с помощью graphicsLayer
, translationX
перемещает компонуемый объект влево или вправо. translationY
перемещает компонуемый объект вверх или вниз.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Вращение
Установите rotationX
для вращения по горизонтали, rotationY
для вращения по вертикали и rotationZ
для вращения по оси Z (стандартное вращение). Это значение указывается в градусах (0-360).
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Источник
Можно указать transformOrigin
. Затем он используется как точка, из которой происходят преобразования. Во всех примерах до сих пор использовался TransformOrigin.Center
, который находится в точке (0.5f, 0.5f)
. Если указать начало координат в точке (0f, 0f)
, преобразования начнутся с верхнего левого угла составного объекта.
Если изменить начало координат с помощью преобразования rotationZ
, можно увидеть, что элемент вращается вокруг верхнего левого угла компонуемого объекта:
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.transformOrigin = TransformOrigin(0f, 0f) this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Клип и форма
Shape определяет контур, по которому обрезается содержимое, когда clip = true
. В этом примере мы устанавливаем два поля для двух разных обрезов — один с помощью переменной clip graphicsLayer
, а другой — с помощью удобной оболочки Modifier.clip
.
Column(modifier = Modifier.padding(16.dp)) { Box( modifier = Modifier .size(200.dp) .graphicsLayer { clip = true shape = CircleShape } .background(Color(0xFFF06292)) ) { Text( "Hello Compose", style = TextStyle(color = Color.Black, fontSize = 46.sp), modifier = Modifier.align(Alignment.Center) ) } Box( modifier = Modifier .size(200.dp) .clip(CircleShape) .background(Color(0xFF4DB6AC)) ) }
Содержимое первого поля (текст «Hello Compose») обрезается по форме круга:

Если затем применить translationY
к верхнему розовому кругу, то вы увидите, что границы Composable остаются прежними, но круг рисуется под нижним кругом (и за его пределами).

Чтобы обрезать компонуемый объект до области, в которой он нарисован, можно добавить еще один Modifier.clip(RectangleShape)
в начале цепочки модификаторов. После этого содержимое останется внутри исходных границ.
Column(modifier = Modifier.padding(16.dp)) { Box( modifier = Modifier .clip(RectangleShape) .size(200.dp) .border(2.dp, Color.Black) .graphicsLayer { clip = true shape = CircleShape translationY = 50.dp.toPx() } .background(Color(0xFFF06292)) ) { Text( "Hello Compose", style = TextStyle(color = Color.Black, fontSize = 46.sp), modifier = Modifier.align(Alignment.Center) ) } Box( modifier = Modifier .size(200.dp) .clip(RoundedCornerShape(500.dp)) .background(Color(0xFF4DB6AC)) ) }

Альфа
Modifier.graphicsLayer
можно использовать для установки alpha
(непрозрачности) для всего слоя. 1.0f
— полная непрозрачность, 0.0f
— невидимость.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )

Стратегия композиции
Работа с альфа-каналом и прозрачностью может быть не такой простой, как изменение одного альфа-значения. Помимо изменения альфа-канала, есть также возможность задать CompositingStrategy
для graphicsLayer
. CompositingStrategy
определяет, как содержимое компонуемого объекта компонуется (собирается вместе) с другим содержимым, уже нарисованным на экране.
Различные стратегии:
Авто (по умолчанию)
Стратегия компоновки определяется остальными параметрами graphicsLayer
. Он рендерит слой в буфер за пределами экрана, если альфа меньше 1.0f или установлен RenderEffect
. Всякий раз, когда альфа меньше 1f, автоматически создается слой компоновки для рендеринга содержимого, а затем отрисовки этого буфера за пределами экрана в месте назначения с соответствующей альфой. Установка RenderEffect
или overscroll всегда рендерит содержимое в буфер за пределами экрана независимо от установленного CompositingStrategy
.
Закадровый
Содержимое компонуемого всегда растрируется в закадровую текстуру или битовую карту перед рендерингом в место назначения. Это полезно для применения операций BlendMode
для маскирования содержимого и для производительности при рендеринге сложных наборов инструкций рисования.
Пример использования CompositingStrategy.Offscreen
— BlendModes
. Взгляните на пример ниже. Допустим, вы хотите удалить части Image
, которое можно составить, выполнив команду рисования, которая использует BlendMode.Clear
. Если вы не установите compositingStrategy
на CompositingStrategy.Offscreen
, BlendMode
будет взаимодействовать со всем содержимым под ним.
Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .size(120.dp) .aspectRatio(1f) .background( Brush.linearGradient( listOf( Color(0xFFC5E1A5), Color(0xFF80DEEA) ) ) ) .padding(8.dp) .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } .drawWithCache { val path = Path() path.addOval( Rect( topLeft = Offset.Zero, bottomRight = Offset(size.width, size.height) ) ) onDrawWithContent { clipPath(path) { // this draws the actual image - if you don't call drawContent, it wont // render anything this@onDrawWithContent.drawContent() } val dotSize = size.width / 8f // Clip a white border for the content drawCircle( Color.Black, radius = dotSize, center = Offset( x = size.width - dotSize, y = size.height - dotSize ), blendMode = BlendMode.Clear ) // draw the red circle indication drawCircle( Color(0xFFEF5350), radius = dotSize * 0.8f, center = Offset( x = size.width - dotSize, y = size.height - dotSize ) ) } } )
Устанавливая CompositingStrategy
на Offscreen
, он создает внеэкранную текстуру для выполнения команд (применяя BlendMode
только к содержимому этого компонуемого объекта). Затем он визуализирует его поверх того, что уже визуализируется на экране, не влияя на уже нарисованное содержимое.

Если вы не использовали CompositingStrategy.Offscreen
, результаты применения BlendMode.Clear
очищают все пиксели в месте назначения, независимо от того, что уже было установлено, оставляя буфер рендеринга окна (черный) видимым. Многие из BlendModes
, которые включают альфа, не будут работать так, как ожидается, без буфера offscreen. Обратите внимание на черное кольцо вокруг красного кругового индикатора:

Чтобы понять это немного глубже: если бы у приложения был полупрозрачный фон окна, и вы не использовали CompositingStrategy.Offscreen
, BlendMode
взаимодействовал бы со всем приложением. Он бы очистил все пиксели, чтобы показать приложение или обои под ним, как в этом примере:

Стоит отметить, что при использовании CompositingStrategy.Offscreen
создается и отображается на экране внеэкранная текстура, имеющая размер области рисования. Любые команды рисования, выполняемые с помощью этой стратегии, по умолчанию обрезаются до этой области. Приведенный ниже фрагмент кода иллюстрирует различия при переключении на использование внеэкранных текстур:
@Composable fun CompositingStrategyExamples() { Column( modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) { // Does not clip content even with a graphics layer usage here. By default, graphicsLayer // does not allocate + rasterize content into a separate layer but instead is used // for isolation. That is draw invalidations made outside of this graphicsLayer will not // re-record the drawing instructions in this composable as they have not changed Canvas( modifier = Modifier .graphicsLayer() .size(100.dp) // Note size of 100 dp here .border(2.dp, color = Color.Blue) ) { // ... and drawing a size of 200 dp here outside the bounds drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx())) } Spacer(modifier = Modifier.size(300.dp)) /* Clips content as alpha usage here creates an offscreen buffer to rasterize content into first then draws to the original destination */ Canvas( modifier = Modifier // force to an offscreen buffer .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) .size(100.dp) // Note size of 100 dp here .border(2.dp, color = Color.Blue) ) { /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the content gets clipped */ drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx())) } } }

ModulateAlpha
Эта стратегия композиции модулирует альфу для каждой из инструкций рисования, записанных в graphicsLayer
. Она не будет создавать внеэкранный буфер для альфы ниже 1.0f, если не установлен RenderEffect
, поэтому она может быть более эффективной для альфа-рендеринга. Однако она может предоставлять разные результаты для перекрывающегося контента. Для случаев использования, когда заранее известно, что контент не перекрывается, это может обеспечить лучшую производительность, чем CompositingStrategy.Auto
со значениями альфа меньше 1.
Ниже приведен еще один пример различных стратегий композиции — применение различных альфа-каналов к разным частям компонуемых объектов и применение стратегии Modulate
:
@Preview @Composable fun CompositingStrategy_ModulateAlpha() { Column( modifier = Modifier .fillMaxSize() .padding(32.dp) ) { // Base drawing, no alpha applied Canvas( modifier = Modifier.size(200.dp) ) { drawSquares() } Spacer(modifier = Modifier.size(36.dp)) // Alpha 0.5f applied to whole composable Canvas( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = 0.5f } ) { drawSquares() } Spacer(modifier = Modifier.size(36.dp)) // 0.75f alpha applied to each draw call when using ModulateAlpha Canvas( modifier = Modifier .size(200.dp) .graphicsLayer { compositingStrategy = CompositingStrategy.ModulateAlpha alpha = 0.75f } ) { drawSquares() } } } private fun DrawScope.drawSquares() { val size = Size(100.dp.toPx(), 100.dp.toPx()) drawRect(color = Red, size = size) drawRect( color = Purple, size = size, topLeft = Offset(size.width / 4f, size.height / 4f) ) drawRect( color = Yellow, size = size, topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f) ) } val Purple = Color(0xFF7E57C2) val Yellow = Color(0xFFFFCA28) val Red = Color(0xFFEF5350)

Записать содержимое компонуемого объекта в битовую карту
Распространенный вариант использования — создание Bitmap
из composable. Чтобы скопировать содержимое вашего composable в Bitmap
, создайте GraphicsLayer
с помощью rememberGraphicsLayer()
.
Перенаправьте команды рисования на новый слой с помощью drawWithContent()
и graphicsLayer.record{}
. Затем нарисуйте слой на видимом холсте с помощью drawLayer
:
val coroutineScope = rememberCoroutineScope() val graphicsLayer = rememberGraphicsLayer() Box( modifier = Modifier .drawWithContent { // call record to capture the content in the graphics layer graphicsLayer.record { // draw the contents of the composable into the graphics layer this@drawWithContent.drawContent() } // draw the graphics layer on the visible canvas drawLayer(graphicsLayer) } .clickable { coroutineScope.launch { val bitmap = graphicsLayer.toImageBitmap() // do something with the newly acquired bitmap } } .background(Color.White) ) { Text("Hello Android", fontSize = 26.sp) }
Вы можете сохранить битовую карту на диск и поделиться ею. Для получения более подробной информации см. полный фрагмент примера . Обязательно проверьте разрешения на устройстве перед попыткой сохранения на диск.
Пользовательский модификатор чертежа
Чтобы создать свой собственный модификатор, реализуйте интерфейс DrawModifier
. Это дает вам доступ к ContentDrawScope
, который является тем же самым, что отображается при использовании Modifier.drawWithContent()
. Затем вы можете извлечь общие операции рисования в пользовательские модификаторы рисования, чтобы очистить код и предоставить удобные оболочки; например, Modifier.background()
— это удобный DrawModifier
.
Например, если вы хотите реализовать Modifier
, который переворачивает содержимое по вертикали, вы можете создать его следующим образом:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Затем используйте этот перевернутый модификатор, примененный к Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )

Дополнительные ресурсы
Дополнительные примеры использования graphicsLayer
и пользовательского рисования см. на следующих ресурсах:
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Графика в Compose
- Настроить изображение {:#customize-image}
- Kotlin для Jetpack Compose