Canvas-Objekte

Dieses Codelab ist Teil des Kurses „Advanced Android in Kotlin“. Sie profitieren von diesem Kurs, wenn Sie die Codelabs nacheinander durcharbeiten. Das ist aber nicht zwingend erforderlich. Alle Kurs-Codelabs finden Sie auf der Landingpage für Codelabs auf Android-Geräten für Fortgeschrittene.

Einführung

Für dieses Codelab sind Clips eine Möglichkeit, um Bereiche eines Bilds, eines Canvas oder einer Bitmap zu definieren, die selektiv oder nicht auf den Bildschirm gezeichnet werden können. Der Zweck des Clips besteht darin, das Überschreiben zu reduzieren. Bei einem Überlauf wird ein Pixel auf dem Bildschirm mehrmals gezeichnet, um das endgültige Bild darzustellen. Wenn Sie das Zeichnen reduzieren, minimieren Sie die Häufigkeit, mit der ein Pixel oder eine Region des Bildschirms gezeichnet wird, um die Zeichenleistung zu maximieren. Mit Clips können Sie ebenfalls interessante Effekte beim Design und der Animation der Benutzeroberfläche erzielen.

Wenn Sie z. B. wie unten gezeigt einen Stapel sich überschneidender Karten zeichnen, ist es in der Regel effizienter, nur die sichtbaren Teile zu zeichnen, anstatt jede Karte von unten nach oben zu zeichnen. Normalerweise ist das Erstellen von Clips mit Kosten verbunden und das Android-System arbeitet viel mit Zeichnungen.

Wenn Sie nur die sichtbaren Teile der Karten zeichnen möchten, geben Sie für jede Karte einen Clipbereich an. Wenn beispielsweise im Diagramm unten ein Clip-Clip auf ein Bild angewendet wird, wird nur der Teil innerhalb dieses Rechtecks angezeigt.

Der Zuschneidebereich ist normalerweise ein Rechteck, aber es kann eine beliebige Form oder Kombination von Formen oder sogar Text sein. Sie können auch angeben, ob die Region innerhalb des Bereichs überschritten werden soll. Sie könnten beispielsweise einen kreisförmigen Bereich mit Ausschnitten erstellen und nur anzeigen, was außerhalb des Kreises liegt.

In diesem Codelab werden Sie verschiedene Methoden zum Erstellen von Clips testen.

Was Sie bereits wissen sollten

Sie sollten mit Folgendem vertraut sein:

  • Hier erfahren Sie, wie Sie eine App mit Activity erstellen und über Android Studio ausführen.
  • Erstellen und Zeichnen auf einem Canvas
  • Benutzerdefinierte View erstellen und onDraw() und onSizeChanged() überschreiben

Lerninhalte

  • Objekte zeichnen, die auf Canvas gezeichnet werden.
  • Zeichenstatus eines Canvas speichern und wiederherstellen
  • Transformationen auf einen Canvas und auf Text anwenden

Aufgaben

  • Sie können eine App erstellen, mit der abgeschnittene Formen auf dem Bildschirm angezeigt werden. Darin sind verschiedene Möglichkeiten zum Erstellen von Clips und das Ergebnis für die Sichtbarkeit dieser Formen dargestellt.
  • Außerdem werden Sie übersetzten und verzerrten Text zeichnen.

In der App ClippingExample erfahren Sie, wie Sie Formen verwenden und kombinieren können, um festzulegen, welche Teile eines Canvas in einer Ansicht angezeigt werden. Die finale App sieht so aus wie im Screenshot unten.

Da Sie diese Anwendung von Grund auf neu erstellen, müssen Sie ein Projekt einrichten, Dimensionen und Strings definieren und einige Variablen deklarieren.

Schritt 1: ClippingExample-Projekt erstellen

  1. Erstellen Sie ein Kotlin-Projekt namens ClippingExample mit der Vorlage Empty Activity (Leere Aktivität). Verwenden Sie com.example.android für das Präfix des Paketnamens.
  2. Öffnen Sie MainActivity.kt.
  3. Ersetze in der onCreate()-Methode die Standardinhaltsansicht und setze die Inhaltsansicht auf eine neue Instanz von ClippedView. Dies ist Ihre benutzerdefinierte Ansicht für die zu erstellenden Beispiele.
setContentView(ClippedView(this))
  1. Erstellen Sie auf derselben Ebene wie bei MainActivity.kt eine neue Kotlin-Datei und Klasse für eine benutzerdefinierte Ansicht mit der Bezeichnung ClippedView, die View erweitert. Weisen Sie ihm die Signatur unten zu. Der Rest Ihrer Arbeit befindet sich innerhalb dieses ClippedView. Die Annotation @JvmOverloads weist den Kotlin-Compiler an, Überlasten für diese Funktion zu erzeugen, die die Standardparameterwerte ersetzen.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

Schritt 2: Dimensionen und String-Ressourcen hinzufügen

  1. Definieren Sie die Dimensionen, die Sie für die abgeschnittenen Ansichten in einer neuen Ressourcendatei in res/values/dimens.xml verwenden möchten. Diese Standardabmessungen sind hartcodiert und haben eine Größe, die auf einen relativ kleinen Bildschirm passt.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">90dp</dimen>
   <dimen name="clipRectBottom">90dp</dimen>
   <dimen name="clipRectTop">0dp</dimen>
   <dimen name="clipRectLeft">0dp</dimen>

   <dimen name="rectInset">8dp</dimen>
   <dimen name="smallRectOffset">40dp</dimen>

   <dimen name="circleRadius">30dp</dimen>
   <dimen name="textOffset">20dp</dimen>
   <dimen name="strokeWidth">4dp</dimen>

   <dimen name="textSize">18sp</dimen>
</resources>

Damit die App auf einem größeren Bildschirm besser dargestellt wird und du Details besser sehen kannst, kannst du eine dimens-Datei mit größeren Werten erstellen. Diese gilt nur für größere Bildschirme.

  1. Klicken Sie in Android Studio mit der rechten Maustaste auf den Ordner values und wählen Sie Neue Datei „&gt“ für Ressourcen aus.
  2. Rufen Sie im Dialogfeld Neue Ressourcendatei die Datei dimens auf. Wählen Sie unter Verfügbare Kennzeichner die Option Kleinste Bildschirmbreite aus und klicken Sie auf die Schaltfläche >>, um sie den Auswahlelementen hinzuzufügen. Geben Sie „480“ in das Feld Kleinste Bildschirmbreite ein und klicken Sie auf OK.

  1. Die Datei sollte wie unten gezeigt im Wertordner angezeigt werden.

  1. Wenn Sie die Datei nicht sehen können, wechseln Sie zur Ansicht Projektdateien der App. Der vollständige Pfad der neuen Datei sieht so aus: ClippingExample/app/src/main/res/values-sw480dp/dimens.xml.

  1. Ersetze den Standardinhalt der Datei values-sw480dp/dimens.xml durch die unten aufgeführten Abmessungen.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">120dp</dimen>
   <dimen name="clipRectBottom">120dp</dimen>

   <dimen name="rectInset">10dp</dimen>
   <dimen name="smallRectOffset">50dp</dimen>

   <dimen name="circleRadius">40dp</dimen>
   <dimen name="textOffset">25dp</dimen>
   <dimen name="strokeWidth">6dp</dimen>
</resources>
  1. Fügen Sie in strings.xml die folgenden Strings hinzu. Damit werden Text auf dem Canvas angezeigt.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

Schritt 3: Paint- und Pfadobjekt erstellen und initialisieren

  1. Wechseln Sie zurück zur Android-Ansicht Ihres Projekts.
  2. Definieren Sie in ClippedView eine Variable Paint zum Zeichnen. Aktivieren Sie Anti-Aliasing und verwenden Sie die in den Dimensionen definierte Strichbreite und -größe, wie unten dargestellt.
private val paint = Paint().apply {
   // Smooth out edges of what is drawn without affecting shape.
   isAntiAlias = true
   strokeWidth = resources.getDimension(R.dimen.strokeWidth)
   textSize = resources.getDimension(R.dimen.textSize)
}
  1. Erstellen und initialisieren Sie in ClippedView ein Path, um den Pfad der gezeichneten Elemente lokal zu speichern. Importieren Sie android.graphics.Path.
private val path = Path()

