Grafika w sekcji Utwórz

Wiele aplikacji musi mieć możliwość precyzyjnego kontrolowania tego, co jest wyświetlane na ekranie. Może to być tak proste, jak umieszczenie na ekranie w odpowiednim miejscu pola lub okręgu, albo tak złożone, jak skomplikowany układ elementów graficznych w wielu różnych stylach.

Podstawowy rysunek z modyfikatorami i symbolem DrawScope

Podstawowym sposobem rysowania niestandardowych elementów w Compose są modyfikatory, takie jak Modifier.drawWithContent, Modifier.drawBehindModifier.drawWithCache.

Aby na przykład 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 tylko komponentu kompozycyjnego, który rysuje, możesz użyć komponentu Canvas. Funkcja kompozycyjna Canvas to wygodna otoczka funkcji Modifier.drawBehind. Element Canvas umieszcza się w układzie tak samo jak każdy inny element interfejsu Compose. W sekcji Canvas możesz rysować elementy, precyzyjnie kontrolując ich styl i położenie.

Wszystkie modyfikatory rysowania udostępniają DrawScope, czyli środowisko rysowania o określonym zakresie, które zachowuje własny stan. Umożliwia to ustawienie parametrów grupy elementów graficznych. DrawScope udostępnia kilka przydatnych pól, np. size, obiekt Size określający bieżące wymiary DrawScope.

Aby coś narysować, możesz użyć jednej z wielu funkcji rysowania na DrawScope. Na przykład poniższy 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, zajmujący jedną czwartą ekranu
Rysunek 1. Prostokąt narysowany za pomocą Canvas w Compose.

Więcej informacji o różnych modyfikatorach rysowania znajdziesz w dokumentacji modyfikatorów grafiki.

Układ współrzędnych

Aby narysować coś na ekranie, musisz znać przesunięcie (xy) oraz rozmiar elementu. W przypadku wielu metod rysowania w DrawScope pozycja i rozmiar są podawane przez domyślne wartości parametrów. Domyślne parametry zwykle umieszczają element w punkcie [0, 0] na obszarze roboczym i zapewniają domyślny size, który wypełnia cały obszar rysowania, jak w przykładzie powyżej – widać, że prostokąt znajduje się w lewym górnym rogu. Aby dostosować rozmiar i położenie elementu, musisz poznać system współrzędnych w Compose.

Punkt początkowy układu współrzędnych ([0,0]) znajduje się w lewym górnym rogu obszaru rysowania. x rośnie w miarę przesuwania się w prawo, a y rośnie w miarę przesuwania się w dół.

Siatka przedstawiająca układ współrzędnych z lewym górnym rogiem [0, 0] i prawym dolnym rogiem [szerokość, wysokość].
Rysunek 2. Układ współrzędnych rysunku / siatka rysunku.

Jeśli np. chcesz narysować linię ukośną od prawego górnego rogu obszaru rysowania do lewego dolnego rogu, możesz użyć funkcji DrawScope.drawLine() i określić przesunięcie początku i końca za pomocą odpowiednich pozycji 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 oferuje przekształcenia, które zmieniają miejsce lub sposób wykonywania poleceń rysowania.

Skala

Użyj DrawScope.scale() aby zwiększyć rozmiar operacji rysowania o określony współczynnik. Operacje takie jakscale() mają zastosowanie do wszystkich operacji rysowania w odpowiedniej funkcji lambda. Na przykład poniższy kod zwiększa wartość scaleX 10 razy, a wartość 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. Stosowanie operacji skalowania do okręgu w Canvas.

Tłumacz

Użyj DrawScope.translate(), aby przesuwać rysunek w górę, w dół, w lewo lub w prawo. Na przykład poniższy 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 został przesunięty poza środek
Rysunek 4. Zastosowanie operacji tłumaczenia do okręgu na płótnie.

Obróć

Użyj DrawScope.rotate() do obracania rysunku 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 pośrodku ekranu.
Rysunek 5. Używamy rotate(), aby zastosować obrót do bieżącego zakresu rysowania, który obraca prostokąt o 45 stopni.

Wcięcie

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

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

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

Prostokąt z marginesami ze wszystkich stron
Rysunek 6. Stosowanie wcięcia 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 wybrane zmiany. Używanie funkcji withTransform() jest bardziej wydajne niż wykonywanie zagnieżdżonych wywołań poszczególnych przekształceń, ponieważ wszystkie przekształcenia są wykonywane razem w ramach jednej operacji. Nie ma więc potrzeby, aby funkcja Compose obliczała i zapisywała każde 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 na bok ekranu
Rysunek 7. Użyj withTransform, aby zastosować zarówno obrót, jak i przesunięcie, obracając prostokąt i przesuwając go w lewo.

Typowe operacje rysowania

Rysowanie tekstu

Aby narysować tekst w Compose, możesz zwykle użyć funkcji kompozycyjnej Text. Jeśli jednak jesteś w DrawScope lub chcesz narysować tekst ręcznie z dostosowaniem, możesz użyć metody DrawScope.drawText().

Aby narysować tekst, utwórz obiekt TextMeasurer za pomocą funkcji rememberTextMeasurer i wywołaj funkcję drawText z użyciem narzędzia pomiarowego:

val textMeasurer = rememberTextMeasurer()

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

Wyświetlanie napisu „Hello” narysowanego w Canvas
Rysunek 8. Rysowanie tekstu na obszarze roboczym.

Mierzenie tekstu

Rysowanie tekstu działa nieco inaczej niż inne polecenia rysowania. Zwykle podajesz poleceniu rysowania rozmiar (szerokość i wysokość), w jakim ma być narysowany kształt lub obraz. W przypadku tekstu istnieje kilka parametrów, które kontrolują rozmiar renderowanego tekstu, takich jak rozmiar czcionki, czcionka, ligatury i odstępy między literami.

W Compose 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 zajmowanego przez 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 powoduje wyświetlenie tekstu na różowym tle:

Tekst wielowierszowy zajmujący ⅔ powierzchni, z prostokątnym tłem
Rysunek 9. Tekst wielowierszowy zajmujący ⅔ powierzchni, z prostokątnym tłem.

Dostosowanie ograniczeń, rozmiaru czcionki lub dowolnej właściwości, która wpływa na zmierzony rozmiar, powoduje zgłoszenie nowego rozmiaru. Możesz ustawić stały rozmiar zarówno elementu width, jak i height, a tekst będzie wtedy zgodny z ustawionym TextOverflow. Na przykład poniższy kod renderuje tekst na ⅓ wysokości i ⅓ szerokości obszaru kompozycji oraz ustawia wartość 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 rysowany w ramach ograniczeń z wielokropkiem na końcu:

Tekst narysowany na różowym tle, z wielokropkiem ucinającym tekst.
Rysunek 10. TextOverflow.Ellipsis ze stałymi ograniczeniami dotyczącymi pomiaru tekstu.

Rysowanie obrazu

Aby narysować ImageBitmap za pomocą DrawScope, wczytaj obraz za pomocą 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 na płótnie
Rysunek 11. Rysowanie ImageBitmap w Canvas.

Rysowanie podstawowych kształtów

DrawScope ma 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

Urządzenie wyjściowe

drawCircle()

narysować okrąg,

drawRect()

draw rect

drawRoundedRect()

rysuj zaokrąglony prostokąt

drawLine()

rysowanie linii,

drawOval()

Rysowanie owalu

drawArc()

Rysowanie łuku

drawPoints()

punkty rysowania,

Rysowanie ścieżki

Ścieżka to seria instrukcji matematycznych, które po wykonaniu tworzą rysunek. DrawScope może narysować ścieżkę za pomocą metody DrawScope.drawPath().

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

Odwrócony fioletowy trójkąt ścieżki narysowany w kompozycji
Rysunek 12. Tworzenie i rysowanie Path w Compose.

Dostęp do obiektu Canvas

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

Jeśli na przykład masz niestandardowy Drawable, który chcesz narysować na płótnie, możesz uzyskać dostęp do płótna 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 obiekt ShapeDrawable zajmujący cały obszar
Rysunek 13. Dostęp do obszaru roboczego, aby narysować Drawable.

Więcej informacji

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