Oprócz funkcji kompozycyjnej Canvas
w aplikacji Compose znajdziesz też przydatne grafiki
Modifiers
, które ułatwiają rysowanie treści niestandardowych. Te modyfikatory są przydatne
ponieważ można je zastosować do każdego elementu kompozycyjnego.
Modyfikatory rysowania
Wszystkie polecenia rysowania są wykonywane przy użyciu modyfikatora rysunku w narzędziu Compose. Istnieją trzy główne modyfikatory rysowania w Compose:
Podstawowy modyfikator rysowania to drawWithContent
. Możesz wybrać wartość
kolejność rysowania elementu kompozycyjnego oraz polecenia rysowania wysyłane w elemencie
modyfikator. drawBehind
to wygodny kod wokół drawWithContent
, który ma
kolejność rysowania ustawiona w tle treści kompozycyjnej. drawWithCache
wywołuje w nim onDrawBehind
lub onDrawWithContent
i zapewnia
do buforowania utworzonych obiektów.
Modifier.drawWithContent
: wybierz kolejność rysowania
Modifier.drawWithContent
umożliwia
wykonaj operacje DrawScope
przed lub po treści zapytania
kompozycyjne. Pamiętaj, by wywołać drawContent
, by potem wyświetlić rzeczywistą treść
kompozycyjne. Za pomocą tego modyfikatora można określić kolejność działań, jeśli
chcesz, aby zawartość była rysowana przed rysunkiem niestandardowym lub po nim
operacji.
Jeśli na przykład chcesz renderować gradient promieniowy na swojej treści, aby wywołać w interfejsie efekt dziurki od klucza w latarce, możesz wykonać następujące 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
: zarysowanie elementu kompozycyjnego
Modifier.drawBehind
umożliwia Ci
DrawScope
operacje za treściami kompozycyjnymi rysowanymi na ekranie. Jeśli
jeśli zapoznasz się z implementacją Canvas
, możesz zauważyć, że
jest tylko wygodnym kodem elementu 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 następujący wynik:
![Tekst i tło utworzone za pomocą metody Modifier.drawBehind](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/modifier_draw_behind.png?authuser=19&hl=pl)
Modifier.drawWithCache
: rysowanie i zapisywanie obiektów rysunkowych w pamięci podręcznej
Modifier.drawWithCache
zachowuje obiekty
tworzonych w niej w pamięci podręcznej. Obiekty są przechowywane w pamięci podręcznej, jeśli rozmiar
obszaru rysowania jest taka sama lub żadne odczytywane obiekty stanu
została zmieniona. Ten modyfikator jest przydatny do poprawy wydajności wywołań rysowania, ponieważ
Pozwala to uniknąć ponownej alokacji obiektów (np. Brush, Shader, Path
).
tworzone podczas rysowania.
Można też buforować obiekty za pomocą remember
, poza
modyfikator. Nie zawsze jest to jednak możliwe, ponieważ nie zawsze masz dostęp
do kompozycji. Użycie właściwości drawWithCache
może być skuteczniejsze, jeśli
obiekty służą tylko do rysowania.
Jeśli na przykład utworzysz obiekt Brush
, aby narysować gradient za obiektem Text
, używając funkcji
drawWithCache
zapisuje w pamięci podręcznej obiekt Brush
do momentu zmiany rozmiaru obszaru rysowania
zmiany:
Text( "Hello Compose!", modifier = Modifier .drawWithCache { val brush = Brush.linearGradient( listOf( Color(0xFF9E82F0), Color(0xFF42A5F5) ) ) onDrawBehind { drawRoundRect( brush, cornerRadius = CornerRadius(10.dp.toPx()) ) } } )
![Buforowanie obiektu Brush za pomocą metody pullWithCache](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/modifier_draw_with_cache.png?authuser=19&hl=pl)
Modyfikatory grafiki
Modifier.graphicsLayer
: stosuj przekształcenia do elementów kompozycyjnych
Modifier.graphicsLayer
to modyfikator, który przekształca zawartość elementu kompozycyjnego w warstwę rysowania. O
warstwa udostępnia kilka funkcji, takich jak:
- Izolacja w instrukcjach rysowania (podobne do:
RenderNode
). Rysunek instrukcje uchwycone w ramach warstwy mogą zostać ponownie wydane przez potoku renderowania bez ponownego uruchamiania kodu aplikacji. - Przekształcenia, które mają zastosowanie do wszystkich instrukcji rysowania zawartych w warstwę.
- Rasteryzacja w celu uzyskania możliwości kompozycji. Przy zrasteryzowaniu warstwy instrukcje rysowania są wykonywane, a dane wyjściowe są zapisywane na ekranie poza ekranem bufora. Komponowanie takiego bufora na potrzeby kolejnych klatek jest szybsze niż poszczególnych instrukcji, ale będzie zachowywać się jak bitmapa, gdy takie jak skalowanie czy obrót.
Transformacje
Pole Modifier.graphicsLayer
zapewnia izolację instrukcji rysowania. w przypadku
można zastosować różne przekształcenia, używając funkcji Modifier.graphicsLayer
.
Mogą być one animowane lub modyfikowane bez konieczności ponownego uruchamiania rysunku
lambda.
Modifier.graphicsLayer
nie zmienia mierzonego rozmiaru ani miejsca docelowego
kompozycyjne, bo ma wpływ tylko na fazę rysowania. Oznacza to, że funkcja kompozycyjna
może nakładać się na inne, jeśli rysuje poza granicami swojego układu.
Przy użyciu tego modyfikatora można zastosować te przekształcenia:
Skala – zwiększ rozmiar
scaleX
i scaleY
pozwalają powiększać lub pomniejszać treści w orientacji poziomej lub pionowej.
kierunku. Wartość 1.0f
oznacza brak zmiany skali; 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
Elementy translationX
i translationY
można zmienić za pomocą tagów graphicsLayer
,
translationX
przesuwa element kompozycyjny w lewo lub w prawo. translationY
przesuwa
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ć urządzenie w poziomie, a rotationY
, aby obracać je w pionie,
rotationZ
, aby obrócić je wokół osi Z (obrót standardowy). Ta wartość jest określona
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żna określić transformOrigin
. Jest on następnie używany jako punkt, z którego
i przekształcania danych. We wszystkich dotychczasowych przykładach użyto
TransformOrigin.Center
, czyli o (0.5f, 0.5f)
. Jeśli określisz punkt początkowy
(0f, 0f)
, przekształcenia zaczynają się w lewym górnym rogu okna
kompozycyjne.
Jeśli zmienisz źródło za pomocą przekształcenia rotationZ
, możesz zobaczyć, ż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 } )
Klip i kształt
Kształt określa kontur, do którego będą przechodzić treści, gdy clip = true
. W
W tym przykładzie ustawiliśmy 2 pola z 2 różnymi klipami
graphicsLayer
zmienną klipu, a druga – za pomocą 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 przycinana do kształt koła:
![Klip został zastosowany do możliwości kompozycyjnej w Box](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/clip_applied.png?authuser=19&hl=pl)
Jeśli następnie zastosujesz wymiar translationY
do górnego różowego okręgu, zobaczysz, że granice
elementów kompozycyjnych są nadal takie same, ale okrąg rysuje się pod spodem
okręgu (i poza jego granicami).
![Klip został zastosowany z przesunięciem Y i czerwone obramowanie dla konturu](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/clip_applied_red_border.png?authuser=19&hl=pl)
Aby przyciąć kompozycję do regionu, w którym jest utworzona, możesz dodać kolejny
Modifier.clip(RectangleShape)
na początku łańcucha modyfikatorów. Treść
a potem pozostaje 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)) ) }
![Klip został zastosowany nad przekształceniem warstwy graficznej](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/clip_applied_everything.png?authuser=19&hl=pl)
Alfa
Za pomocą Modifier.graphicsLayer
można ustawić alpha
(przezroczystość) dla całego obiektu
warstwę. Element 1.0f
jest całkowicie nieprzezroczysty, a element 0.0f
jest niewidoczny.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )
![Obraz z zastosowaną wersją alfa](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/image_alpha.png?authuser=19&hl=pl)
Strategia komponowania
Korzystanie z wersji alfa i przejrzystości może nie być tak proste, jak
wartości alfa. Poza zmianą kanału alfa możesz też ustawić
CompositingStrategy
– graphicsLayer
. CompositingStrategy
określa, w jaki sposób
treść funkcji kompozycyjnej jest skomponowana (połączona) z drugą
treści, które są już narysowane na ekranie.
Dostępne strategie to:
Automatycznie (domyślnie)
Strategia komponowania jest określana przez pozostałe pola graphicsLayer
. Wyrenderuje warstwę w buforze poza ekranem, jeśli poziom alfa jest mniejszy niż
Ustawiono wartość 1.0f lub RenderEffect
. Jeśli alfa jest mniejsza niż 1f,
warstwa komponowania jest tworzona automatycznie w celu renderowania zawartości, a następnie rysowana
ten bufor poza ekranem do miejsca docelowego z odpowiednim alfa. Ustawienie
RenderEffect
lub nadmiernie przewijanie zawsze renderuje treść poza ekranem
bufor niezależnie od ustawienia CompositingStrategy
.
Poza ekranem
Zawartość funkcji kompozycyjnej jest zawsze zrastrowana na obszar poza ekranem
tekstury lub bitmapy przed renderowaniem w miejscu docelowym. Przydaje się to w przypadku
stosowania operacji BlendMode
do maskowania treści oraz do wydajności, gdy
złożonych instrukcji rysowania.
Przykład użycia CompositingStrategy.Offscreen
z BlendModes
. Patrząc na poniższy przykład,
Załóżmy, że chcesz usunąć fragmenty elementu Image
kompozycyjnego, uruchamiając polecenie rysowania, które
używa BlendMode.Clear
. Jeśli nie ustawisz właściwości compositingStrategy
na
CompositingStrategy.Offscreen
, BlendMode
współdziała ze wszystkimi treściami
pod nim.
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 ) ) } } )
Jeśli CompositingStrategy
ma wartość Offscreen
, powoduje to utworzenie elementu poza ekranem
tekstury do wykonania poleceń (stosując BlendMode
tylko do
zawartości tego elementu kompozycyjnego). Następnie renderuje ją nad tym, co jest już
renderowane na ekranie i nie mają wpływu na treść już narysowaną.
![Modifier.drawWithContent na obrazie z oznaczeniem okręgu i użyciem funkcji BlendMode.Clear w aplikacji.](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/complex_modifier_example_drawWithCache_background.png?authuser=19&hl=pl)
Jeśli nie używasz karty CompositingStrategy.Offscreen
, wyniki zastosowania
BlendMode.Clear
usuwa wszystkie piksele w miejscu docelowym, niezależnie od tego,
została już ustawiona, pozostawiając czarny bufor renderowania okna. Wiele spośród
BlendModes
, w których występuje wersja alfa, nie będzie działać zgodnie z oczekiwaniami bez
bufor poza ekranem. Zwróć uwagę na czarny pierścień wokół wskaźnika czerwonego okręgu:
![Modifier.drawWithContent na obrazie z zaznaczonym okręgiem, z ustawieniem BlendMode.Clear i bez ustawionej strategii tworzenia kompozycji](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/blendmode_without_compositing_strategy.png?authuser=19&hl=pl)
Jeśli aplikacja ma półprzezroczyste okno,
i nie użyto CompositingStrategy.Offscreen
,
Aplikacja BlendMode
będzie wchodzić w interakcję z całą aplikacją. Wszystkie piksele zostaną usunięte
aplikację lub tapetę poniżej, jak w tym przykładzie:
![Nie ustawiono strategii tworzenia kompozycji i nie używasz BlendMode.Clear w aplikacji z przezroczystym tłem okna. Wokół czerwonego okręgu stanu wyświetla się różowa tapeta.](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/compositing_strategy_punch_through_to_wallpaper.png?authuser=19&hl=pl)
Warto zauważyć, że podczas korzystania z CompositingStrategy.Offscreen
,
tekstura o rozmiarze obszaru rysowania jest tworzona i renderowana z powrotem
ekranu. Wszystkie polecenia rysowania wykonywane w ramach tej strategii są domyślnie ustawione jako
przypięty do tego obszaru. Poniższy fragment kodu ilustruje różnice, gdy
przełączenie na używanie tekstur pozaekranowych:
@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())) } } }
![CompositingStrategy.Auto vs CompositingStrategy.Offscreen – klipy wyświetlane poza ekranem, w których miejsca nie są dostępne automatycznie](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/graphics_compositing_strategy.png?authuser=19&hl=pl)
ModulateAlpha
Ta strategia kompozycji moduluje współczynnik alfa każdego rysunku
instrukcji nagranych w graphicsLayer
. Nie utworzy ona
bufor poza ekranem dla wersji alfa poniżej 1,0f, chyba że jest ustawiony parametr RenderEffect
, dzięki czemu może
i efektywniejsze renderowanie w wersji alfa. Może jednak przynieść inne wyniki
w przypadku pokrywających się treści. W przypadkach, w których z wyprzedzeniem wiadomo, że treść
nie nakładają się, może to zapewnić większą skuteczność niż
Funkcja CompositingStrategy.Auto
o wartościach alfa mniejszych niż 1.
Inny przykład różnych strategii kompozycji znajdziesz poniżej – z zastosowaniem różnych
alfa do różnych części elementów kompozycyjnych oraz zastosowanie funkcji Modulate
strategia:
@Preview @Composable fun CompositingStratgey_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)
![Modulatalfa stosuje zestaw alfa do każdego polecenia rysowania](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/modulate_alpha.png?authuser=19&hl=pl)
Zapisz zawartość elementu kompozycyjnego w bitmapie
Typowym przypadkiem użycia jest utworzenie Bitmap
z funkcji kompozycyjnej. Aby skopiować wartość
zawartości funkcji kompozycyjnej w funkcji Bitmap
utwórz GraphicsLayer
za pomocą funkcji
rememberGraphicsLayer()
Przekieruj polecenia rysowania do nowej warstwy za pomocą klawiszy drawWithContent()
i
graphicsLayer.record{}
Następnie narysuj warstwę w widocznym obszarze roboczym za pomocą
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ć tę mapę bitową na dysku i ją udostępnić. Aby uzyskać więcej informacji, zapoznaj się z pełnym przykładowy fragment kodu. Zanim spróbujesz, sprawdź uprawnienia na urządzeniu aby zapisać na dysku.
Niestandardowy modyfikator rysunku
Aby utworzyć własny modyfikator niestandardowy, zaimplementuj interfejs DrawModifier
. Ten
daje Ci dostęp do elementu ContentDrawScope
, który jest taki sam jak ten, który jest widoczny
podczas korzystania z Modifier.drawWithContent()
. Następnie możesz wyodrębnić wspólny rysunek
za pomocą niestandardowych modyfikatorów rysowania, aby wyczyścić kod i zapewnić
wygodne opakowania; np. Modifier.background()
to wygodna
DrawModifier
Jeśli na przykład chcesz zaimplementować obiekt Modifier
, który obraca się w pionie
treści, możesz utworzyć jedną z nich w następujący 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 tego odwróconego modyfikatora zastosowanego w elemencie Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )
![Niestandardowy modyfikator odwrócenia tekstu](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/modifiers/modifier_flipped.png?authuser=19&hl=pl)
Dodatkowe materiały
Więcej przykładów użycia obiektu graphicsLayer
i rysunku niestandardowego znajdziesz w
następujące zasoby:
Polecane dla Ciebie
- Uwaga: tekst linku wyświetla się, gdy JavaScript jest wyłączony
- Grafika w funkcji tworzenia wiadomości
- Dostosowywanie obrazu {:#customize-image}
- Kotlin w Jetpack Compose