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 dowolnego komponentu.
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 wykonywanie operacji DrawScope
przed treścią kompozytową lub po niej. 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 wyświetlanymi na ekranie. Jeśli przyjrzysz się implementacji funkcji Canvas
, zauważysz, że jest to tylko wygodny element opakowujący funkcję 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:
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 np. utworzysz element Brush
, aby narysować gradient za elementem Text
, element drawWithCache
będzie przechowywać w pamięci obiekt Brush
, dopóki nie zmieni się rozmiar obszaru rysunku:
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
: stosuj przekształcenia do elementów kompozycyjnych
Modifier.graphicsLayer
to modyfikator, który przekształca zawartość kompozytowanego obiektu rysowania w warstwę rysowania. Warstwy zapewniają kilka różnych 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ę.
- Rastryzowanie w ramach możliwości kompozytowych. Gdy warstwa jest rastrowana, instrukcje rysowania są wykonywane, a wyjście jest przechwytywane w buforze poza ekranem. 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
Wartości translationX
i translationY
można zmieniać za pomocą graphicsLayer
,
translationX
przesuwa kompozycję 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 } )
Przycinanie 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:
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).
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)) ) }
Alfa
Za pomocą atrybutu Modifier.graphicsLayer
można ustawić alpha
(przezroczystość) dla całego poziomu. 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 } )
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, jak treści komponenta są łączone (zestawiane) z innymi treściami wyświetlanymi na ekranie.
Dostępne strategie to:
Automatycznie (domyślnie)
Strategia komponowania jest określana przez pozostałe pola graphicsLayer
. Warstwę renderuje do bufora poza ekranem, jeśli alfa jest mniejsza niż 1.0f lub jeśli ustawiona jest wartość 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. Jest to przydatne do maskowania zawartości za pomocą operacji BlendMode
oraz do zwiększenia wydajności podczas renderowania złożonych zbiorów instrukcji rysowania.
Przykład użycia elementu CompositingStrategy.Offscreen
to element 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ą.
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:
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:
Warto pamiętać, że podczas używania CompositingStrategy.Offscreen
tworzona jest tekstura poza ekranem o rozmiarze obszaru rysunku, która jest renderowana z powrotem na ekranie. Wszystkie polecenia rysowania wykonywane w ramach tej strategii są domyślnie ustawione jako
przypięty do tego obszaru. Poniższy fragment kodu pokazuje różnice, jakie występują po przejściu na korzystanie z tekstur 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 tworzenia 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)
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 zastosować Modifier
, które odwraca zawartość w poziomie, możesz je utworzyć 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 tego odwróconego modyfikatora zastosowanego w elemencie Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Dodatkowe materiały
Więcej przykładów użycia funkcji graphicsLayer
i rysowania niestandardowego znajdziesz w tych materiałach:
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy obsługa JavaScript jest wyłączona
- Grafika w funkcji tworzenia wiadomości
- Dostosowywanie obrazu {:#customize-image}
- Kotlin w Jetpack Compose