Modyfikatory grafiki

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
}

Rysunek 1.: metoda Modifier.drawWithContent używana na wierzchu kompozytowanego elementu w celu utworzenia interfejsu typu latarka.

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:

Tekst i tło utworzone za pomocą metody Modifier.drawBehind
Rysunek 2. Tekst i tło utworzone za pomocą metody Modifier.drawBehind

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())
                )
            }
        }
)

Buforowanie obiektu Brush za pomocą metody drawWithCache
Rysunek 3. Buforowanie obiektu Brush za pomocą metody pullWithCache

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
        }
)

Ilustracja 4. Skala pozioma i pozioma zastosowane do komponentu Obraz
Tłumaczenie

Wartości translationXtranslationY 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()
        }
)

Rysunek 5.: translationX i translationY zastosowane do obrazu za pomocą Modifier.graphicsLayer
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
        }
)

Rysunek 6: parametry rotationX, rotationY i rotationZ ustawione w elementach Image przez Modifier.graphicsLayer
Ź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
        }
)

Rysunek 7.: Obrót z zastosowanym TransformOrigin ustawionym na 0f, 0f

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:

Klip został zastosowany do możliwości kompozycyjnej w Box
Rysunek 8. Klip zastosowany do elementu kompozycyjnego Box

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).

Zastosowano klip z przemieszczeniem w osi Y i czerwonym obrysem
Rys. 9: Zastosowano klips z przesunięciem Y i czerwone obramowanie dla konturu

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
Rys. 10: Klip został zastosowany nad przekształceniem warstwy graficznej

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
        }
)

Obraz z zastosowaną wersją alfa
Rys. 11 Obraz z zastosowanym kodem alfa

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ć CompositingStrategygraphicsLayer. 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ą.

Modifier.drawWithContent na obrazie z oznaczeniem okręgu i użyciem funkcji BlendMode.Clear w aplikacji.
Rysunek 12. Obraz z komponentem Modifier.drawWithContent na obrazie z okręgiem, a w aplikacji BlendMode.Clear and CompositingStrategy.Offscreen

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 obiekcie Image, który pokazuje oznaczenie koła, z użyciem BlendMode.Clear i bez ustawionej strategii kompozytowania
Rysunek 13. Metoda Modifier.drawWithContent zastosowana do obrazu z określonym okręgiem, z użyciem metody BlendMode.Clear i bez ustawionej metody CompositingStrategy

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.
Rysunek 14. Nie ustawiono strategii kompozycji i użyto funkcji BlendMode.Clear w aplikacji z półprzezroczystym tłem okna. Zwróć uwagę, że różowa tapeta wyświetla się w obszarze wokół czerwonego okręgu stanu.

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()))
       }
   }
}

CompositingStrategy.Auto vs CompositingStrategy.Offscreen – klipy wyświetlane poza ekranem, w których miejsca nie są dostępne automatycznie
Rysunek 15.: Strategia komponowania.Auto a strategia tworzenia kompozycji. Poza ekranem – klipy wyświetlane poza ekranem w regionie, w którym automatyczna nie działa
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)

Modulatalfa stosuje zestaw alfa do każdego polecenia rysowania
Rysunek 16. ModulateAlpha stosuje ustawienie alfa do każdego polecenia rysowania

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()
)

Niestandardowy modyfikator odwrócenia tekstu
Rys. 17 Niestandardowy modyfikator odwrócony w tekście

Dodatkowe materiały

Więcej przykładów użycia funkcji graphicsLayer i rysowania niestandardowego znajdziesz w tych materiałach: