Modyfikatory grafiki

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
}

Rysunek 1.: Element Modifier.drawWithContent używany w interfejsie elementu Composable w celu utworzenia interfejsu w stylu latarki.

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:

Tekst i tło narysowane przy użyciu interfejsu Modifier.drawBehind
Rys. 2. Tekst i tło narysowane za pomocą metody Modifier.drawBehind

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

Zapisywanie obiektu Brush w pamięci podręcznej przy użyciu metody pullWithCache
Rys. 3. Zapisywanie obiektu Brush w pamięci podręcznej z użyciem metody pullWithCache

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

Rysunek 4. SkalaX i skala Y zastosowane do funkcji kompozycyjnej obrazu
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()
        }
)

Rysunek 5: przesunięcieX i przesunięcie Y zastosowane do obrazu z warstwą Modifier.graphicsLayer
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
        }
)

Rysunek 6. RotacjaX, obrótY i obrótZ ustawione dla obrazu przez Modifier.graphicsLayer
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
        }
)

Rysunek 7. Rotacja zastosowana z TransformOrigin ustawionym na 0f, 0f

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:

Klip został zastosowany do funkcji kompozycyjnej w usłudze Box
Rysunek 8.: Klip zastosowany do elementu kompozycyjnego w usłudze Box

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.

Zastosowano klip z przesunięciem Y i czerwonym obramowaniem do konturów
Rysunek 9. Zastosowanie klipu z przesunięciem Y i czerwonym obramowaniem do konturów

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

Klip zastosowany nad przekształceniem warstwy graficznego
Rys. 10. Klip stosowany nad przekształceniem warstwy graficznego

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

Obraz z zastosowaną wersją alfa
Rysunek 11.Obraz z zastosowaną wersją alfa

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.

Modifier.drawWithContent na obrazie z kolistym oznaczeniem z funkcją BlendMode.Clear w aplikacji
Rysunek 12. Obraz z elementem Modifier.drawWithContent z okrągłym oznaczeniem oraz elementami BlendMode.Clear i CompositingStrategy.Offscreen w aplikacji

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:

Element Modifier.drawWithContent na obrazie z oznaczeniem kołowym z ustawieniem BlendMode.Clear i brakiem CompositingStrategy
Rysunek 13. Obraz z elementem Modifier.drawWithContent z oznaczeniem kołowym i ustawieniem BlendMode.Clear i no CompositingStrategy

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:

Nie ustawiono strategii CompositingStrategy, która korzysta z funkcji BlendMode.Clear w przypadku aplikacji z przezroczystym tłem okna. Różowa tapeta wyświetla się w obszarze wokół czerwonego okręgu stanu.
Rysunek 14. Brak ustawionej strategii komponowania i użycie BlendMode.Clear w aplikacji z przezroczystym tłem okna. Zwróć uwagę, jak różowa tapeta wyświetla się w obszarze wokół czerwonego okręgu stanu.

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

CompositingStrategy.Auto vs CompositingStrategy.Offscreen – klipy spoza ekranu
Rysunek 15: CompositingStrategy.Auto vs CompositingStrategy.Offscreen – klipy spoza ekranu do regionu, w którym nie pojawia się automatyczna
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)

Funkcja ModulationAlpha stosuje zestaw alfa do każdego pojedynczego polecenia rysowania
Rys. 16. Modulacja alfa wprowadza zestaw alfa do każdego polecenia rysowania.

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

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

Dodatkowe materiały

Więcej przykładów korzystania z graphicsLayer i rysunku niestandardowego znajdziesz w tych materiałach: