Grafika w sekcji Utwórz

Wiele aplikacji musi mieć możliwość dokładnego kontrolowania tego, co jest wyświetlane na ekranie. Może to być po prostu umieszczenie ramki czy okręgu we właściwym miejscu albo skomplikowanego układu elementów graficznych w wielu różnych stylach.

Podstawowy rysunek z modyfikatorami i DrawScope

Podstawowym sposobem rysowania elementów niestandardowych w funkcji Utwórz jest modyfikatory, takie jak Modifier.drawWithContent, Modifier.drawBehind i Modifier.drawWithCache.

Jeśli na przykład chcesz narysować coś za elementem kompozycyjnym, możesz użyć modyfikatora drawBehind, aby rozpocząć wykonywanie poleceń rysowania:

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

Jeśli potrzebujesz jedynie funkcji kompozycyjnej, która rysuje, możesz użyć funkcji Canvas. Funkcja kompozycyjna Canvas to wygodny sposób opakowania Modifier.drawBehind. Element Canvas umieszczasz w układzie w taki sam sposób jak każdy inny element interfejsu Compose. W obrębie Canvas możesz rysować elementy z precyzyjną kontrolą nad ich stylem i lokalizacją.

Wszystkie modyfikatory rysowania ujawniają DrawScope – środowisko rysowania o zakresie ograniczonym, które zachowuje własny stan. Pozwala to ustawić parametry grupy elementów graficznych. Pole DrawScope zawiera kilka przydatnych pól, np. size, obiekt Size, który określa bieżące wymiary obiektu DrawScope.

Aby coś narysować, możesz skorzystać z jednej z wielu funkcji rysowania na ekranie 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 narysowany na białym tle, który zajmuje jedną czwartą ekranu
Rysunek 1. Prostokąt narysowany przy użyciu Canvas w sekcji Utwórz.

Więcej informacji o różnych modyfikatorach rysunków 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 zazwyczaj ustawiają element w punkcie [0, 0] w obszarze roboczym i udostępniają domyślny element (size), który wypełnia cały obszar rysowania, jak w przykładzie powyżej. Jak widać w powyższym przykładzie, prostokąt jest umieszczony w lewym górnym rogu. Aby móc dostosowywać rozmiar i pozycję elementu, musisz znać jego układ współrzędnych.

Punkt początkowy układu współrzędnych ([0,0]) znajduje się w lewym górnym rogu piksela w obszarze rysowania. x zwiększa się, gdy porusza się w prawo, a y w miarę przesuwania się w dół.

Siatka przedstawiająca układ współrzędnych przedstawiający lewy górny róg [0, 0] i prawy dolny róg [width, height]
Rysunek 2. Rysuj układ współrzędnych / siatkę 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, podając odpowiednie pozycje 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
    )
}

Podstawowe przekształcenia

DrawScope udostępnia przekształcenia pozwalające zmienić miejsce i sposób wykonywania poleceń rysowania.

Skala

Użyj narzędzia DrawScope.scale(), aby według współczynnika zwiększyć rozmiar operacji rysowania. Operacje takie jak scale() mają zastosowanie do wszystkich operacji rysowania w odpowiednim obiekcie lambda. Na przykład ten kod zwiększa 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 o nierównomiernym skali
Rysunek 3. Zastosowanie operacji skali do okręgu w Canvas.

Tłumacz

Użyj DrawScope.translate(), aby przenosić operacje rysowania w górę, w dół, w lewo lub w prawo. Na przykład ten kod przenosi 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 kręgu w Canvas.

Obróć

Użyj narzędzia DrawScope.rotate(), aby obracać operacje rysowania wokół punktu obrotu. 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 o 45 stopni na środku ekranu
Rysunek 5. Używamy rotate(), by zastosować obrócenie do bieżącego zakresu rysowania, co spowoduje obrócenie prostokąta o 45 stopni.

Odcięcie

Użyj DrawScope.inset(), aby dostosować domyślne parametry bieżącego obiektu DrawScope, zmieniając granice rysowania i zmieniając jego położenie na rysunkach:

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

Ten kod skutecznie dodaje dopełnienie do poleceń rysowania:

prostokąt, który został obłożony dookoła,
Rysunek 6. Stosuję wstawienie do poleceń rysowania.

Wiele przekształceń

Aby zastosować do rysunków wiele przekształceń, użyj funkcji DrawScope.withTransform(), która utworzy i zastosuje jedno przekształcenie łączące wszystkie niezbędne zmiany. Korzystanie z funkcji withTransform() jest wydajniejsze niż generowanie zagnieżdżonych wywołań poszczególnych przekształceń, ponieważ wszystkie te przekształcenia są wykonywane razem w ramach jednej operacji, a nie w ramach tworzenia trzeba obliczać i zapisywać każde z zagnieżdżonych przekształceń.

Na przykład ten kod stosuje do prostokąta zarówno translację, 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 na bok ekranu
Rysunek 7. Użyj funkcji withTransform, aby zastosować zarówno obrót, jak i przesunięcie, obracając prostokąt i przesuwając go w lewo.

Typowe operacje rysowania

Narysuj tekst

Aby rysować tekst w sekcji Utwórz, zwykle możesz używać funkcji kompozycyjnej Text. Jeśli jednak używasz DrawScope lub chcesz narysować tekst ręcznie, korzystając z dostosowywania, możesz użyć metody DrawScope.drawText().

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

val textMeasurer = rememberTextMeasurer()

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

Wyświetlam obraz Hello narysowany w Canvas
Rysunek 8. rysowanie tekstu w Canvas.

Mierzenie tekstu

Rysowanie tekstu działa trochę inaczej niż inne polecenia rysowania. Normalnie, przekazujesz poleceniem rysunkowym rozmiar (szerokość i wysokość), by narysować kształt lub obraz. W przypadku tekstu jest kilka parametrów, które kontrolują rozmiar renderowanego tekstu, np. rozmiar czcionki, czcionka, ligatury i odstępy między literami.

Za pomocą funkcji TextMeasurer możesz 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ć informacji zmierzonych, aby określić rozmiar obszaru, na jaki zajmuje tekst:

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 tworzy różowe tło tekstu:

tekst wielowierszowy zajmujący 2⁄3 powierzchni całego obszaru, z prostokątnym tłem;
Rysunek 9. wielowierszowy tekst zajmujący 2⁄3 całego obszaru z prostokątem tła.

Jeśli zmienisz ograniczenia, rozmiar czcionki lub dowolną właściwość, która ma wpływ na mierzony rozmiar, spowoduje to utworzenie nowego rozmiaru w raporcie. Możesz ustawić stały rozmiar dla width i height. Tekst będzie się wyświetlać zgodnie z tym ustawieniem TextOverflow. Na przykład ten kod renderuje tekst na 1⁄3 wysokości i 1⁄3 szerokości obszaru kompozycyjnego, a TextOverflow na 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 narysowany w ramach ograniczeń i znajduje się na końcu wielokropka:

Tekst na różowym tle z uciętym wielokropkiem.
Rysunek 10. TextOverflow.Ellipsis ze stałymi ograniczeniami pomiaru tekstu.

Narysuj obraz

Aby narysować element ImageBitmap z użyciem DrawScope, wczytaj obraz za pomocą metody 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. Rysujemy ImageBitmap w Canvas.

Narysuj podstawowe kształty

Aplikacja DrawScope zawiera 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()

koło do rysowania

drawRect()

rysuj prostokąt

drawRoundedRect()

narysuj zaokrąglony prostokąt

drawLine()

narysuj linię

drawOval()

narysuj owal

drawArc()

narysuj łuk

drawPoints()

rysuj punkty

Narysuj ścieżkę

Ścieżka to seria instrukcji matematycznych, których efektem jest jeden rysunek. DrawScope może narysować ścieżkę, używając metody DrawScope.drawPath().

Załóżmy, że chcesz narysować trójkąt. Ścieżkę można wygenerować za pomocą funkcji takich jak lineTo() i moveTo(), korzystając z rozmiaru obszaru rysowania. Następnie wywołaj drawPath() tą nowo utworzoną ścieżką, 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()
)

Trójkąt skierowany do góry nogami w kolorze fioletowej ścieżki
Rysunek 12. Tworzę i rysuję obiekt Path w sekcji Utwórz.

Uzyskuję dostęp do Canvas obiektu

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

Jeśli na przykład masz niestandardowy obiekt Drawable, który chcesz narysować na kanwie, możesz otworzyć obszar roboczy i wywołać 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()
)

Czarny, owalny obraz shapeDrawable zajmujący pełny rozmiar
Rysunek 13. Otwieram obszar roboczy, aby narysować Drawable.

Więcej informacji

Więcej informacji o rysowaniu w widoku tworzenia wiadomości znajdziesz w tych materiałach: