Oprócz Canvas
komponentów kompozycyjnych Compose ma kilka przydatnych elementów graficznychModifiers
, które pomagają w rysowaniu treści niestandardowych. Te modyfikatory są przydatne, ponieważ można je zastosować do dowolnego elementu kompozycyjnego.
Modyfikatory rysowania
Wszystkie polecenia rysowania są wykonywane za pomocą modyfikatora rysowania w Compose. W Compose są 3 główne modyfikatory rysowania:
Podstawowy modyfikator rysowania to drawWithContent
, w którym możesz określić kolejność rysowania elementu kompozycyjnego i polecenia rysowania wydawane wewnątrz modyfikatora. drawBehind
to wygodna funkcja opakowująca drawWithContent
, która ma kolejność rysowania ustawioną na „za treściami komponentu”. drawWithCache
wywołuje w swoim wnętrzu funkcję onDrawBehind
lub onDrawWithContent
i zapewnia mechanizm buforowania utworzonych w nich obiektów.
Modifier.drawWithContent
: wybieranie kolejności rysowania
Modifier.drawWithContent
umożliwia wykonywanie operacji DrawScope
przed treścią funkcji kompozycyjnej lub po niej. Pamiętaj, aby wywołać funkcję drawContent
, aby wyrenderować rzeczywistą treść funkcji kompozycyjnej. Za pomocą tego modyfikatora możesz określić kolejność operacji, jeśli chcesz, aby Twoje treści były rysowane przed niestandardowymi operacjami rysowania lub po nich.
Jeśli na przykład chcesz renderować gradient promienisty na treści, aby utworzyć efekt dziurki od klucza latarki w interfejsie, możesz wykonać te czynności:
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
: rysowanie za elementem kompozycyjnym
Modifier.drawBehind
umożliwia wykonywanie operacji DrawScope
za treściami kompozycyjnymi wyświetlanymi na ekranie. Jeśli przyjrzysz się implementacji Canvas
, zauważysz, że jest to tylko wygodna otoczka funkcji Modifier.drawBehind
.
Aby narysować zaokrąglony prostokąt za elementem Text
:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
co daje ten wynik:

Modifier.drawWithCache
: Rysowanie i buforowanie obiektów rysowania
Modifier.drawWithCache
przechowuje w pamięci podręcznej obiekty utworzone w jego obrębie. Obiekty są przechowywane w pamięci podręcznej, dopóki rozmiar obszaru rysowania jest taki sam lub dopóki nie zmienią się żadne obiekty stanu, które są odczytywane. Ten modyfikator przydaje się do zwiększania wydajności wywołań rysowania, ponieważ pozwala uniknąć ponownego przydzielania obiektów (np. Brush, Shader, Path
itp.) tworzonych podczas rysowania.
Możesz też buforować obiekty za pomocą remember
poza modyfikatorem. Nie zawsze jest to jednak możliwe, ponieważ nie zawsze masz dostęp do kompozycji. Jeśli obiekty są używane tylko do rysowania, lepszym rozwiązaniem może być użycie drawWithCache
.
Jeśli na przykład utworzysz Brush
, aby narysować gradient za Text
, używając
drawWithCache
, obiekt Brush
zostanie zapisany w pamięci podręcznej do czasu zmiany rozmiaru obszaru rysowania:
Text( "Hello Compose!", modifier = Modifier .drawWithCache { val brush = Brush.linearGradient( listOf( Color(0xFF9E82F0), Color(0xFF42A5F5) ) ) onDrawBehind { drawRoundRect( brush, cornerRadius = CornerRadius(10.dp.toPx()) ) } } )

Modyfikatory grafiki
Modifier.graphicsLayer
: stosowanie przekształceń do funkcji kompozycyjnych
Modifier.graphicsLayer
to modyfikator, który powoduje, że zawartość funkcji kompozycyjnej jest rysowana w warstwie rysowania. Warstwa zapewnia kilka różnych funkcji, takich jak:
- Izolacja instrukcji rysowania (podobnie jak w przypadku
RenderNode
). Instrukcje rysowania przechwycone w ramach warstwy mogą być ponownie wydawane przez proces renderowania bez ponownego wykonywania kodu aplikacji. - Przekształcenia, które są stosowane do wszystkich instrukcji rysowania zawartych w warstwie.
- Rasteryzacja na potrzeby funkcji kompozycji. Gdy warstwa jest rasteryzowana, wykonywane są instrukcje rysowania, a wynik jest zapisywany w buforze poza ekranem. Łączenie takiego bufora z kolejnymi klatkami jest szybsze niż wykonywanie poszczególnych instrukcji, ale po zastosowaniu przekształceń, takich jak skalowanie lub obracanie, będzie się zachowywać jak bitmapa.
Przekształcenia
Modifier.graphicsLayer
zapewnia izolację instrukcji rysowania, np. za pomocą Modifier.graphicsLayer
można stosować różne przekształcenia.
Można je animować lub modyfikować bez konieczności ponownego wykonywania funkcji lambda rysowania.
Modifier.graphicsLayer
nie zmienia zmierzonego rozmiaru ani umiejscowienia komponentu, ponieważ wpływa tylko na fazę rysowania. Oznacza to, że komponent może nakładać się na inne, jeśli zostanie narysowany poza granicami układu.
Za pomocą tego modyfikatora można zastosować te przekształcenia:
Skala – zwiększanie rozmiaru
scaleX
i scaleY
powiększają lub pomniejszają treści odpowiednio w kierunku poziomym lub pionowym. Wartość 1.0f
oznacza brak zmiany skali, a wartość 0.5f
oznacza połowę wymiaru.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.scaleX = 1.2f this.scaleY = 0.8f } )
Tłumaczenie
translationX
i translationY
można zmienić za pomocą graphicsLayer
.translationX
przesuwa element kompozycyjny w lewo lub w prawo. translationY
przesuwa element kompozycyjny w górę lub w dół.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Obrót
Ustaw rotationX
, aby obracać w poziomie, rotationY
, aby obracać w pionie, i rotationZ
, aby obracać wzdłuż osi Z (standardowy obrót). Ta wartość jest podawana w stopniach (0–360).
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Źródło
Możesz określić transformOrigin
. Jest on następnie używany jako punkt, od którego
następują przekształcenia. W dotychczasowych przykładach używaliśmy znaku
TransformOrigin.Center
, który znajduje się w pozycji (0.5f, 0.5f)
. Jeśli określisz punkt początkowy w pozycji (0f, 0f)
, przekształcenia będą się rozpoczynać w lewym górnym rogu elementu kompozycyjnego.
Jeśli zmienisz punkt początkowy za pomocą rotationZ
transformacji, zobaczysz, że element obraca się wokół lewego górnego rogu elementu kompozycyjnego:
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 } )
Przytnij i zmień kształt
Kształt określa kontur, do którego przycinana jest zawartość, gdy clip = true
. W tym przykładzie ustawiamy 2 pola z 2 różnymi klipami – jeden z nich korzysta ze zmiennej klipu graphicsLayer
, a drugi z wygodnego opakowania 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)) ) }
Zawartość pierwszego pola (tekst „Hello Compose”) jest przycięta do kształtu koła:

Jeśli następnie zastosujesz translationY
do górnego różowego kółka, zobaczysz, że granice elementu kompozycyjnego pozostają takie same, ale kółko jest rysowane pod dolnym kółkiem (i poza jego granicami).

Aby przyciąć komponent do regionu, w którym jest rysowany, możesz dodać kolejny modyfikator Modifier.clip(RectangleShape)
na początku łańcucha modyfikatorów. Treść pozostanie w pierwotnych granicach.
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)) ) }

Alfa
Za pomocą tego ustawienia można określić alpha
(przezroczystość) całej warstwy.Modifier.graphicsLayer
1.0f
jest całkowicie nieprzezroczysty, a 0.0f
– niewidoczny.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )

Strategia kompozycji
Praca z wartościami alfa i przezroczystości może być bardziej skomplikowana niż zmiana pojedynczej wartości alfa. Oprócz zmiany wartości alfa możesz też ustawić CompositingStrategy
na graphicsLayer
. CompositingStrategy
określa, w jaki sposób treść komponentu kompozycyjnego jest komponowana (łączona) z inną treścią już narysowaną na ekranie.
Dostępne strategie to:
Automatycznie (domyślnie)
Strategia kompozycji jest określana przez pozostałe parametry graphicsLayer
. Renderuje warstwę do bufora poza ekranem, jeśli wartość alfa jest mniejsza niż 1,0 f lub ustawiony jest atrybut RenderEffect
. Gdy wartość alfa jest mniejsza niż 1, automatycznie tworzona jest warstwa kompozycji, która renderuje zawartość, a następnie rysuje ten bufor poza ekranem w miejscu docelowym z odpowiednią wartością alfa. Ustawienie wartości RenderEffect
lub overscroll zawsze powoduje renderowanie treści w buforze poza ekranem niezależnie od ustawienia CompositingStrategy
.
Poza ekranem
Zawartość elementu kompozycyjnego jest zawsze rasteryzowana do tekstury lub mapy bitowej poza ekranem przed renderowaniem w miejscu docelowym. Jest to przydatne w przypadku stosowania operacji BlendMode
do maskowania treści oraz w przypadku wydajności podczas renderowania złożonych zestawów instrukcji rysowania.
Przykładem użycia CompositingStrategy.Offscreen
jest BlendModes
. Załóżmy, że na przykładzie poniżej chcesz usunąć części funkcji kompozycyjnej Image
, wydając polecenie rysowania, które używa BlendMode.Clear
. Jeśli nie ustawisz compositingStrategy
na CompositingStrategy.Offscreen
, element BlendMode
będzie wchodzić w interakcję z całą zawartością poniżej.
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 ) ) } } )
Ustawienie CompositingStrategy
na Offscreen
powoduje utworzenie tekstury poza ekranem, na której będą wykonywane polecenia (zastosowanie BlendMode
tylko do zawartości tego komponentu). Następnie renderuje go na tym, co jest już wyświetlane na ekranie, nie wpływając na już narysowaną treść.

Jeśli nie użyjesz CompositingStrategy.Offscreen
, zastosowanie BlendMode.Clear
wyczyści wszystkie piksele w miejscu docelowym niezależnie od tego, co było już ustawione, pozostawiając widoczny bufor renderowania okna (czarny). Wiele BlendModes
, które wykorzystują kanał alfa, nie będzie działać zgodnie z oczekiwaniami bez bufora poza ekranem. Zwróć uwagę na czarny pierścień wokół czerwonego wskaźnika:

Aby lepiej to zrozumieć: jeśli aplikacja miała przezroczyste tło okna i nie używasz CompositingStrategy.Offscreen
, BlendMode
będzie wchodzić w interakcję z całą aplikacją. Wyczyści wszystkie piksele, aby wyświetlić aplikację lub tapetę pod spodem, jak w tym przykładzie:

Warto zauważyć, że podczas korzystania z CompositingStrategy.Offscreen
tworzona jest tekstura poza ekranem o rozmiarze obszaru rysowania, która jest renderowana z powrotem na ekranie. Wszystkie polecenia rysowania wykonane za pomocą tej strategii są domyślnie przycinane do tego regionu. Fragment kodu poniżej pokazuje różnice po przejściu na tekstury poza ekranem:
@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
Ta strategia kompozycji moduluje wartość alfa dla każdej instrukcji rysowania zarejestrowanej w graphicsLayer
. Nie utworzy bufora poza ekranem dla wartości alfa poniżej 1,0 f, chyba że ustawiono wartość RenderEffect
, więc może być bardziej wydajny w przypadku renderowania alfa. Może jednak zwracać różne wyniki w przypadku nakładających się treści. W przypadku zastosowań, w których z góry wiadomo, że treści nie nakładają się na siebie, może to zapewnić lepszą wydajność niż CompositingStrategy.Auto
z wartościami alfa mniejszymi niż 1.
Poniżej znajdziesz kolejny przykład różnych strategii kompozycji – zastosowanie różnych wartości alfa do różnych części komponentów i zastosowanie strategii 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)

Zapisywanie zawartości komponentu do mapy bitowej
Częstym przypadkiem użycia jest tworzenie Bitmap
na podstawie funkcji kompozycyjnej. Aby skopiować zawartość funkcji kompozycyjnej do Bitmap
, utwórz GraphicsLayer
za pomocą rememberGraphicsLayer()
.
Przekieruj polecenia rysowania do nowej warstwy za pomocą poleceń drawWithContent()
i graphicsLayer.record{}
. Następnie narysuj warstwę na widocznym obszarze roboczym za pomocą funkcji 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) }
Możesz zapisać bitmapę na dysku i ją udostępnić. Więcej informacji znajdziesz w pełnym przykładzie kodu. Zanim spróbujesz zapisać plik na dysku, sprawdź uprawnienia na urządzeniu.
Modyfikator rysowania niestandardowego
Aby utworzyć własny modyfikator niestandardowy, zaimplementuj interfejs DrawModifier
. Dzięki temu uzyskasz dostęp do ContentDrawScope
, czyli tego samego, co jest udostępniane podczas korzystania z Modifier.drawWithContent()
. Możesz następnie wyodrębnić typowe operacje rysowania do niestandardowych modyfikatorów rysowania, aby uporządkować kod i zapewnić wygodne opakowania, np. Modifier.background()
jest wygodnym DrawModifier
.
Jeśli na przykład chcesz wdrożyć Modifier
, które odwraca zawartość w pionie, możesz utworzyć je w ten sposób:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Następnie użyj odwróconego modyfikatora zastosowanego do klawisza Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )

Dodatkowe materiały
Więcej przykładów użycia graphicsLayer
i rysowania niestandardowego znajdziesz w tych materiałach:
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Grafika w komponowaniu
- Dostosowywanie obrazu {:#customize-image}
- Kotlin w Jetpack Compose