Modyfikatory grafiki

Oprócz funkcji Canvas kompozycyjnej aplikacja Compose zawiera kilka przydatnych elementów graficznychModifiers, które ułatwiają rysowanie niestandardowych treści. Te modyfikatory są przydatne, bo można je stosować w dowolnym elemencie kompozycyjnym.

Modyfikatory rysowania

Wszystkie polecenia rysowania są wykonywane przy użyciu modyfikatora rysunku w narzędziu Compose. W Compose są dostępne 3 główne modyfikatory rysunku:

Podstawowym modyfikatorem rysowania jest drawWithContent. Umożliwia on określenie kolejności rysowania w obiekcie kompozycyjnym oraz poleceń rysowania wysyłanych w obrębie tego modyfikatora. drawBehind to wygodny otoczenie wokół drawWithContent, w którym kolejność rysowania jest ustawiona na za zawartość funkcji kompozycyjnej. Funkcja drawWithCache wywołuje w nim onDrawBehind lub onDrawWithContent i zapewnia mechanizm buforowania utworzonych w nim obiektów.

Modifier.drawWithContent: wybierz kolejność rysowania

Modifier.drawWithContent umożliwia wykonywanie operacji DrawScope przed treścią funkcji kompozycyjnej lub po niej. Pamiętaj, by wywołać drawContent, by potem wyrenderować rzeczywistą zawartość elementu kompozycyjnego. Dzięki temu modyfikatorowi możesz określić kolejność działań w zależności od tego, czy treść ma być 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 od klucza w latarce, wykonaj 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
}

Rys. 1. Metoda Modifier.drawWithContent używana razem z komponentem w celu utworzenia interfejsu użytkownika w stylu latarki.

Modifier.drawBehind: zarysowanie elementu kompozycyjnego

Modifier.drawBehind umożliwia wykonywanie operacji DrawScope w odniesieniu do treści kompozycyjnej, która jest rysowana na ekranie. Gdy przyjrzysz się implementacji Canvas, możesz zauważyć, że jest to po prostu wygodna otoka wokół 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 narysowane przy użyciu Modifier.drawBehind

Modifier.drawWithCache: rysowanie i zapisywanie obiektów rysunkowych w pamięci podręcznej

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 żadne odczytywane obiekty stanu nie uległy zmianie. Ten modyfikator jest przydatny przy zwiększaniu wydajności wywołań rysowania, ponieważ pozwala uniknąć ponownego przydzielania obiektów (takich jak Brush, Shader, Path itp.), które są tworzone podczas rysowania.

Można też buforować obiekty za pomocą remember (bez użycia modyfikatora). Jednak nie zawsze jest to 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 gradient za obiektem Text, jeśli utworzysz obiekt Brush, użycie drawWithCache zapisze obiekt Brush w pamięci podręcznej, dopóki rozmiar obszaru rysowania się nie zmieni:

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
Rysunek 3. Zapisywanie obiektu Brush w pamięci podręcznej za pomocą polecenia DodWithCache

Modyfikatory grafiki

Modifier.graphicsLayer: stosuj przekształcenia do elementów kompozycyjnych

Modifier.graphicsLayer to modyfikator, który sprawia, że zawartość elementu kompozycyjnego rysuje się w warstwie rysowania. Warstwa oferuje kilka różnych funkcji, takich jak:

  • Izolacja instrukcji rysowania (podobna do metody RenderNode). Instrukcje rysowania zarejestrowane jako część warstwy mogą być ponownie wydawane przez potok renderowania bez ponownego uruchamiania kodu aplikacji.
  • Przekształcenia, które mają zastosowanie do wszystkich instrukcji rysowania zawartych w warstwie.
  • Rasteryzacja w celu uzyskania możliwości kompozycji. Przy zrasteryzowaniu warstwy są wykonywane jej instrukcje rysowania, a dane wyjściowe są zapisywane w buforze poza ekranem. Komponowanie takiego bufora na potrzeby kolejnych klatek jest szybsze niż wykonywanie poszczególnych instrukcji, ale po zastosowaniu przekształceń takich jak skalowanie czy obrót zachowuje się jak bitmapa.

Transformacje

Instrukcje rysowania umożliwiają korzystanie z funkcji Modifier.graphicsLayer. Za pomocą funkcji Modifier.graphicsLayer można na przykład stosować różne przekształcenia. Można je animowane i modyfikować bez konieczności ponownego uruchamiania rysunku lambda.

Modifier.graphicsLayer nie zmienia zmierzonego rozmiaru ani położenia elementu kompozycyjnego, ponieważ wpływa 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 zmniejszają lub zmniejszają treść 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
        }
)

Rysunek 4. skala X i skala Y zastosowane do elementu kompozycyjnego obrazu
Tłumaczenie

Elementy translationX i translationY można zmienić za pomocą przycisku graphicsLayer, a funkcja 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()
        }
)

Rys. 5: TranslacjaX i przesunięcieY zastosowane do obrazu z zastosowaniem Modifier.graphicsLayer
Obrót

Ustaw rotationX, by obracać elementy w poziomie, rotationY, żeby obracać je w pionie, a rotationZ, żeby obracać je w okolicy Z (obrót standardowy). Wartość ta 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
        }
)

Rys. 6: rotacja X, obrótY i obrót Z ustawiane dla elementu Image by Modifier.graphicsLayer
Miejsce wylotu

Można określić transformOrigin. Jest on następnie używany jako punkt, od którego zachodzą przekształcenia. Do tej pory we wszystkich przykładach używana jest wartość TransformOrigin.Center, która ma wartość (0.5f, 0.5f). Jeśli określisz punkt początkowy (0f, 0f), przekształcenia będą się zaczynać od lewego górnego rogu elementu kompozycyjnego.

Jeśli zmienisz źródło za pomocą przekształcenia rotationZ, zobaczysz, że element obraca się w lewym górnym 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
        }
)

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

Klip i kształt

Kształt określa kontur, do którego będą przechodzić treści, gdy clip = true. W tym przykładzie ustawiliśmy 2 pola z 2 różnymi klipami – jedno ze zmienną klipu graphicsLayer, a drugie z wygodnym opakowaniem 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”) zostanie skrócona do kształtu koła:

Klip został zastosowany do możliwości kompozycyjnej w Box
Rysunek 8. Klip zastosowany do obiektu Box z możliwością kompozycyjnej

Jeśli potem zastosujesz wymiar translationY do górnego różowego okręgu, zobaczysz, że granice funkcji kompozycyjnej są takie same, ale okrąg rysuje się pod dolnym okręgiem (i poza jego granicami).

Klip został zastosowany z przesunięciem Y i czerwone obramowanie dla konturu
Rys. 9. Klip został zastosowany z przesunięciem Y i czerwone obramowanie dla konspektu

Aby przyciąć kompozycję do regionu, który jest utworzony, możesz dodać kolejny Modifier.clip(RectangleShape) na początku łańcucha modyfikatorów. Treść pozostaje wtedy w oryginalnych 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
Rysunek 10. Przypięty klip nad przekształceniem warstwy graficznej

Alfa

Za pomocą Modifier.graphicsLayer możesz ustawić alpha (przezroczystość) dla całej warstwy. 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 zastosowaną wersją alfa

Strategia komponowania

Korzystanie z wersji alfa i przejrzystości może być trudniejsze niż zmiana pojedynczej wartości alfa. Oprócz zmiany wersji alfa możesz też ustawić CompositingStrategy na graphicsLayer. CompositingStrategy określa sposób, w jaki zawartość elementu kompozycyjnego jest komponowana (połączona) z innymi treściami widocznymi już na ekranie.

Dostępne strategie to:

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, renderuje warstwę w buforze poza ekranem. Gdy wartość alfa jest mniejsza niż 1f, automatycznie tworzona jest warstwa komponująca w celu wyrenderowania treści, a następnie rysowanie tego bufora pozaekranowego do miejsca docelowego z odpowiednią wersją alfa. Ustawienie RenderEffect lub nadmierne przewinięcie zawsze powoduje renderowanie treści w buforze poza ekranem niezależnie od ustawienia CompositingStrategy.

Poza ekranem

Przed wyrenderowaniem treści w miejscu docelowym zawartość funkcji kompozycyjnej jest zawsze zrastrowana na teksturę pozaekranową lub bitmapę. Jest to przydatne przy stosowaniu operacji BlendMode do maskowania treści oraz podczas renderowania złożonych zestawów instrukcji rysowania.

Przykład użycia CompositingStrategy.Offscreen z BlendModes. Biorąc pod uwagę poniższy przykład, załóżmy, że chcesz usunąć fragmenty funkcji kompozycyjnej Image, wykonując polecenie rysowania z użyciem BlendMode.Clear. Jeśli nie ustawisz compositingStrategy na CompositingStrategy.Offscreen, BlendMode będzie współdziałać ze całą zawartością znajdującą się 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
                   )
               )
           }

       }
)

Ustawienie CompositingStrategy na Offscreen powoduje utworzenie tekstury poza ekranem, dla której są wykonywane polecenia (stosując BlendMode tylko do zawartości tego elementu kompozycyjnego). Następnie renderuje go nad tym, co jest już wyrenderowane na ekranie, i nie ma wpływu na te treści.

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 oznaczonym okręgiem, z funkcją BlendMode.Clear and CompositingStrategy.Offscreen w aplikacji

Jeśli nie używasz CompositingStrategy.Offscreen, użycie BlendMode.Clear spowoduje usunięcie wszystkich pikseli w miejscu docelowym niezależnie od tego, co było już ustawione – bufor renderowania okna (czarny) będzie widoczny. Wiele elementów BlendModes, które obejmują wersję alfa, nie będzie działać zgodnie z oczekiwaniami bez bufora 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
Rysunek 13. Obraz z zaznaczoną ramką przedstawiającą okrąg, z obiektem BlendMode.Clear i brak ustawionej strategii kompozycji

Aby to wyjaśnić: gdyby aplikacja miała przezroczyste tło okna i nie użyto CompositingStrategy.Offscreen, BlendMode działałby z całą aplikacją. Zostaną usunięte wszystkie piksele i pojawią się pod spodem aplikacja lub tapeta, 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 kompilacji, a użytkownik korzysta z 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 zauważyć, że przy korzystaniu z funkcji CompositingStrategy.Offscreen tekstura pozaekranowa o rozmiarze obszaru rysowania jest tworzona i renderowana z powrotem na ekranie. Wszystkie polecenia rysowania wykonywane w ramach tej strategii są domyślnie przypięte do tego regionu. Fragment kodu poniżej pokazuje różnice w zastosowaniu 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 automatyczne nie są obsługiwane
ModulateAlpha

Ta strategia kompozycji moduluje alfa dla każdego z instrukcji rysowania zarejestrowanych w graphicsLayer. Nie utworzy bufora poza ekranem dla wersji alfa poniżej 1,0f, jeśli nie jest ustawiony atrybut RenderEffect, dzięki czemu może być bardziej wydajny podczas renderowania w wersji alfa. Efekty nakładania się treści mogą jednak być inne. W przypadkach, w których z góry wiadomo, że treści się nie nakładają, może to zapewnić większą skuteczność niż CompositingStrategy.Auto, przy których wartości alfa są mniejsze niż 1.

Poniżej znajdziesz kolejny przykład różnych strategii kompozycji – zastosowanie różnych wersji alfa do różnych części elementów kompozycyjnych oraz zastosowanie strategii 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)

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

Zapisz zawartość elementu kompozycyjnego w bitmapie

Typowym przypadkiem użycia jest utworzenie Bitmap z funkcji kompozycyjnej. Aby skopiować zawartość kompozytu do funkcji Bitmap, utwórz GraphicsLayer za pomocą parametru rememberGraphicsLayer().

Przekieruj polecenia rysowania do nowej warstwy za pomocą 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 dowiedzieć się więcej, zobacz pełny przykładowy fragment kodu. Przed zapisaniem na dysku sprawdź uprawnienia urządzenia.

Niestandardowy modyfikator rysunku

Aby utworzyć własny modyfikator niestandardowy, zaimplementuj interfejs DrawModifier. Daje Ci to dostęp do obiektu ContentDrawScope, który jest taki sam jak ten, który jest widoczny podczas korzystania z funkcji Modifier.drawWithContent(). Następnie można wyodrębnić typowe operacje rysowania do niestandardowych modyfikatorów rysowania, aby wyczyścić kod i zapewnić wygodne otoki, np. Modifier.background() to wygodna DrawModifier.

Jeśli na przykład chcesz zaimplementować obiekt Modifier, który odwraca treści w pionie, możesz utworzyć taki dokument:

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
Rysunek 17. Niestandardowy odwrócony modyfikator tekstu

Dodatkowe materiały

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