Grafika w sekcji Utwórz

Wiele aplikacji musi mieć możliwość precyzyjnego kontrolowania tego, co jest wyświetlane na ekranie. Może to być coś tak prostego jak umieszczenie na ekranie kwadratu lub koła w odpowiednim miejscu. Może to być też skomplikowane rozmieszczenie elementów graficznych w różnych stylach.

Rysunek podstawowy z modyfikatorami i DrawScope

Głównym sposobem tworzenia elementów niestandardowych w sekcji Komponowanie jest używanie modyfikatorów, takich jak Modifier.drawWithContent, Modifier.drawBehindModifier.drawWithCache.

Aby na przykład narysować coś za kompozycją, możesz użyć modyfikatora drawBehind, aby rozpocząć wykonywanie poleceń rysowania:

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

Jeśli potrzebujesz tylko komponentu, który wyświetla obraz, możesz użyć komponentu Canvas. Komponent Canvas to wygodna szata dla Modifier.drawBehind. Umieść Canvas w układzie w taki sam sposób jak dowolny inny element interfejsu tworzenia wiadomości. W sekcji Canvas możesz rysować elementy z dokładną kontrolą nad ich stylem i lokalizacją.

Wszystkie modyfikatory rysunku udostępniają DrawScope, czyli ograniczone środowisko rysunku, które zachowuje własny stan. Umożliwia to ustawienie parametrów grupy elementów graficznych. Obiekt DrawScope zawiera kilka przydatnych pól, takich jak size, oraz obiekt Size określający bieżące wymiary obiektu DrawScope.

Aby coś narysować, możesz użyć jednej z wielu funkcji rysowania w 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 ćwierć ekranu
Rysunek 1. Prostokąt narysowany za pomocą Canvas w Compose.

Więcej informacji o różnych modyfikatorach rysowania znajdziesz w dokumentacji Modyfikatory 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 obiekcie DrawScope pozycja i rozmiar są podawane przez domyślne wartości parametrów. Parametry domyślne zwykle umieszczają element w miejscu [0, 0] na kanwie i zawierają domyślną wartość size, która wypełnia cały obszar rysunku, jak w przykładzie powyżej – widzisz prostokąt w lewym górnym rogu. Aby dostosować rozmiar i położenie elementu, musisz zrozumieć system współrzędnych w sekcji Tworzenie.

Punkt początkowy układu współrzędnych ([0,0]) znajduje się w lewym górnym rogu obszaru rysunku. Wartość x rośnie, gdy przesuwasz ją w prawo, a wartość y rośnie, gdy przesuwasz ją w dół.

Siatka pokazująca układ współrzędnych w lewym górnym rogu [0, 0] i w prawym dolnym rogu [szerokość, wysokość]
Rysunek 2. Rysowanie układu współrzędnych / siatki rysunkowej.

Jeśli np. chcesz narysować linię ukośną od prawego górnego rogu obszaru rysunku do lewego dolnego rogu, możesz użyć funkcji DrawScope.drawLine() i podać przesunięcie początkowe i końcowe z odpowiednimi współrzędnymi 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 zawiera przekształcenia, które umożliwiają zmianę miejsca lub sposobu wykonywania poleceń rysowania.

Skala

Użyj parametru DrawScope.scale(), aby zwiększyć rozmiar operacji rysowania. Operacje takie jak scale() mają zastosowanie do wszystkich operacji rysowania w odpowiedniej funkcji lambda. Na przykład poniższy kod zwiększa wartość zmiennej scaleX 10 razy, a zmiennej scaleY – 15 razy:

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

Okrąg niejednolicie powiększony
Rysunek 3. Skalowanie okręgu w Canvas.

Tłumacz

Użyj przycisku DrawScope.translate(), aby przesunąć operacje rysowania w górę, w dół, w lewo lub w prawo. Na przykład kod poniżej 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())
    }
}

koło, które zostało przesunięte poza środek
Rysunek 4. Zmiana położenia koła w Canvas.

Obróć

Użyj opcji DrawScope.rotate(), aby obracać rysunek 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 pośrodku ekranu z prostokątem obróconym o 45°
Rysunek 5. Używamy funkcji rotate(), aby obrócić bieżący zakres rysunku, co powoduje obrót prostokąta o 45 stopni.

Odcięcie

Użyj parametru DrawScope.inset(), aby dostosować domyślne parametry bieżącego DrawScope, zmieniając w ten sposób granice rysunku i odpowiednio przesuwając rysunki:

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

Ten kod dodaje wypełnienie do poleceń rysowania:

Prostokąt z dodatkową przestrzenią wokół
Rysunek 6. Stosowanie wstawienia do wewnątrz do poleceń rysowania.

Wiele przekształceń

Aby zastosować do rysunków wiele przekształceń, użyj funkcji DrawScope.withTransform(), która tworzy i zachowuje pojedyncze przekształcenie, łączące wszystkie żądane zmiany. Użycie funkcji withTransform() jest wydajniejsze niż wywoływanie zagnieżdżonych funkcji w przypadku poszczególnych przekształceń, ponieważ wszystkie przekształcenia są wykonywane razem w ramach pojedynczej operacji, a nie wymagają obliczania i zapisywania przez funkcję Compose poszczególnych zagnieżdżonych przekształceń.

Na przykład ten kod stosuje przesunięcie i obrót prostokąta:

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óbnym prostokątem przesuniętym na bok ekranu
Rysunek 7. Użyj withTransform, aby zastosować zarówno obrót, jak i translację, obracając prostokąt i przesuwając go w lewo.

Typowe operacje rysowania

Rysowanie tekstu

Aby rysować tekst w sekcji Tworzenie, możesz zwykle użyć modułu Text. Jeśli jednak korzystasz z poziomu DrawScope lub chcesz ręcznie narysować tekst z użyciem funkcji dostosowywania, możesz użyć metody DrawScope.drawText().

Aby narysować tekst, utwórz TextMeasurer za pomocą rememberTextMeasureri wywołaj funkcję drawText z użyciem narzędzia do pomiaru:

val textMeasurer = rememberTextMeasurer()

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

Rysowanie „Cześć” w aplikacji Canvas
Rysunek 8. Rysowanie tekstu na obszarze roboczym.

Tekst pomiaru

Rysowanie tekstu działa nieco inaczej niż inne polecenia rysowania. Zazwyczaj podajesz polecenie rysowania rozmiar (szerokość i wysokość), aby narysować 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ć atrybutu TextMeasurer, aby uzyskać dostęp do zmierzonego rozmiaru tekstu w zależności od wymienionych powyżej 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 różowego tła tekstu:

Tekst wielowierszowy zajmujący ⅔ pełnego obszaru z kwadratowym tłem
Rysunek 9. Tekst wielowierszowy zajmujący 2/3 pełnego obszaru z prostokątem tła.

Zmiana 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 dla width, jak i height. Tekst będzie wtedy dopasowany do ustawień TextOverflow. Na przykład kod poniżej renderuje tekst na wysokości ⅓ i szerokości ⅓ obszaru kompozycyjnego 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 wyświetlany w ograniczeniach z wielokropkiem na końcu:

Tekst na różowym tle z trójkropkiem.
Rysunek 10. TextOverflow.Ellipsis z stałymi ograniczeniami dotyczącymi pomiaru tekstu.

Rysowanie obrazu

Aby narysować ImageBitmap za pomocą narzędzia DrawScope, załaduj obraz za pomocą ImageBitmap.imageResource(), a następnie wywołaj funkcję drawImage:

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

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

Obraz przedstawiający psa namalowany na płótnie
Rysunek 11. Rysowanie ImageBitmap na kanwie.

Rysowanie podstawowych kształtów

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

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

Interfejs API

Urządzenie wyjściowe

drawCircle()

rysuj okrąg

drawRect()

draw rect

drawRoundedRect()

draw rounded rect

drawLine()

rysowanie linii

drawOval()

rysowanie owalu

drawArc()

rysowanie łuku

drawPoints()

rysować punkty

Rysowanie ścieżki

Ścieżka to seria instrukcji matematycznych, która po wykonaniu tworzy 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 wielkości obszaru rysunku. Następnie wywołaj funkcję drawPath() z tym nowo utworzonym ś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 trójkątny fioletowy ślad ścieżki na stronie tworzenia
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ć funkcji DrawScope.drawIntoCanvas(), aby uzyskać dostęp do obiektu Canvas, którego możesz używać do wywoływania funkcji.

Jeśli na przykład masz niestandardową funkcję Drawable, która ma rysować na płótnie, możesz uzyskać dostęp do obrazu 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()
)

Czarny owalny ShapeDrawable w pełnym rozmiarze
Rysunek 13. Dostęp do obszaru roboczego w celu narysowania Drawable.

Więcej informacji

Więcej informacji o rysowaniu w Compose znajdziesz w tych zasobach: