Grafika w sekcji Utwórz

Wiele aplikacji musi mieć możliwość dokładnego kontrolowania tego, co ma się wyświetlać na ekranie. Może to być np. umieszczenie pola lub okręgu w odpowiednim miejscu na ekranie lub rozbudowane ułożenie elementów graficznych w wielu różnych stylach.

Podstawowy rysunek z modyfikatorami i DrawScope

Podstawowym sposobem rysowania niestandardowych elementów w funkcji tworzenia wiadomości jest użycie modyfikatorów takich jak Modifier.drawWithContent, Modifier.drawBehind i Modifier.drawWithCache.

Aby na przykład narysować coś w tyle funkcji kompozycyjnej, możesz użyć modyfikatora drawBehind, aby zacząć wykonywać polecenia rysowania:

Spacer(
    modifier = Modifier
        .fillMaxSize()
        .drawBehind {
            // this = DrawScope
        }
)

Jeśli potrzebujesz tylko narzędzia kompozycyjnego do rysowania, możesz użyć funkcji kompozycyjnej Canvas. Element kompozycyjny Canvas to wygodny kod wokół Modifier.drawBehind. Element Canvas umieszczasz w układzie w taki sam sposób jak każdy inny element interfejsu tworzenia wiadomości. W Canvas możesz rysować elementy, kontrolując ich styl i lokalizację.

Wszystkie modyfikatory rysowania udostępniają DrawScope – środowisko rysowania o zakresie, które zachowuje własny stan. Dzięki temu możesz ustawić parametry grupy elementów graficznych. DrawScope zawiera kilka przydatnych pól, np. size, obiekt Size określający bieżące wymiary obiektu DrawScope.

Aby coś narysować, możesz użyć jednej z wielu funkcji rysowania w przeglądarce DrawScope. Na przykład ten kod rysuje prostokąt w lewym górnym rogu ekranu:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    drawRect(
        color = Color.Magenta,
        size = canvasQuadrantSize
    )
}

Różowy prostokąt na białym tle, który zajmuje jedną czwartą ekranu
Rysunek 1 Prostokąt narysowany w Canvas w funkcji tworzenia wiadomości.

Więcej informacji o różnych modyfikatorach rysunku znajdziesz w dokumentacji modyfikatorów graficznych.

Układ współrzędnych

Aby narysować coś na ekranie, musisz znać przesunięcie (x i y) oraz rozmiar elementu. W przypadku wielu metod rysowania w DrawScope położenie i rozmiar są określane przez domyślne wartości parametrów. Parametry domyślne zwykle umieszczają element w punkcie [0, 0] w obszarze roboczym i określają wartość domyślną (size), która wypełnia cały obszar rysowania, jak w przykładzie powyżej – prostokąt znajduje się w lewym górnym rogu. Aby dostosować rozmiar i położenie elementu, musisz poznać układ współrzędnych w funkcji Utwórz.

Początek układu współrzędnych ([0,0]) znajduje się w lewym górnym rogu obszaru rysowania. Wskaźnik x rośnie, gdy przesuwa się w prawo, a y rośnie, gdy przesuwa się w dół.

Siatka z układem współrzędnych z lewym górnym [0, 0] i prawym dolnym [szerokość, wysokość]
Rysunek 2. Rysowanie układu współrzędnych lub siatki rysowania.

Jeśli na przykład chcesz narysować ukośną linię od prawego górnego rogu obszaru roboczego do lewego dolnego rogu, możesz użyć funkcji DrawScope.drawLine() i określić przesunięcie początku i końca z odpowiednimi pozycjami x i y:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )
}

Przekształcenia podstawowe

DrawScope umożliwia przekształcenie, które pozwala zmienić miejsce i sposób wykonywania poleceń rysowania.

Skala

Użyj DrawScope.scale(), aby zwiększyć rozmiar operacji rysowania o dany współczynnik. Operacje takie jak scale() mają zastosowanie do wszystkich operacji rysowania w ramach danej funkcji lambda. Na przykład ten kod zwiększa wartość scaleX 10 razy i scaleY 15 razy:

Canvas(modifier = Modifier.fillMaxSize()) {
    scale(scaleX = 10f, scaleY = 15f) {
        drawCircle(Color.Blue, radius = 20.dp.toPx())
    }
}

Okrąg skalowany nierównomiernie
Rysunek 3. Zastosowanie operacji skalowania do okręgu w Canvas.

Tłumacz

Używaj DrawScope.translate(), aby przenosić operacje rysowania w górę, w dół, w lewo i w prawo. Na przykład ten kod przesuwa rysunek o 100 pikseli w prawo i o 300 pikseli w górę:

Canvas(modifier = Modifier.fillMaxSize()) {
    translate(left = 100f, top = -300f) {
        drawCircle(Color.Blue, radius = 200.dp.toPx())
    }
}

Okrąg, który przesunął się poza środek
Rysunek 4. Stosuję operację tłumaczenia do okręgu w Canvas.

Obróć

Aby obracać operacje rysowania wokół punktu przestawnego, użyj DrawScope.rotate(). Na przykład ten kod obraca prostokąt o 45 stopni:

Canvas(modifier = Modifier.fillMaxSize()) {
    rotate(degrees = 45F) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

Telefon z prostokątem obróconym na środku ekranu o 45 stopni
Rysunek 5. Użyjemy funkcji rotate(), by zastosować obrót do bieżącego zakresu rysowania, który powoduje obrót prostokąta o 45 stopni.

Odcięcie

Użyj DrawScope.inset(), aby dostosować domyślne parametry bieżącego DrawScope, zmieniając granice rysowania i tłumacząc rysunki:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    inset(horizontal = 50f, vertical = 30f) {
        drawRect(color = Color.Green, size = canvasQuadrantSize)
    }
}

Ten kod dodaje dopełnienie do poleceń rysowania:

Prostokąt wyściełany dookoła
Rysunek 6. Stosuję wstawki do poleceń rysowania.

Wiele przekształceń

Aby zastosować do rysunków wiele przekształceń, użyj funkcji DrawScope.withTransform(), która tworzy i stosuje jedno przekształcenie łączące wszystkie pożądane zmiany. Korzystanie z klasy withTransform() jest bardziej wydajne niż używanie zagnieżdżonych wywołań poszczególnych przekształceń, ponieważ wszystkie przekształcenia są wykonywane razem w ramach jednej operacji, zamiast tworzyć i zapisywać każdą z zagnieżdżonych przekształceń.

Na przykład ten kod stosuje do prostokąta zarówno przesunięcie, jak i obrót:

Canvas(modifier = Modifier.fillMaxSize()) {
    withTransform({
        translate(left = size.width / 5F)
        rotate(degrees = 45F)
    }) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

Telefon z obróconym prostokątem przesuniętym w bok ekranu
Rysunek 7. Użyj funkcji withTransform, aby zastosować obrót i przesunięcie, obracając prostokąt i przesuwając go w lewo.

Typowe operacje rysowania

Narysuj tekst

Do rysowania tekstu w funkcji tworzenia wiadomości zwykle możesz użyć funkcji kompozycyjnej Text. Jeśli jednak używasz DrawScope lub chcesz narysować tekst ręcznie za pomocą dostosowywania, możesz użyć metody DrawScope.drawText().

Aby narysować tekst, utwórz TextMeasurer przy użyciu rememberTextMeasurer i wywołaj drawText za pomocą miernika:

val textMeasurer = rememberTextMeasurer()

Canvas(modifier = Modifier.fillMaxSize()) {
    drawText(textMeasurer, "Hello")
}

Wyświetla powitanie narysowane w Canvas
Rysunek 8. Narysowany tekst w Canvas.

Zmierz tekst

Rysowanie tekstu działa trochę inaczej niż pozostałe polecenia do rysowania. Normalnie, aby narysować kształt lub obraz, wykonuje się polecenie rysowania zawierające rozmiar (szerokość i wysokość). W przypadku tekstu rozmiar wyświetlanego tekstu zależy od kilku parametrów, takich jak rozmiar czcionki, czcionka, ligatury i odstępy między literami.

W przypadku tworzenia wiadomości możesz użyć TextMeasurer, aby uzyskać dostęp do zmierzonego rozmiaru tekstu (w zależności od powyższych czynników). Jeśli chcesz narysować tło za tekstem, możesz użyć zmierzonych informacji, aby uzyskać rozmiar obszaru, który zajmuje:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()),
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

Ten fragment kodu powoduje wyświetlanie różowego tła w tekście:

Tekst wielowierszowy zajmujący 2⁄3 całego obszaru z prostokątem tła
Rysunek 9. Tekst wielowierszowy, który zajmuje 2⁄3 pełnego obszaru, z prostokątem tła.

Dostosowanie ograniczeń, rozmiaru czcionki lub innych właściwości, które mają wpływ na zmierzony rozmiar, powoduje wygenerowanie nowego raportu o danym rozmiarze. Możesz ustawić stały rozmiar zarówno w polu width, jak i height, aby tekst był zgodny z ustawionym TextOverflow. Na przykład ten kod renderuje tekst w 1⁄3 wysokości i 1⁄3 szerokości obszaru kompozycyjnego, a w polu TextOverflow ustawia się TextOverflow.Ellipsis:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixed(
                        width = (size.width / 3f).toInt(),
                        height = (size.height / 3f).toInt()
                    ),
                    overflow = TextOverflow.Ellipsis,
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

Tekst jest teraz rysowany w ograniczeniach z wielokropkiem na końcu:

Tekst narysowany na różowym tle z wycinaniem wielokropka.
Rysunek 10 TextOverflow.Ellipsis ze stałymi ograniczeniami dotyczącymi pomiaru tekstu.

Narysuj obraz

Aby narysować wzór ImageBitmap za pomocą DrawScope, wczytaj obraz za pomocą polecenia ImageBitmap.imageResource(), a następnie wywołaj drawImage:

val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)

Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    drawImage(dogImage)
})

Obraz psa narysowany w Canvas
Rysunek 11 Rysunek ImageBitmap w Canvas.

Narysuj kształty podstawowe

W DrawScope jest wiele funkcji rysowania kształtów. Aby narysować kształt, użyj jednej ze wstępnie zdefiniowanych funkcji rysowania, np. drawCircle:

val purpleColor = Color(0xFFBA68C8)
Canvas(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp),
    onDraw = {
        drawCircle(purpleColor)
    }
)

Interfejs API

Odpowiedź

drawCircle()

narysuj okrąg

drawRect()

prostokąt do rysowania

drawRoundedRect()

narysuj zaokrąglony prostokąt

drawLine()

narysuj linię

drawOval()

narysuj owal

drawArc()

rysuj łuk

drawPoints()

remisuj punkty

Narysuj ścieżkę

Ścieżka to seria instrukcji matematycznych, których wynikiem jest rysunek. DrawScope może narysować ścieżkę przy użyciu metody DrawScope.drawPath().

Załóżmy, że chcesz narysować trójkąt. Możesz wygenerować ścieżkę za pomocą funkcji takich jak lineTo() i moveTo(), korzystając z rozmiaru obszaru rysowania. Następnie wywołaj funkcję drawPath(), używając tej nowo utworzonej ścieżki, aby uzyskać trójkąt.

Spacer(
    modifier = Modifier
        .drawWithCache {
            val path = Path()
            path.moveTo(0f, 0f)
            path.lineTo(size.width / 2f, size.height / 2f)
            path.lineTo(size.width, 0f)
            path.close()
            onDrawBehind {
                drawPath(path, Color.Magenta, style = Stroke(width = 10f))
            }
        }
        .fillMaxSize()
)

Odwrócony fioletowy trójkąt ścieżki narysowany w funkcji Utwórz
Rysunek 12 Tworzę i rysuję Path w Compose.

Uzyskuję dostęp do Canvas obiektu

DrawScope nie zapewnia bezpośredniego dostępu do obiektu Canvas. Możesz użyć polecenia DrawScope.drawIntoCanvas(), aby uzyskać dostęp do samego obiektu Canvas, w którym można wywoływać funkcje.

Jeśli na przykład masz niestandardowy obiekt Drawable, który chcesz narysować na obszarze roboczym, możesz otworzyć obszar roboczy i wywołać funkcję Drawable#draw(), przekazując obiekt Canvas:

val drawable = ShapeDrawable(OvalShape())
Spacer(
    modifier = Modifier
        .drawWithContent {
            drawIntoCanvas { canvas ->
                drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt())
                drawable.draw(canvas.nativeCanvas)
            }
        }
        .fillMaxSize()
)

Owalny, czarny element shapeDrawable w pełnym rozmiarze
Rysunek 13 Uzyskuję dostęp do obszaru roboczego, aby narysować Drawable.

Więcej informacji

Więcej informacji o rysowaniu w Compose znajdziesz w tych materiałach: