Grafiken in Compose

Bei vielen Apps muss genau gesteuert werden können, was auf dem Bildschirm gezeichnet wird. Sie könnten zum Beispiel einen Kasten oder einen Kreis auf dem Bildschirm platzieren. oder eine aufwendige Anordnung grafischer Elemente in vielen verschiedenen Stilen.

Einfache Zeichnung mit Modifikatoren und DrawScope

Benutzerdefinierte Elemente lassen sich in „Schreiben“ hauptsächlich mit Modifikatoren zeichnen, z. B. Modifier.drawWithContent, Modifier.drawBehind und Modifier.drawWithCache

Wenn Sie beispielsweise etwas hinter Ihrem Composeable zeichnen möchten, können Sie mit der Tastenkombination drawBehind Zeichenbefehle ausführen:

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

Wenn Sie nur ein grafisches Element benötigen, können Sie das Canvas-Element verwenden. Das Canvas-Komposit ist eine praktische Ummantelung für Modifier.drawBehind. Sie platzieren das Canvas in Ihrem Layout genauso wie jedes andere Compose-UI-Element. Im Canvas können Sie Elemente zeichnen und dabei ihren Stil und Standort.

Alle Zeichenmodifikatoren stellen eine DrawScope bereit, eine begrenzte Zeichenumgebung, die ihren eigenen Status beibehält. So können Sie die Parameter für eine Gruppe grafische Elemente. Das DrawScope-Objekt enthält mehrere nützliche Felder, z. B. size, ein Size-Objekt, das die aktuellen Dimensionen des DrawScope angibt.

Um etwas zu zeichnen, können Sie eine der vielen Zeichenfunktionen in DrawScope verwenden. Mit dem folgenden Code wird beispielsweise ein Rechteck in der oberen linken Ecke des Bildschirms gezeichnet:

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

Rosa Rechteck, gezeichnet auf einem weißen Hintergrund, der ein Viertel des Bildschirms einnimmt
Abbildung 1: Rechteck, das mit Canvas in „Compose“ gezeichnet wurde

Weitere Informationen zu verschiedenen Modifikatoren zum Zeichnen finden Sie unter Grafikmodifikatoren. Dokumentation.

Koordinatensystem

Um etwas auf dem Bildschirm zu zeichnen, müssen Sie den Versatz (x und y) und die Größe des Ihren Artikel. Bei vielen der Zeichenmethoden auf DrawScope sind die Position und Größe werden durch Standardparameterwerte bereitgestellt. Mit den Standardparametern wird das Element in der Regel am [0, 0]-Punkt auf dem Canvas positioniert und ein Standardsize festgelegt, der den gesamten Zeichenbereich füllt, wie im Beispiel oben. Das Rechteck ist hier links oben zu sehen. Wenn Sie die Größe und Position eines Elements anpassen möchten, müssen Sie das Koordinatensystem in Compose kennen.

Der Ursprung des Koordinatensystems ([0,0]) befindet sich im linken oberen Pixel des Zeichenbereichs. Der Wert von x steigt bei Bewegung nach rechts und y im Laufe der Zeit an. nach unten zeigen.

Ein Raster mit dem Koordinatensystem, das die obere linke Ecke [0, 0] und die untere rechte Ecke [Breite, Höhe] zeigt
Abbildung 2: Koordinatensystem/Zeichenraster für Zeichnungen.

Wenn Sie beispielsweise eine Diagonale von der rechten oberen Ecke des Canvas-Bereichs zur linken unteren Ecke ziehen möchten, können Sie die Funktion DrawScope.drawLine() verwenden und einen Start- und Endversatz mit den entsprechenden x- und y-Positionen angeben:

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
    )
}

Grundlegende Transformationen

DrawScope bietet Transformationen, mit denen sich ändern lässt, wo oder wie die Zeichenbefehle verwendet werden ausgeführt werden.

Skalieren

Verwenden Sie DrawScope.scale() um die Größe Ihrer Zeichenvorgänge um einen Faktor zu erhöhen. Vorgänge wie scale() gelten für alle Zeichenvorgänge innerhalb der entsprechenden Lambda-Funktion. Im folgenden Code wird scaleX beispielsweise zehnmal und scaleY fünfzehnmal erhöht:

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

Ein nicht einheitlich skalierter Kreis
Abbildung 3: Skalierungsvorgang auf einen Kreis in Canvas anwenden

Übersetzen

Mit DrawScope.translate() können Sie die Zeichnungen nach oben, unten, links oder rechts verschieben. Mit dem folgenden Code wird die Zeichnung beispielsweise um 100 Pixel nach rechts und 300 Pixel nach oben verschoben:

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

Ein Kreis, der nicht mittig ausgerichtet ist
Abbildung 4: Übersetzungsvorgang auf einen Kreis in Canvas anwenden

Drehen

Verwenden Sie DrawScope.rotate() um Ihre Zeichenvorgänge um einen Drehpunkt zu drehen. Beispiel: Der Parameter Mit dem folgenden Code wird ein Rechteck um 45 Grad gedreht:

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

Ein Smartphone mit einem um 45 Grad gedrehten Rechteck in der Mitte des Displays
Abbildung 5. Wir verwenden rotate(), um eine Drehung auf den aktuellen Zeichenbereich anzuwenden, wodurch das Rechteck um 45 Grad gedreht wird.

Eingebettet

Mit DrawScope.inset() können Sie die Standardparameter der aktuellen DrawScope, wobei die Grenzen der Zeichnung geändert und die Zeichnungen übersetzt werden entsprechend:

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

Mit diesem Code wird den Zeichenbefehlen effektiv ein Padding hinzugefügt:

Ein Rechteck, das rundum mit einem Abstand versehen ist
Abbildung 6. Eingefügte Elemente auf Zeichenbefehle anwenden

Mehrere Transformationen

Um mehrere Transformationen auf Ihre Zeichnungen anzuwenden, verwenden Sie die Methode DrawScope.withTransform()-Funktion, die ein wendet eine einzelne Transformation an, in der alle gewünschten Änderungen kombiniert werden. Mit withTransform() ist effizienter als verschachtelte Aufrufe an einzelne Nutzer. da alle Transformationen gemeinsam in einem anstatt Compose einzeln zu berechnen und zu speichern, verschachtelte Transformationen ein.

Mit dem folgenden Code werden beispielsweise sowohl eine Übersetzung als auch eine Rotation auf den Rechteck:

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
        )
    }
}

Ein Smartphone mit einem gedrehten Rechteck, das zur Seite des Displays verschoben ist
Abbildung 7. Mit withTransform können Sie sowohl eine Drehung als auch eine Verschiebung anwenden. Drehen Sie dazu das Rechteck und verschieben Sie es nach links.

Gängige Zeichenvorgänge

Text zeichnen

Um Text in „Schreiben“ zu zeichnen, können Sie normalerweise die zusammensetzbare Funktion Text verwenden. Wenn Sie sich jedoch in einem DrawScope befinden oder den Text manuell mit Anpassungen zeichnen möchten, können Sie die Methode DrawScope.drawText() verwenden.

Wenn Sie Text zeichnen möchten, erstellen Sie mit rememberTextMeasurer eine TextMeasurer und rufen Sie drawText mit dem Messwert auf:

val textMeasurer = rememberTextMeasurer()

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

Auf Canvas gezeichnete Hallo
Abbildung 8. Text auf Canvas zeichnen.

Text messen

Das Zeichnen von Text funktioniert etwas anders als andere Zeichenbefehle. Normalerweise geben Sie dem Zeichenbefehl die Größe (Breite und Höhe) an, in der die Form/das Bild gezeichnet werden soll. Bei Text gibt es einige Parameter, die die Größe des gerenderten Text, wie etwa Schriftgröße, Schriftart, Ligaturen und Buchstabenabstand.

In Compose können Sie mithilfe eines TextMeasurer auf die gemessene Textgröße zugreifen, die von den oben genannten Faktoren abhängt. Wenn Sie hinter dem Text einen Hintergrund zeichnen möchten, können Sie anhand der gemessenen Informationen die Größe des Bereichs ermitteln, den der Text einnimmt:

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()
)

Bei diesem Code-Snippet wird der Text mit einem rosa Hintergrund versehen:

Mehrzeiliger Text, der ⅔ des gesamten Bereichs einnimmt, mit einem Hintergrund-Rechteck
Abbildung 9. Mehrzeiliger Text, der ⅔ des gesamten Bereichs einnimmt, mit einem Hintergrund-Rechteck.

Anpassen der Einschränkungen, der Schriftgröße oder anderer Eigenschaften, die sich auf die gemessene Größe auswirken führt zu einer neuen Größe. Sie können sowohl für width als auch für und height. Der Text folgt dann auf TextOverflow. Für Beispiel: Der folgende Code rendert Text in 1⁄3 der Höhe und 1⁄3 der Breite des zusammensetzbaren Bereichs und legt TextOverflow auf TextOverflow.Ellipsis fest:

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()
)

Der Text wird jetzt innerhalb der Begrenzungen mit Auslassungspunkten am Ende dargestellt:

Auf rosafarbenem Hintergrund gezeichneter Text mit Auslassungspunkten, die den Text abgeschnitten haben.
Abbildung 10. TextOverflow.Ellipsis mit festen Einschränkungen bei der Textmessung.

Bild zeichnen

Wenn Sie mit DrawScope ein ImageBitmap zeichnen möchten, laden Sie das Bild mit ImageBitmap.imageResource() und rufen Sie dann drawImage auf:

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

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

Ein auf Canvas gezeichnetes Bild eines Hundes
Abbildung 11. Zeichnen eines ImageBitmap auf Canvas

Einfache Formen zeichnen

In DrawScope gibt es viele Funktionen zum Zeichnen von Formen. Verwenden Sie zum Zeichnen einer Form eine der vordefinierten Zeichenfunktionen, z. B. drawCircle:

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

API

Ausgabe

drawCircle()

Kreis zeichnen

drawRect()

draw rect

drawRoundedRect()

draw rounded rect

drawLine()

draw line

drawOval()

Oval zeichnen

drawArc()

Bogen zeichnen

drawPoints()

Punkte zeichnen

Pfad zeichnen

Ein Pfad ist eine Reihe mathematischer Anweisungen, die einmal zu einer Zeichnung führen ausgeführt haben. DrawScope kann mit der Methode DrawScope.drawPath() einen Pfad zeichnen.

Angenommen, Sie möchten ein Dreieck zeichnen. Sie können einen Pfad mit Funktionen wie lineTo() und moveTo() anhand der Größe des Zeichenbereichs generieren. Rufen Sie dann drawPath() mit diesem neu erstellten Pfad auf, um ein Dreieck zu erhalten.

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()
)

Ein auf dem Kopf stehendes lilafarbenes Dreieck mit dem Pfad „Schreiben“
Abbildung 12. Path in Compose wird erstellt und gezeichnet.

Zugriff auf Canvas-Objekt

Mit DrawScope haben Sie keinen direkten Zugriff auf ein Canvas-Objekt. Mit DrawScope.drawIntoCanvas() können Sie auf das Canvas-Objekt selbst zugreifen, für das Sie Funktionen aufrufen können.

Wenn Sie beispielsweise ein benutzerdefiniertes Drawable haben, das Sie auf dem Canvas zeichnen möchten, können Sie auf den Canvas zugreifen und Drawable#draw() aufrufen, wobei Sie das Canvas-Objekt übergeben:

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()
)

Ein ovales schwarzes ShapeDrawable, das die volle Größe einnimmt
Abbildung 13. Zugriff auf die Zeichenfläche, um ein Drawable zu zeichnen

Weitere Informationen

Weitere Informationen zu Zeichnungen in „Compose“ finden Sie in den folgenden Ressourcen: