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 pola lub okręgu na ekranie w odpowiednim miejscu albo skomplikowany układ elementów graficznych w wielu różnych stylach.

Podstawowy rysunek z modyfikatorami i DrawScope

Podstawowym sposobem rysowania niestandardowych elementów w Compose są 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 tylko funkcji kompozycyjnej, która rysuje, możesz użyć funkcji kompozycyjnej Canvas. Funkcja kompozycyjna Canvas to wygodna otoka wokół Modifier.drawBehind. Element Canvas umieszczasz w układzie tak samo jak każdy inny element interfejsu Compose. W Canvas możesz rysować elementy, precyzyjnie kontrolując ich styl i lokalizację.

Wszystkie modyfikatory rysowania udostępniają DrawScope, czyli ograniczone środowisko rysowania, które zachowuje własny stan. Umożliwia to ustawienie parametrów dla grupy elementów graficznych. DrawScope zawiera kilka przydatnych pól, takich jak size, obiekt Size określający bieżące wymiary 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 1/4 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 (x i y) 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 – prostokąt znajduje się w lewym górnym rogu. Aby dostosować rozmiar i położenie elementu, musisz zrozumieć system współrzędnych w Compose.

Punkt początkowy układu współrzędnych ([0,0]) znajduje się w lewym górnym pikselu obszaru rysowania. Wartość x rośnie, gdy przesuwasz się w prawo, a wartość y rośnie, gdy przesuwasz 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 na przykład chcesz narysować linię po przekątnej od prawego górnego rogu obszaru roboczego do lewego dolnego rogu, możesz użyć funkcji DrawScope.drawLine() i określić przesunięcie początkowe i końcowe 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 oferuje przekształcenia, które zmieniają miejsce lub sposób wykonywania poleceń rysowania.

Skaluj

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

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

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

Tłumacz

Użyj przycisków DrawScope.translate(), aby przesuwać operacje rysowania w górę, w dół, w lewo lub 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. Zastosowanie operacji translacji do okręgu w Canvas.

Obróć

Użyj 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(), aby zastosować obrót do bieżącego zakresu rysowania, co 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 rysunku i odpowiednio go przesuwając:

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 z dopełnieniem ze wszystkich stron
Rysunek 6. Applying an inset to drawing commands.

Wiele przekształceń

Aby zastosować do rysunków kilka przekształceń, użyj funkcji DrawScope.withTransform(), która tworzy i stosuje pojedyncze przekształcenie łączące wszystkie żądane zmiany. Używanie 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, zamiast obliczania i zapisywania przez Compose każdego 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 użyć funkcji kompozycyjnej Text. Jeśli jednak używasz DrawScope lub chcesz ręcznie narysować tekst z dostosowaniem, możesz użyć metody DrawScope.drawText().

Aby narysować tekst, utwórz TextMeasurer za pomocą rememberTextMeasurer i wywołaj drawText z miernikiem:

val textMeasurer = rememberTextMeasurer()

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

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

Tekst pomiaru

Rysowanie tekstu działa nieco inaczej niż inne polecenia rysowania. Zwykle polecenie rysowania zawiera rozmiar (szerokość i wysokość), w jakim ma zostać 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, że tekst ma różowe tło:

Tekst wielowierszowy zajmujący ⅔ całego obszaru, z prostokątem w tle
Rysunek 9. Tekst wielowierszowy zajmujący ⅔ całego obszaru, z prostokątem w tle.

Dostosowanie ograniczeń, rozmiaru czcionki lub dowolnej właściwości, która wpływa na mierzony rozmiar, powoduje zgłoszenie nowego rozmiaru. Możesz ustawić stały rozmiar zarówno dla width, jak i height, a tekst będzie zgodny z ustawionym TextOverflow. Na przykład ten kod renderuje tekst na ⅓ wysokości i ⅓ szerokości obszaru kompozycyjnego oraz ustawia 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 na różowym tle, ucięty wielokropkiem.
Rysunek 10. TextOverflow.Ellipsis ze stałymi ograniczeniami dotyczącymi pomiaru tekstu.

Narysuj obraz

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 przedstawiający psa narysowanego w Canvas
Rysunek 11. Rysowanie ImageBitmap w Canvas.

rysowanie podstawowych kształtów,

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

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

Interfejs API

Wyniki

drawCircle()

narysuj okrąg

drawRect()

narysuj prostokąt

drawRoundedRect()

narysuj prostokąt z zaokrąglonymi krawędziami

drawLine()

narysuj linię

drawOval()

narysuj owal

drawArc()

narysuj łuk

drawPoints()

punkty do losowania

Narysuj ścieżkę

Ścieżka to seria instrukcji matematycznych, które po wykonaniu dają 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() i moveTo(), używając 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 narysowany w Compose
Rysunek 12. Tworzenie i rysowanie Path w Compose.

Uzyskiwanie dostępu do obiektu Canvas

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

Jeśli na przykład masz niestandardowy obiekt Drawable, który chcesz narysować na obszarze roboczym, możesz uzyskać dostęp do obszaru roboczego 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()
)

Owalny czarny obiekt ShapeDrawable zajmujący cały rozmiar
Rysunek 13. Otwieram obszar roboczy, aby narysować Drawable.

Więcej informacji

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