Oprócz funkcji kompozycyjnej Canvas
funkcja Utwórz ma kilka przydatnych elementów graficznych Modifiers
, które ułatwiają rysowanie własnych treści. Te modyfikatory są przydatne,
ponieważ można je stosować do dowolnych funkcji kompozycyjnych.
Modyfikatory rysowania
Wszystkie polecenia rysowania są wykonywane przy użyciu modyfikatora rysowania w oknie tworzenia wiadomości. W obszarze tworzenia wiadomości są 3 główne modyfikatory rysowania:
Podstawowy modyfikator rysowania to drawWithContent
. Możesz w nim określić kolejność rysowania elementu kompozycyjnego oraz poleceń rysowania w nim. drawBehind
to wygodny kod towarzyszący drawWithContent
, w którym kolejność rysowania jest ustawiona za treścią elementu kompozycyjnego. drawWithCache
wywołuje w nim onDrawBehind
lub onDrawWithContent
i udostępnia mechanizm buforowania utworzonych w nich obiektów.
Modifier.drawWithContent
: wybierz kolejność rysowania
Modifier.drawWithContent
umożliwia wykonywanie operacji DrawScope
przed treścią elementu kompozycyjnego lub po niej. Pamiętaj, by wywołać drawContent
, aby wyrenderować faktyczną zawartość funkcji kompozycyjnej. Ten modyfikator pozwala określić kolejność działań, jeśli chcesz, aby treść była rysowana przed niestandardowymi operacjami rysowania lub po nich.
Jeśli na przykład chcesz wyrenderować gradient promieniowy na treści, aby utworzyć w interfejsie efekt dziurki latarki, 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 funkcją kompozycyjną
Modifier.drawBehind
umożliwia wykonywanie operacji DrawScope
za treścią kompozycyjną narysowaną na ekranie. Podczas analizowania implementacji Canvas
zauważysz, że jest to po prostu wygodne obejście Modifier.drawBehind
.
Aby narysować zaokrąglony prostokąt za Text
:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
Daje to następujący wynik:
Modifier.drawWithCache
: rysowanie i buforowanie obiektów graficznych
Modifier.drawWithCache
przechowuje utworzone w niej obiekty w pamięci podręcznej. Obiekty są przechowywane w pamięci podręcznej, o ile rozmiar obszaru rysowania jest taki sam lub jeśli jakiekolwiek odczytane obiekty stanu nie uległy zmianie. Ten modyfikator przydaje się do poprawiania wydajności wywołań rysowania, ponieważ eliminuje potrzebę zmiany alokacji obiektów (np. Brush, Shader, Path
itp.) tworzonych przy rysowaniu.
Możesz też zapisać obiekty w pamięci podręcznej za pomocą funkcji 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, lepiej jest użyć właściwości drawWithCache
.
Jeśli na przykład utworzysz obiekt Brush
, aby narysować gradient za Text
, użycie drawWithCache
spowoduje buforowanie obiektu Brush
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 sprawia, że zawartość funkcji kompozycyjnej rysuje się w warstwie rysowania. Warstwa udostępnia kilka różnych funkcji, na przykład:
- Izolacja instrukcji rysowania (podobnie jak
RenderNode
). Instrukcje rysowania przechwycone w ramach warstwy mogą zostać skutecznie ponownie opublikowane przez potok renderowania bez ponownego wykonywania kodu aplikacji. - Przekształcenia mające zastosowanie do wszystkich instrukcji rysowania zawartych w warstwie.
- Rasteryzacja na potrzeby kompozycji. Gdy warstwa jest zrastrowana, wykonywane są jej instrukcje rysowania, a wyniki są zapisywane w buforze pozaekranowym. Komponowanie takiego bufora dla kolejnych klatek trwa krócej niż wykonanie poszczególnych instrukcji, ale po zastosowaniu przekształceń takich jak skalowanie lub obrót działa jak bitmapa.
Transformacje
Modifier.graphicsLayer
zapewnia izolację instrukcji rysowania; na przykład różne przekształcenia można stosować za pomocą funkcji Modifier.graphicsLayer
.
Mogą one być animowane lub modyfikowane bez konieczności ponownego wykonywania rysunku lambda.
Modifier.graphicsLayer
nie zmienia rozmiaru ani miejsca docelowego elementu kompozycyjnego, ponieważ ma wpływ tylko na fazę rysowania. Oznacza to, że funkcja kompozycyjna może nakładać się na inne, jeśli rysuje poza jej granicami układu.
Przy użyciu tego modyfikatora można stosować te przekształcenia:
Skala – zwiększ rozmiar
Opcje scaleX
i scaleY
służą do powiększania lub zmniejszania treści odpowiednio w kierunku poziomym i pionowym. Wartość 1.0f
oznacza brak zmian 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łumaczenia
Parametry 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
, by obracać widok w poziomie, rotationY
– obróć pionowo,
rotationZ
– aby obracać wokół osi Z (obrót standardowy). 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 } )
Miejsce wylotu
Można określić transformOrigin
. Służą one za punkt wyjścia
do przekształceń danych. We wszystkich dotychczasowych przykładach wykorzystano TransformOrigin.Center
, czyli wartość (0.5f, 0.5f)
. Jeśli określisz źródło w elemencie (0f, 0f)
, przekształcenia będą się rozpoczynać od lewego górnego rogu elementu kompozycyjnego.
Jeśli zmienisz źródło za pomocą przekształcenia rotationZ
, zobaczysz, że element jest obrócony wokół lewego górnego rogu funkcji kompozycyjnej:
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 } )
Na klips i kształt
Kształt określa kontur, do którego przycinane są treści, gdy clip = true
. W tym przykładzie ustawiliśmy 2 pudełka z 2 różnymi klipami – jedno przy użyciu zmiennej przycinania graphicsLayer
i drugie 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 „Witaj, utwórz”) jest przycięta do kształtu okręgu:
Jeśli następnie zastosujesz element translationY
do górnego różowego okręgu, zobaczysz, że progi elementu kompozycyjnego są takie same, ale okrąg rysuje się poniżej dolnego okręgu i wykracza poza jego granice.
Aby przyciąć element kompozycyjny do regionu, w którym jest narysowany, możesz dodać kolejny element Modifier.clip(RectangleShape)
na początku łańcucha modyfikatora. Treść pozostaje
w obrębie pierwotnych granic.
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
Modifier.graphicsLayer
pozwala ustawić alpha
(przezroczystość) dla całej warstwy. Element 1.0f
jest w pełni nieprzezroczysty, a 0.0f
– niewidoczny.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )
Strategia komponowania
Praca z alfa i przejrzystością może nie być tak prosta jak zmiana pojedynczej wartości alfa. Oprócz zmiany wersji alfa możesz też ustawić CompositingStrategy
dla graphicsLayer
. Element CompositingStrategy
określa sposób, w jaki zawartość funkcji kompozycyjnej jest skomponowana (skomponowana) z inną treścią narysowaną już na ekranie.
Dostępne są następujące strategie:
Automatyczna (domyślna)
Strategia komponowania jest określana przez pozostałe parametry graphicsLayer
. Jeśli wartość alfa jest mniejsza niż 1,0f lub ustawiona jest wartość RenderEffect
, warstwa renderuje się w buforze poza ekranem. Gdy wartość alfa ma wartość mniejszą niż 1f, automatycznie tworzona jest warstwa kompozycyjna do wyrenderowania zawartości, a następnie ten bufor pozaekranowy do miejsca docelowego z odpowiednią wartością alfa. Ustawienie RenderEffect
lub nadmiernie przewijanie zawsze powoduje wyrenderowanie treści do bufora poza ekranem niezależnie od ustawienia CompositingStrategy
.
Poza ekranem
Zawartość elementu kompozycyjnego jest zawsze zrastrowana do postaci tekstury lub bitmapy spoza ekranu przed renderowaniem w miejscu docelowym. Przydaje się to podczas stosowania operacji BlendMode
do maskowania treści oraz zwiększania wydajności renderowania złożonych zestawów instrukcji rysowania.
Przykładem użycia CompositingStrategy.Offscreen
jest użycie właściwości BlendModes
. Jeśli chodzi o przykład poniżej, załóżmy, że chcesz usunąć części elementu Image
kompozycyjnego, uruchamiając polecenie rysowania korzystające z BlendMode.Clear
. Jeśli compositingStrategy
nie zostanie ustawiony na CompositingStrategy.Offscreen
, BlendMode
będzie wchodzić w interakcje z całą jego zawartością.
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 ) ) } } )
Gdy ustawisz CompositingStrategy
na Offscreen
, tworzona jest tekstura poza ekranem, w której wykonywane są polecenia (stosując BlendMode
tylko do zawartości tego elementu kompozycyjnego). Następnie renderuje go nad treścią już wyrenderowaną na ekranie,
nie ma wpływu na treść, która już została narysowana.
Jeśli nie używasz właściwości CompositingStrategy.Offscreen
, użycie BlendMode.Clear
spowoduje usunięcie wszystkich pikseli w miejscu docelowym niezależnie od ustawień ustawionych wcześniej. Bufor renderowania okna pozostanie czarny. Wiele elementów BlendModes
obejmujących wersję alfa nie będzie działać zgodnie z oczekiwaniami bez bufora poza ekranem. Zwróć uwagę na czarny pierścień wokół czerwonego okręgu:
Aby to lepiej zrozumieć: gdyby aplikacja miała półprzezroczyste tło okna, a nie CompositingStrategy.Offscreen
, komponent BlendMode
wszedłby w interakcję z całą aplikacją. Spowoduje to usunięcie wszystkich pikseli, aby aplikacja lub tapeta była widoczna pod spodem, jak w tym przykładzie:
Warto zauważyć, że gdy używasz CompositingStrategy.Offscreen
, na ekranie tworzona jest tekstura pozaekranowa o wielkości obszaru rysowania. Wszystkie polecenia rysowania wykonywane w tej strategii są domyślnie przycięte do tego obszaru. W poniższym fragmencie kodu widać różnice,
jakie trzeba będzie używać 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())) } } }
ModulateAlpha
Ta strategia kompozycji moduluje wersję alfa dla każdej instrukcji rysowania zarejestrowanych w graphicsLayer
. Jeśli nie zostanie ustawiony parametr RenderEffect
, nie utworzy on bufora poza ekranem dla wersji alfa poniżej 1,0f, więc może być bardziej wydajny w przypadku renderowania alfa. Może to jednak przynieść
różne wyniki w przypadku pokrywających się treści. W przypadkach, w których wiadomo z wyprzedzeniem, że treści się nie nakładają, może to zapewnić większą skuteczność niż CompositingStrategy.Auto
z wartościami alfa mniejszymi niż 1.
Poniżej przedstawiamy kolejny przykład różnych strategii kompozycji – zastosowanie różnych wartości alfa do różnych części funkcji kompozycyjnych oraz strategię Modulate
:
@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)
Zapisywanie zawartości funkcji kompozycyjnej w bitmapie
Typowym przypadkiem użycia jest tworzenie elementu Bitmap
na podstawie funkcji kompozycyjnej. Aby skopiować zawartość funkcji kompozycyjnej do interfejsu Bitmap
, utwórz GraphicsLayer
za pomocą rememberGraphicsLayer()
.
Przekieruj polecenia rysowania na nową warstwę za pomocą funkcji 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ć bitmapę na dysku i udostępnić ją. Więcej informacji znajdziesz w pełnym przykładowym fragmencie. Zanim spróbujesz zapisać plik na dysku, sprawdź, czy masz odpowiednie uprawnienia.
Niestandardowy modyfikator rysowania
Aby utworzyć własny modyfikator niestandardowy, zaimplementuj interfejs DrawModifier
. Daje to dostęp do elementu ContentDrawScope
, który jest taki sam jak wartość widoczna podczas korzystania z metody Modifier.drawWithContent()
. Następnie możesz wyodrębnić typowe operacje rysowania do niestandardowych modyfikatorów rysowania, aby wyczyścić kod i zapewnić wygodne opakowania, np. Modifier.background()
jest wygodnym formatem DrawModifier
.
Jeśli na przykład chcesz zaimplementować obiekt Modifier
, który obraca treść w pionie, możesz go 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 na Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Dodatkowe materiały
Więcej przykładów korzystania z graphicsLayer
i rysunku niestandardowego znajdziesz w tych materiałach:
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Grafika w obszarze tworzenia wiadomości
- Dostosowywanie obrazu {:#customize-image}
- Kotlin dla Jetpack Compose