Schritt 4: Formen einrichten

In dieser App zeigen Sie mehrere Zeilen und zwei Spalten mit unterschiedlich zugeschnittenen Formen an.

Ihre Gemeinsamkeit:

  • Ein großes Rechteck (quadratisch), das als Container dient
  • Diagonale Linie über das große Rechteck
  • Ein Kreis
  • Ein kurzer Textstring

In diesem Schritt richten Sie Dimensionen für diese Formen aus Ressourcen ein, sodass Sie die Dimensionen nur einmal benötigen, wenn Sie sie später verwenden.

  1. Fügen Sie in ClippedView unter dem path Variablen für Abmessungen für ein Clip-Rechteck um die gesamten Formen hinzu.
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
  1. Fügen Sie Variablen für die Ergänzung eines Rechtecks und den Offset eines kleinen Rechtecks hinzu.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. Fügen Sie eine Variable für den Radius eines Kreises hinzu. Dies ist der Radius des Kreises, der innerhalb des Rechtecks gezeichnet wird.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. Fügen Sie einen Abstand und eine Textgröße für Text ein, der innerhalb des Rechtecks gezeichnet wird.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

Schritt 4: Zeilen- und Spaltenpositionen einrichten

Die Formen für diese App werden in zwei Spalten und vier Zeilen angezeigt. Sie richten sich nach den Werten der oben eingerichteten Dimensionen. Die Berechnung hierfür ist nicht Teil dieses Codelabs. Wir empfehlen dir aber, sie dir genauer anzusehen, wenn du den Code in diesem Schritt verschiebst.

  1. Legen Sie die Koordinaten für zwei Spalten fest.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. Fügen Sie die Koordinaten der einzelnen Zeilen hinzu, einschließlich der letzten Zeile des umgewandelten Textes.
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
  1. Führen Sie Ihre App aus. Die App sollte mit einem leeren weißen Bildschirm unter dem Namen der App geöffnet werden.

In onDraw() rufen Sie Methoden zum Zeichnen von sieben unterschiedlichen Rechtecken auf, wie im Screenshot der App unten dargestellt. Die Rechtecke werden alle auf dieselbe Weise gezeichnet, aber der einzige Unterschied besteht in den definierten Zuschneidebereichen und der Position auf dem Bildschirm.

Der Algorithmus zum Zeichnen der Rechtecke funktioniert wie in der folgenden Tabelle und Erklärung gezeigt. Insgesamt können Sie eine Reihe von Rechtecken zeichnen, indem Sie den Ursprung von Canvas verschieben. Das Konzept besteht aus den folgenden Schritten:

(1) Übersetzen Sie zuerst Canvas an die Stelle, an der das Rechteck gezeichnet werden soll. Anstatt zu berechnen, wo das nächste Rechteck und alle anderen Formen gezeichnet werden sollen, verschieben Sie also den Ursprung Canvas des Koordinatensystems.

(2) Zeichnen Sie dann das Rechteck an der neuen Stelle des Canvas. Das heißt, Sie zeichnen die Formen an derselben Stelle im übersetzten Koordinatensystem. Das ist wesentlich einfacher und effizienter.

(3) Stellen Sie den Canvas wieder auf seinen ursprünglichen Origin-Wert ein.

So implementierst du den Algorithmus:

  1. Rufen Sie in onDraw() eine Funktion auf, um die Canvas mit der grauen Hintergrundfarbe zu füllen und die Originalformen zu zeichnen.
  2. Rufen Sie eine Funktion für jedes abgeschnittene Rechteck und den zu zeichnenden Text auf.

Für jedes Rechteck oder jeden Text:

  1. Speichere den aktuellen Status von Canvas, um ihn wieder zurücksetzen zu können.
  2. Übersetzen Sie die Origin des Canvas an die Stelle, an der Sie zeichnen möchten.
  3. Zuschnittformen und -pfade anwenden.
  4. Zeichnen Sie das Rechteck oder den Text.
  5. Der Status von Canvas wiederherstellen.

Schritt: onDraw() überschreiben

  1. Überschreibe onDraw(), wie im Code unten dargestellt. Sie rufen eine Funktion für jede Form auf, die Sie zeichnen und die Sie später implementieren.
 override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawBackAndUnclippedRectangle(canvas)
        drawDifferenceClippingExample(canvas)
        drawCircularClippingExample(canvas)
        drawIntersectionClippingExample(canvas)
        drawCombinedClippingExample(canvas)
        drawRoundedRectangleClippingExample(canvas)
        drawOutsideClippingExample(canvas)
        drawSkewedTextExample(canvas)
        drawTranslatedTextExample(canvas)
        // drawQuickRejectExample(canvas)
    }
  1. Erstellen Sie Stubs für jede Zeichenfunktion, damit der Code weiterhin kompiliert wird. Sie können den Code unten kopieren.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
}
private fun drawDifferenceClippingExample(canvas: Canvas){
}
private fun drawCircularClippingExample(canvas: Canvas){
}
private fun drawIntersectionClippingExample(canvas: Canvas){
}
private fun drawCombinedClippingExample(canvas: Canvas){
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas){
}
private fun drawOutsideClippingExample(canvas: Canvas){
}
private fun drawTranslatedTextExample(canvas: Canvas){
}
private fun drawSkewedTextExample(canvas: Canvas){
}
private fun drawQuickRejectExample(canvas: Canvas){
}

In der App werden dasselbe Rechteck und sieben Formen siebenmal gezeichnet, ohne zunächst Clips und dann sechsmal mit unterschiedlichen Zuschneidepfaden. Bei der Methode drawClippedRectangle() wird der Code zum Zeichnen eines Rechtecks wie unten dargestellt berücksichtigt.

Schritt 1: skizzClippedRectangle()-Methode erstellen

  1. Erstellen Sie eine drawClippedRectangle()-Methode, die das Argument canvas des Typs Canvas annimmt.
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. Lege in der drawClippedRectangle()-Methode die Grenzen des Zuschneidebereichs für die gesamte Form fest. Verwenden Sie ein Clip- Rechteck, das nur das Quadrat einschließt.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

Mit der Methode Canvas.clipRect(...) wird der Bereich des Bildschirms reduziert, in den zukünftige Ziehvorgänge schreiben können. Er legt die Begrenzungsgrenzen als räumliche Überschneidung des aktuellen Zuschneide-Rechtecks und des Rechtecks fest, das an clipRect() weitergegeben wird. Es gibt viele Varianten der Methode clipRect(), die verschiedene Formen von Regionen akzeptieren und verschiedene Vorgänge für das Zuschneidebereich ermöglichen.

  1. Fülle canvas auf Weiß. Ja. Die gesamte Leinwand, weil du keine Rechtecke gezeichnet hast, was abgeschnitten wird Aufgrund des Zuschneidebereichs wird nur der Bereich ausgefüllt, der durch das Zuschneidebereich definiert wurde. Dadurch wird ein weißes Rechteck erstellt. Die restlichen Oberflächen sind grau.
canvas.drawColor(Color.WHITE)
  1. Ändern Sie die Farbe in Rot und zeichnen Sie im Zuschneidebereich eine diagonale Linie.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. Legen Sie die Farbe auf Grün fest und zeichnen Sie innerhalb des Zuschneidebereichs einen Kreis.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. Legen Sie die Farbe auf Blau fest und zeichnen Sie Text, der am rechten Rand des Zuschneidebereichs ausgerichtet ist. Verwenden Sie canvas.drawText(), um Text zu zeichnen.
paint.color = Color.BLUE
// Align the RIGHT side of the text with the origin.
paint.textSize = textSize
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(
   context.getString(R.string.clipping),
   clipRectRight,textOffset,paint
)

Schritt 2: Methode „awBackAndUnclippedRectangle()“ implementieren

  1. Um die drawClippedRectangle()-Methode in Aktion zu sehen, zeichnen Sie das erste nicht zugeschnittene Rechteck, indem Sie die drawBackAndUnclippedRectangle()-Methode implementieren (siehe unten). Speichern Sie den canvas, übersetzen Sie in die erste Zeile und Spaltenposition, zeichnen Sie ihn durch drawClippedRectangle() und setzen Sie den canvas dann auf seinen vorherigen Zustand zurück.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Führen Sie Ihre App aus. Sie sollten das erste weiße Rechteck mit seinem Kreis, der roten Linie und dem Text auf einem grauen Hintergrund sehen.

In den folgenden Beispielverfahren zum Erstellen von Clips wenden Sie verschiedene Kombinationen von Ausschnittregionen an, um grafische Effekte zu erzielen. Außerdem erfahren Sie, wie Sie Abschneideregionen kombinieren können, um eine beliebige Form zu erstellen.

Jede dieser Methoden folgt demselben Muster.

  1. Aktuellen Status des Canvas speichern: canvas.save()

Der Aktivitätskontext sorgt für einen Stapel von Zeichenzuständen. Grafikzustände bestehen aus der aktuellen Transformationsmatrix und der aktuellen Zuschneideregion. Sie können den aktuellen Zustand speichern, Aktionen ausführen, die den Zeichenstatus ändern (z. B. übersetzen oder drehen den Canvas) und den gespeicherten Zustand dann wiederherstellen. Hinweis: Dies ist wie der Befehl „&sh“ in „git“.

Wenn Ihre Zeichnung Transformationen enthält, sind sie verkettet und rückgängig gemacht, wenn sie umgekehrt werden. Wenn Sie es beispielsweise übersetzen, dehnen und dann drehen, wird es schnell komplex. Speichern Sie stattdessen den Zustand des Canvas, wenden Sie Ihre Transformationen an und zeichnen Sie dann den vorherigen Zustand. Anschließend können Sie ihn wiederherstellen.

Sie können beispielsweise einen Ausschnittbereich definieren und diesen Status speichern. Übersetzen Sie dann den Canvas, fügen Sie einen Zuschneidebereich hinzu und drehen Sie ihn. Nach dem Zeichnen können Sie den ursprünglichen Clipstatus wiederherstellen und eine andere Transformation und Verschiebungsverzweigung vornehmen, wie im Diagramm dargestellt.

  1. Übersetzen Sie den Ausgangspunkt des Canvas in die Zeilen-/Spaltenkoordinaten: canvas.translate()

Es ist viel einfacher, den Ursprung des Canvas zu verschieben und dasselbe in einem neuen Koordinatensystem zu zeichnen, als alle Elemente zu zeichnen. Tipp: Sie können für das Drehen von Elementen dasselbe Verfahren verwenden.

  1. Übernehmen Sie Transformationen für path, falls vorhanden.
  2. Clip anwenden: canvas.clipPath(path)
  3. Zeichnen Sie die Formen: drawClippedRectangle() or drawText().
  4. Vorherigen Canvas-Status wiederherstellen: canvas.restore()

Schritt 1: DragDifferenceClippingExample(Canvas) implementieren

Fügen Sie Code hinzu, um das zweite Rechteck zu zeichnen, das den Unterschied zwischen zwei beschnittbaren Rechtecken zum Erstellen eines Bilderrahmeneffekts nutzt.

Verwenden Sie den folgenden Code, um Folgendes zu tun:

  1. Speichern Sie den Canvas.
  2. Übersetzen Sie den Ausgangspunkt des Canvas in offene Bereiche in der ersten Zeile, der zweiten Spalte, also rechts neben dem ersten Rechteck.
  3. Zwei Rechtecke mit Ausschnitten anwenden. Der Operator DIFFERENCE subtrahiert das zweite Rechteck vom ersten Rechteck.
  1. Rufen Sie die Methode drawClippedRectangle() auf, um den geänderten Canvas zu zeichnen.
  2. Canvas-Status wiederherstellen.
private fun drawDifferenceClippingExample(canvas: Canvas) {
   canvas.save()
   // Move the origin to the right for the next rectangle.
   canvas.translate(columnTwo,rowOne)
   // Use the subtraction of two clipping rectangles to create a frame.
   canvas.clipRect(
       2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .DIFFERENCE) was deprecated in API level 26. The recommended
   // alternative method is clipOutRect(float, float, float, float),
   // which is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
       canvas.clipRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset,
            Region.Op.DIFFERENCE
       )
   } else {
       canvas.clipOutRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Führen Sie Ihre App aus, die so aussehen sollte.

Schritt 2: DragCircularClippingExample(Canvas) implementieren

Fügen Sie als Nächstes Code hinzu, um ein Rechteck zu zeichnen, das einen kreisförmigen Ausschnittbereich verwendet, der aus einem kreisförmigen Pfad erstellt wurde. Hierdurch wird im Wesentlichen der Kreis entfernt, wodurch der graue Hintergrund angezeigt wird.

private fun drawCircularClippingExample(canvas: Canvas) {

   canvas.save()
   canvas.translate(columnOne, rowTwo)
   // Clears any lines and curves from the path but unlike reset(),
   // keeps the internal data structure for faster reuse.
   path.rewind()
   path.addCircle(
       circleRadius,clipRectBottom - circleRadius,
       circleRadius,Path.Direction.CCW
   )
   // The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
   // API level 26. The recommended alternative method is
   // clipOutPath(Path), which is currently available in
   // API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipPath(path, Region.Op.DIFFERENCE)
   } else {
       canvas.clipOutPath(path)
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

Schritt 3: DragIntersectionClippingExample(Canvas) implementieren

Fügen Sie als Nächstes Code hinzu, um den Schnittpunkt von zwei Zuschneide Rechtecken in der zweiten Zeile und Spalte zu zeichnen.

Je nach Bildschirmauflösung kann die Darstellung dieser Region variieren. Experimentiere mit der Dimension smallRectOffset, um die Größe der sichtbaren Region zu ändern. Ein kleinerer smallRectOffset führt zu einem größeren Bereich auf dem Bildschirm.

private fun drawIntersectionClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowTwo)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight - smallRectOffset,
       clipRectBottom - smallRectOffset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .INTERSECT) was deprecated in API level 26. The recommended
   // alternative method is clipRect(float, float, float, float), which
   // is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom,
           Region.Op.INTERSECT
       )
   } else {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

Schritt 4: DragCombiniertClippingExample(Canvas) implementieren

Kombinieren Sie nun die Formen, einen Kreis und ein Rechteck und zeichnen Sie den gewünschten Pfad. So definieren Sie einen Ausschnittbereich.

private fun drawCombinedClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne, rowThree)
   path.rewind()
   path.addCircle(
       clipRectLeft + rectInset + circleRadius,
       clipRectTop + circleRadius + rectInset,
       circleRadius,Path.Direction.CCW
   )
   path.addRect(
       clipRectRight / 2 - circleRadius,
       clipRectTop + circleRadius + rectInset,
       clipRectRight / 2 + circleRadius,
       clipRectBottom - rectInset,Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Schritt 5: DragRoundedRectangleClippingExample(Canvas) implementieren

Als Nächstes fügen Sie ein rundes Rechteck hinzu. Das ist eine häufig verwendete Clipform.

  1. Erstellen Sie auf oberster Ebene eine rechteckige Variable und initialisieren Sie sie. RectF ist eine Klasse, die rechteckige Koordinaten in Gleitkommazahlen enthält.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. Implementiere die Funktion drawRoundedRectangleClippingExample(). Die Funktion addRoundRect() verwendet ein Rechteck, Werte für die x- und y-Werte des Eckenradius und die Richtung, um die Kontur des runden Rechtecks zu winden. Path.Direction gibt an, wie geschlossene Formen (z.B. Rekte und Ovale) ausgerichtet werden, wenn sie einem Pfad hinzugefügt werden. CCW steht für „gegen den Uhrzeigersinn“.
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowThree)
   path.rewind()
   path.addRoundRect(
       rectF,clipRectRight / 4,
       clipRectRight / 4, Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Schritt 6: DragOutsideClippingExample(Canvas) implementieren

Verknüpfe die Außenseite um das Rechteck, indem du die Öffnungen des Zuschneidebereichs verdoppelst.

private fun drawOutsideClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne,rowFour)
   canvas.clipRect(2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Schritt 7: DolmettranslatedTextExample(Canvas) implementieren

Das Zeichnen von Text unterscheidet sich nicht sehr von anderen Formen und Sie können Transformationen auf Text anwenden. Beispielsweise können Sie Text übersetzen, indem Sie den Canvas übersetzen und den Text zeichnen.

  1. Implementieren Sie die Funktion unten.
private fun drawTranslatedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.GREEN
   // Align the RIGHT side of the text with the origin.
   paint.textAlign = Paint.Align.LEFT
   // Apply transformation to canvas.
   canvas.translate(columnTwo,textRow)
   // Draw text.
   canvas.drawText(context.getString(R.string.translated),
       clipRectLeft,clipRectTop,paint)
   canvas.restore()
}
  1. Führen Sie Ihre App aus, um den übersetzten Text zu sehen.

Schritt 8: DragSkewedTextExample(Canvas) implementieren

Sie können auch den Text verzerren. Das heißt, sie werden auf verschiedene Arten verzerrt dargestellt.

  1. Erstellen Sie die Funktion unten in ClippedView.
private fun drawSkewedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.YELLOW
   paint.textAlign = Paint.Align.RIGHT
   // Position text.
   canvas.translate(columnTwo, textRow)
   // Apply skew transformation.
   canvas.skew(0.2f, 0.3f)
   canvas.drawText(context.getString(R.string.skewed),
       clipRectLeft, clipRectTop, paint)
   canvas.restore()
}
  1. Führen Sie Ihre App aus, um den verzerrten Text vor dem übersetzten Text zu sehen.

Mit der Methode quickReject() Canvas können Sie prüfen, ob ein angegebenes Rechteck oder ein angegebener Pfad vollständig außerhalb der aktuell sichtbaren Regionen liegen würde, nachdem alle Transformationen angewendet wurden.

Die Methode quickReject() ist äußerst nützlich, wenn du komplexere Zeichnungen konstruierst und dies so schnell wie möglich erledigen musst. Mit quickReject() können Sie effizient entscheiden, welche Objekte Sie gar nicht zeichnen müssen und keine eigene Schnittlogik schreiben müssen.

  • Bei der Methode quickReject() wird true zurückgegeben, wenn das Rechteck oder der Pfad auf dem Bildschirm nicht sichtbar ist. Bei teilweisen Überschneidungen musst du dies dennoch selbst tun.
  • Das EdgeType ist entweder AA (Antialiased: Kanten werden gerundet, da sie möglicherweise abgerundet werden, oder BW (Schwarz: Kanten werden gerundet, indem sie auf die nächste Pixelgrenze gerundet werden). Nur auf das nächste Pixel gerundet.

Es gibt mehrere Versionen von quickReject(). Sie sind auch in der Dokumentation aufgeführt.

boolean

quickRejected(float left, float top, float right, float bottom, Canvas.EdgeType type)

boolean

quickDeny(RectF rect, Canvas.EdgeType type)

boolean

quickDeny(Pfad path, Canvas.EdgeType type)

Für diese Übung zeichnet ihr wie gewohnt eine neue Zeile unter dem Text und innerhalb des clipRect.

  • Du rufst zuerst quickReject() mit einem Rechteck inClipRectangle auf, das sich mit clipRect überschneidet. quickReject() gibt also „false“ zurück, clipRect wird mit BLACK gefüllt und das Rechteck inClipRectangle wird gezeichnet.

  • Ändere dann den Code und rufe quickReject() mit notInClipRectangle an. quickReject() gibt jetzt „true“ zurück und clipRect ist mit WHITE gefüllt und notInClipRectangle wird nicht gezeichnet.

Wenn Sie komplexe Zeichnungen haben, können Sie schnell erkennen, welche Formen sich vollständig außerhalb des Zuschnittbereichs befinden, und möglicherweise müssen Sie zusätzliche Berechnungen oder Zeichnungen vornehmen, weil sie sich teilweise oder vollständig im Zuschneidebereich befinden.

Schritt: Mit QuickDeny() testen

  1. Erstellen Sie auf oberster Ebene eine Variable für die y-Koordinaten einer zusätzlichen Zeile.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. Füge die folgende drawQuickRejectExample()-Funktion zu ClippedView hinzu. Lesen Sie den Code, denn er enthält alles, was Sie wissen müssen, um quickReject() zu verwenden.
private fun drawQuickRejectExample(canvas: Canvas) {
   val inClipRectangle = RectF(clipRectRight / 2,
       clipRectBottom / 2,
       clipRectRight * 2,
       clipRectBottom * 2)

   val notInClipRectangle = RectF(RectF(clipRectRight+1,
       clipRectBottom+1,
       clipRectRight * 2,
       clipRectBottom * 2))

   canvas.save()
   canvas.translate(columnOne, rejectRow)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
   )
   if (canvas.quickReject(
           inClipRectangle, Canvas.EdgeType.AA)) {
       canvas.drawColor(Color.WHITE)
   }
   else {
       canvas.drawColor(Color.BLACK)
       canvas.drawRect(inClipRectangle, paint
       )
   }
       canvas.restore()
}
  1. Deaktivieren Sie in onDraw() den Aufruf von drawQuickRejectExample().
  2. Führen Sie Ihre App aus. Sie sehen dann ein schwarzes Rechteck, das die ausgefüllte Zuschnittregion und Teile von inClipRectangle sind, da sich die beiden Rechtecke überschneiden. quickReject() gibt also false zurück und inClipRectangle wird gezeichnet.

  1. Ändern Sie in drawQuickRejectExample() den Code so, dass quickReject() für notInClipRectangle. ausgeführt wird. quickReject()In diesem Fall gibt quickReject() true zurück und der Zuschneidebereich ist weiß gefüllt.

Laden Sie den Code für das fertige Codelab herunter.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping


Alternativ können Sie das Repository als ZIP-Datei herunterladen, entpacken und in Android Studio öffnen.

Zip herunterladen

  • Der Context einer Aktivität unterhält einen Zustand, in dem Transformationen und Clips von Regionen für die Canvas beibehalten werden.
  • Mit canvas.save() und canvas.restore() kannst du zeichnen und zum ursprünglichen Zustand deiner Leinwand zurückkehren.
  • Wenn Sie mehrere Formen auf einem Canvas zeichnen möchten, können Sie entweder ihre Position berechnen oder den Ursprung der Zeichnung verschieben (übersetzen). Letztere kann das Erstellen von Hilfsmethoden für wiederholte Zeichensequenzen erleichtern.
  • Bereiche können eine beliebige Form, eine Kombination aus Formen oder Pfaden sein.
  • Sie können Regionen mit Ausschnitten hinzufügen, subtrahieren und überschneiden, um genau die gewünschte Region zu erhalten.
  • Sie können Transformationen auf Text anwenden, indem Sie den Canvas transformieren.
  • Mit der Methode quickReject() Canvas können Sie prüfen, ob ein angegebenes Rechteck oder ein angegebener Pfad vollständig außerhalb der aktuell sichtbaren Regionen liegen würde.

Udacity-Kurs:

Android-Entwicklerdokumentation:

In den Artikeln zur Graphics Architecture finden Sie eine ausführliche Erklärung der Darstellung des Android-Frameworks auf dem Bildschirm.

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler oder Studenten aufgeführt, die an diesem von einem Kursleiter geleiteten Codelab arbeiten. Die Lehrkraft kann Folgendes tun:

  • Bei Bedarf können Sie die entsprechenden Aufgaben zuweisen.
  • Schülern mitteilen, wie sie Aufgaben für die Aufgabe abgeben
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge so oft oder so oft verwenden, wie sie möchten. anderen Aufgaben können sie nach Belieben zugewiesen werden.

Wenn Sie alleine an diesem Codelab arbeiten, können Sie Ihr Wissen mit diesen Hausaufgaben testen.

Diese Fragen beantworten

Frage 1

Mit welcher Methode können Sie Formen effizient davon zeichnen, dass sie gezeichnet werden?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

Frage 2

Welche Informationen werden von Canvas.save() und Canvas.restore() gespeichert und wiederhergestellt?

▢ Farbe, Linienbreite usw.

▢ Nur aktuelle Transformationen

▢ Aktuelle Transformationen und Ausschnittregion

▢ Nur aktuelle Ausschnittregion

Frage 3

Paint.Align gibt Folgendes an:

▢ Bild einer Form ausrichten

▢ Von der Seite des Ursprungs, aus der der Text stammt

▢ wo im Clipbereich ausgerichtet ist

▢ Welche Textseite am Ursprung ausgerichtet ist

Links zu weiteren Codelabs in diesem Kurs finden Sie auf der Landingpage des erweiterten Android-Tools in Kotlin.