Canvas-Objekte zuschneiden

Dieses Codelab ist Teil des Kurses „Advanced Android in Kotlin“. Sie können den größten Nutzen aus diesem Kurs ziehen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Das ist jedoch nicht zwingend erforderlich. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu „Android für Fortgeschrittene mit Kotlin“ aufgeführt.

Einführung

In diesem Codelab ist Clipping eine Möglichkeit, Bereiche eines Bildes, Canvas oder Bitmaps zu definieren, die selektiv auf dem Bildschirm gezeichnet werden oder nicht. Das Clipping dient unter anderem dazu, Overdraw zu reduzieren. Overdraw tritt auf, wenn ein Pixel auf dem Bildschirm mehr als einmal gezeichnet wird, um das endgültige Bild darzustellen. Wenn Sie Overdraw reduzieren, minimieren Sie die Anzahl der Male, die ein Pixel oder ein Bereich des Displays gezeichnet wird, um die Zeichenleistung zu maximieren. Sie können das Clipping auch verwenden, um interessante Effekte im UI-Design und in Animationen zu erzielen.

Wenn Sie beispielsweise einen Stapel überlappender Karten wie unten dargestellt zeichnen, ist es in der Regel effizienter, nur die sichtbaren Bereiche zu zeichnen, anstatt jede Karte von unten nach oben vollständig zu zeichnen. „Normalerweise“, weil auch Clipping-Vorgänge Kosten verursachen und das Android-System insgesamt viele Zeichenoptimierungen vornimmt.

Wenn Sie nur die sichtbaren Teile der Karten zeichnen möchten, geben Sie für jede Karte eine Beschneidungsregion an. Wenn beispielsweise im folgenden Diagramm ein Beschneidungsrechteck auf ein Bild angewendet wird, wird nur der Teil innerhalb dieses Rechtecks angezeigt.

Der Beschneidungsbereich ist in der Regel ein Rechteck, kann aber eine beliebige Form oder Kombination von Formen, sogar Text, sein. Sie können auch angeben, ob die Region innerhalb der Clipping-Region ein- oder ausgeschlossen werden soll. Sie könnten beispielsweise eine kreisförmige Clipping-Region erstellen und nur das anzeigen, was außerhalb des Kreises liegt.

In diesem Codelab experimentieren Sie mit verschiedenen Möglichkeiten zum Zuschneiden.

Was Sie bereits wissen sollten

Sie sollten mit Folgendem vertraut sein:

  • So erstellen Sie eine App mit einem Activity und führen sie mit Android Studio aus.
  • So erstellen Sie eine Canvas und zeichnen darauf.
  • Benutzerdefiniertes View erstellen und onDraw() und onSizeChanged() überschreiben

Lerninhalte

  • Objekte zuschneiden, um auf einer Canvas zu zeichnen
  • So speichern und stellen Sie den Zeichenstatus eines Canvas wieder her.
  • Transformationen auf einen Arbeitsbereich und auf Text anwenden

Aufgaben

  • Erstelle eine App, die zugeschnittene Formen auf dem Bildschirm darstellt und verschiedene Möglichkeiten zum Zuschneiden sowie die Auswirkungen auf die Sichtbarkeit dieser Formen zeigt.
  • Außerdem werden Sie übersetzten und verzerrten Text zeichnen.

Die App ClippingExample zeigt, wie Sie Formen verwenden und kombinieren können, um festzulegen, welche Teile eines Canvas in einer Ansicht angezeigt werden. Ihre fertige App sollte so aussehen wie im Screenshot unten.

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

Schritt 1: Projekt „ClippingExample“ erstellen

  1. Erstellen Sie ein Kotlin-Projekt mit dem Namen ClippingExample mit der Vorlage Empty Activity. Verwenden Sie com.example.android als Präfix für den Paketnamen.
  2. Öffnen Sie MainActivity.kt.
  3. Ersetzen Sie in der Methode onCreate() die Standardinhaltsansicht und legen Sie die Inhaltsansicht auf eine neue Instanz von ClippedView fest. Das ist Ihre benutzerdefinierte Ansicht für die Clipping-Beispiele, die Sie als Nächstes erstellen.
setContentView(ClippedView(this))
  1. Erstellen Sie auf derselben Ebene wie MainActivity.kt eine neue Kotlin-Datei und -Klasse für eine benutzerdefinierte Ansicht mit dem Namen ClippedView, die View erweitert. Geben Sie die unten gezeigte Signatur ein. Der Rest Ihrer Arbeit wird in diesem ClippedView erledigt. Die Annotation @JvmOverloads weist den Kotlin-Compiler an, Überladungen für diese Funktion zu generieren, 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 beschnittenen Ansichten verwenden, in einer neuen Ressourcendatei in res/values/dimens.xml. Diese Standardabmessungen sind fest codiert und für einen relativ kleinen Bildschirm ausgelegt.
<?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 gut aussieht und Details leichter zu erkennen sind, können Sie eine dimens-Datei mit größeren Werten erstellen, die nur für größere Bildschirme gilt.

  1. Klicken Sie in Android Studio mit der rechten Maustaste auf den Ordner values und wählen Sie New > Values resource file aus.
  2. Geben Sie im Dialogfeld Neue Ressourcendatei den Namen dimens für die Datei ein. Wählen Sie unter Verfügbare Qualifizierer die Option Kleinste Bildschirmbreite aus und klicken Sie auf die Schaltfläche >>, um sie zu den Ausgewählten Qualifizierern hinzuzufügen. Geben Sie im Feld Kleinste Bildschirmbreite den Wert 480 ein und klicken Sie auf OK.

  1. Die Datei sollte in Ihrem Ordner „values“ angezeigt werden, wie unten dargestellt.

  1. Wenn Sie die Datei nicht sehen, wechseln Sie zur Ansicht Projektdateien der App. Der vollständige Pfad der neuen Datei ist wie unten dargestellt: ClippingExample/app/src/main/res/values-sw480dp/dimens.xml.

  1. Ersetzen Sie den Standardinhalt der Datei values-sw480dp/dimens.xml durch die unten angegebenen Dimensionen.
<?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. Sie werden verwendet, um Text auf dem Canvas darzustellen.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

Schritt 3: Paint- und Path-Objekt erstellen und initialisieren

  1. Wechseln Sie zurück zur Android-Ansicht Ihres Projekts.
  2. Definieren Sie in ClippedView eine Paint-Variable, die für das Zeichnen verwendet werden soll. Aktivieren Sie das Antialiasing und verwenden Sie die in den Dimensionen definierte Strichstärke und Textgröß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 eine Path, um den Pfad des Gezeichneten lokal zu speichern. Importieren Sie android.graphics.Path.
private val path = Path()

Schritt 4: Formen einrichten

In dieser App werden mehrere Zeilen und zwei Spalten mit Formen angezeigt, die auf verschiedene Arten zugeschnitten sind.

Sie haben alle Folgendes gemeinsam:

  • Ein großes Rechteck (Quadrat), das als Container dient
  • Eine diagonale Linie durch das große Rechteck
  • Ein Kreis
  • Ein kurzer Textstring

In diesem Schritt legen Sie Dimensionen für diese Formen aus Ressourcen fest, sodass Sie die Dimensionen später nur einmal abrufen müssen.

  1. Fügen Sie in ClippedView unter path Variablen für Dimensionen für ein Beschneidungsrechteck um alle 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 den Einzug eines Rechtecks und den Versatz 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. Das ist der Radius des Kreises, der in das Rechteck gezeichnet wird.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. Fügen Sie einen Versatz und eine Textgröße für Text hinzu, 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, die durch die Werte der oben eingerichteten Dimensionen bestimmt werden. Die mathematischen Grundlagen dafür sind nicht Teil dieses Codelabs. Sehen Sie sich den Code in diesem Schritt aber genau an, wenn Sie ihn kopieren.

  1. Koordinaten für zwei Spalten einrichten
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. Fügen Sie die Koordinaten für jede Zeile hinzu, einschließlich der letzten Zeile für den transformierten Text.
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 die App aus. Sie sollte mit einem leeren weißen Bildschirm unter dem Namen der App geöffnet werden.

In onDraw() rufen Sie Methoden auf, um sieben verschiedene zugeschnittene Rechtecke zu zeichnen, wie im App-Screenshot unten zu sehen ist. Die Rechtecke werden alle auf dieselbe Weise gezeichnet. Der einzige Unterschied besteht in den definierten Clipping-Regionen und der Position auf dem Bildschirm.

Der Algorithmus, mit dem die Rechtecke gezeichnet werden, funktioniert wie im Diagramm und in der Beschreibung unten dargestellt. Zusammenfassend lässt sich sagen, dass Sie eine Reihe von Rechtecken zeichnen, indem Sie den Ursprung von Canvas verschieben. Konzeptionell besteht dies aus den folgenden Schritten:

1. Verschieben Sie 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 müssen, verschieben Sie den Canvas-Ursprung, also das Koordinatensystem.

2. Zeichnen Sie das Rechteck am neuen Ursprung des Arbeitsbereichs. Das bedeutet, dass Sie die Formen an derselben Stelle im übersetzten Koordinatensystem zeichnen. Das ist viel einfacher und etwas effizienter.

3. Stellen Sie die Canvas auf die ursprüngliche Origin zurück.

So sieht der Algorithmus aus, den Sie implementieren werden:

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

Für jedes Rechteck oder jeden Text:

  1. Speichern Sie den aktuellen Zustand von Canvas, damit Sie ihn später wiederherstellen können.
  2. Verschieben Sie den Origin des Canvas an die Stelle, an der Sie zeichnen möchten.
  3. Beschneidungsformen und ‑pfade anwenden
  4. Zeichnen Sie das Rechteck oder den Text.
  5. Stellen Sie den Status des Canvas wieder her.

Schritt: onDraw() überschreiben

  1. Überschreiben Sie onDraw() wie im folgenden Code gezeigt. Sie rufen eine Funktion für jede Form auf, die Sie zeichnen, und implementieren diese später.
 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 der Zeichenfunktionen, 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 wird dasselbe Rechteck und dieselben Formen sieben Mal gezeichnet: zuerst ohne Clipping und dann sechs Mal mit verschiedenen Clipping-Pfaden. Die Methode drawClippedRectangle() faktorisiert den Code zum Zeichnen eines Rechtecks, wie unten dargestellt.

Schritt 1: drawClippedRectangle()-Methode erstellen

  1. Erstellen Sie eine drawClippedRectangle()-Methode, die ein Argument canvas vom Typ Canvas akzeptiert.
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. Legen Sie in der Methode drawClippedRectangle() die Grenzen des Beschneidungsrechtecks für die gesamte Form fest. Wenden Sie ein Beschneidungsrechteck an, das die Zeichnung auf das Quadrat beschränkt.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

Mit der Methode Canvas.clipRect(...) wird der Bereich des Bildschirms verkleinert, in den zukünftige Zeichenvorgänge schreiben können. Die Clipping-Grenzen werden auf die räumliche Überschneidung des aktuellen Clipping-Rechtecks und des Rechtecks festgelegt, das an clipRect() übergeben wird. Es gibt viele Varianten der clipRect()-Methode, die unterschiedliche Formen für Regionen akzeptieren und verschiedene Operationen für das Zuschneiderechteck ermöglichen.

  1. Fülle das canvas mit weißer Farbe. Ja! Die gesamte Zeichenfläche, da Sie keine Rechtecke zeichnen, sondern Inhalte ausschneiden. Aufgrund des Beschneidungsrechtecks wird nur der durch das Beschneidungsrechteck definierte Bereich gefüllt, wodurch ein weißes Rechteck entsteht. Der Rest der Oberfläche bleibt grau.
canvas.drawColor(Color.WHITE)
  1. Ändern Sie die Farbe in Rot und zeichnen Sie eine diagonale Linie in das Beschneidungsrechteck.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. Legen Sie die Farbe auf Grün fest und zeichnen Sie einen Kreis in das Beschneidungsrechteck.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. Legen Sie die Farbe auf Blau fest und zeichnen Sie Text, der an der rechten Kante des Beschneidungsrechtecks 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 „drawBackAndUnclippedRectangle()“ implementieren

  1. Um die drawClippedRectangle()-Methode in Aktion zu sehen, zeichnen Sie das erste nicht zugeschnittene Rechteck, indem Sie die drawBackAndUnclippedRectangle()-Methode wie unten gezeigt implementieren. Speichern Sie die canvas, übersetzen Sie sie in die erste Zeilen- und Spaltenposition, zeichnen Sie sie, indem Sie drawClippedRectangle() aufrufen, und stellen Sie dann die canvas in ihrem vorherigen Zustand wieder her.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Führen Sie die App aus. Sie sollten das erste weiße Rechteck mit dem Kreis, der roten Linie und dem Text auf einem grauen Hintergrund sehen.

In den folgenden Beispielen für das Zuschneiden wenden Sie verschiedene Kombinationen von Zuschneidebereichen an, um grafische Effekte zu erzielen. Außerdem erfahren Sie, wie Sie Zuschneidebereiche kombinieren können, um beliebige Formen zu erstellen.

Jede dieser Methoden folgt demselben Muster.

  1. Aktuellen Status der Arbeitsfläche speichern: canvas.save()

Der Aktivitätskontext verwaltet einen Stapel von Zeichenstatus. Zeichnungsstatus bestehen aus der aktuellen Transformationsmatrix und dem aktuellen Zuschneidebereich. Sie können den aktuellen Zustand speichern, Aktionen ausführen, die den Zeichenstatus ändern (z. B. das Verschieben oder Drehen des Arbeitsbereichs), und dann den gespeicherten Zeichenstatus wiederherstellen. Hinweis: Dies entspricht dem Befehl „stash“ in Git.

Wenn Ihre Zeichnung Transformationen enthält, ist das Verketten und Rückgängigmachen von Transformationen durch Umkehren fehleranfällig. Wenn Sie beispielsweise zuerst übersetzen, dann strecken und dann drehen, wird es schnell komplex. Speichern Sie stattdessen den Status des Canvas, wenden Sie die Transformationen an, zeichnen Sie und stellen Sie dann den vorherigen Status wieder her.

Sie können beispielsweise einen Zuschneidebereich definieren und diesen Status speichern. Übersetzen Sie dann den Zeichenbereich, fügen Sie einen Beschneidungsbereich hinzu und drehen Sie ihn. Nachdem Sie etwas gezeichnet haben, können Sie den ursprünglichen Clipping-Zustand wiederherstellen und eine andere Translation und perspektivische Transformation durchführen, wie im Diagramm dargestellt.

  1. Verschieben Sie den Ursprung des Canvas zu den Zeilen-/Spaltenkoordinaten: canvas.translate()

Es ist viel einfacher, den Ursprung des Canvas zu verschieben und dasselbe in einem neuen Koordinatensystem zu zeichnen, als alle zu zeichnenden Elemente zu verschieben. Tipp: Sie können dieselbe Technik auch zum Drehen von Elementen verwenden.

  1. Wenden Sie ggf. Transformationen auf die path an.
  2. Clipping anwenden: canvas.clipPath(path)
  3. Zeichnen Sie die Formen: drawClippedRectangle() or drawText()
  4. Vorherigen Canvas-Zustand wiederherstellen: canvas.restore()

Schritt 1: drawDifferenceClippingExample(canvas) implementieren

Fügen Sie Code hinzu, um das zweite Rechteck zu zeichnen. Dabei wird die Differenz zwischen zwei Clipping-Rechtecken verwendet, um einen Bilderrahmen-Effekt zu erzeugen.

Verwenden Sie den folgenden Code, der Folgendes ausführt:

  1. Speichern Sie das Canvas.
  2. Verschieben Sie den Ursprung des Arbeitsbereichs in den leeren Bereich in der ersten Zeile, zweiten Spalte, rechts neben dem ersten Rechteck.
  3. Zwei Clipping-Rechtecke anwenden Der Operator DIFFERENCE subtrahiert das zweite Rechteck vom ersten.
  1. Rufen Sie die Methode drawClippedRectangle() auf, um den geänderten Canvas zu zeichnen.
  2. Stellen Sie den Canvas-Zustand wieder her.
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 die App aus. Sie sollte so aussehen:

Schritt 2: drawCircularClippingExample(canvas) implementieren

Fügen Sie als Nächstes Code hinzu, um ein Rechteck zu zeichnen, das einen kreisförmigen Clipping-Bereich verwendet, der aus einem kreisförmigen Pfad erstellt wurde. Dadurch wird der Kreis im Wesentlichen entfernt (nicht gezeichnet) und stattdessen der graue Hintergrund angezeigt.

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: drawIntersectionClippingExample(canvas) implementieren

Fügen Sie als Nächstes Code hinzu, um den Schnittpunkt zweier Clipping-Rechtecke in der zweiten Zeile und Spalte zu zeichnen.

Je nach Bildschirmauflösung kann diese Region unterschiedlich aussehen. Experimentieren Sie mit der Dimension smallRectOffset, um die Größe des sichtbaren Bereichs 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: drawCombinedClippingExample(canvas) implementieren

Kombinieren Sie als Nächstes Formen, einen Kreis und ein Rechteck, und zeichnen Sie einen beliebigen Pfad, um einen Clipping-Bereich zu definieren.

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: drawRoundedRectangleClippingExample(canvas) implementieren

Fügen Sie als Nächstes ein abgerundetes Rechteck hinzu, das häufig als Zuschneideform verwendet wird.

  1. Erstellen und initialisieren Sie auf der obersten Ebene eine Rechteckvariable. RectF ist eine Klasse, die Rechteckkoordinaten als Gleitkommazahlen enthält.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. Implementieren Sie die Funktion drawRoundedRectangleClippingExample(). Die Funktion addRoundRect() verwendet ein Rechteck, Werte für die x- und y-Werte des Eckenradius und die Richtung, in der die Kontur des abgerundeten Rechtecks verlaufen soll. Mit Path.Direction wird angegeben, wie geschlossene Formen (z.B. Rechtecke, 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: drawOutsideClippingExample(canvas) implementieren

Schneiden Sie den Bereich außerhalb des Rechtecks zu, indem Sie die Insets des Zuschneiderechtecks verdoppeln.

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: drawTranslatedTextExample(canvas) implementieren

Das Zeichnen von Text unterscheidet sich nicht wirklich von anderen Formen und Sie können Transformationen auf Text anwenden. Sie können beispielsweise Text übersetzen, indem Sie den Zeichenbereich ü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: drawSkewedTextExample(canvas) implementieren

Sie können auch Text verzerren. Das heißt, es wird auf verschiedene Weise verzerrt.

  1. Erstellen Sie die folgende Funktion 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 die 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 nach Anwendung aller Transformationen vollständig außerhalb der aktuell sichtbaren Bereiche liegt.

Die quickReject()-Methode ist sehr nützlich, wenn Sie komplexere Zeichnungen erstellen und dies so schnell wie möglich tun müssen. Mit quickReject() können Sie effizient entscheiden, welche Objekte Sie überhaupt nicht zeichnen müssen. Außerdem müssen Sie keine eigene Schnittmengenlogik schreiben.

  • Die Methode quickReject() gibt true zurück, wenn das Rechteck oder der Pfad auf dem Bildschirm nicht sichtbar wäre. Bei teilweisen Überschneidungen müssen Sie weiterhin selbst prüfen.
  • Der EdgeType-Wert ist entweder AA (Antialiased: Kanten werden durch Abrunden behandelt, da sie möglicherweise Antialiasing aufweisen) oder BW (Black-White: Kanten werden durch einfaches Runden auf die nächste Pixelgrenze behandelt).

Es gibt mehrere Versionen von quickReject(), die Sie auch in der Dokumentation finden.

boolean

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

boolean

quickReject(RectF rect, Canvas.EdgeType type)

boolean

quickReject(Path path, Canvas.EdgeType type)

In dieser Übung zeichnen Sie wie zuvor eine neue Zeile unter den Text und innerhalb des clipRect.

  • Zuerst rufen Sie 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.

  • Ändern Sie dann den Code und rufen Sie quickReject() mit notInClipRectangle auf. quickReject() gibt jetzt „true“ zurück, clipRect wird mit WHITE gefüllt und notInClipRectangle wird nicht gezeichnet.

Bei komplexen Zeichnungen können Sie so schnell erkennen, welche Formen sich vollständig außerhalb des Clipping-Bereichs befinden und für welche zusätzliche Berechnungen und Zeichnungen erforderlich sind, da sie sich teilweise oder vollständig innerhalb des Clipping-Bereichs befinden.

Schritt: quickReject() testen

  1. Erstellen Sie auf der obersten Ebene eine Variable für die y-Koordinaten einer zusätzlichen Zeile.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. Fügen Sie ClippedView die folgende Funktion drawQuickRejectExample() hinzu. Lesen Sie den Code, da er alle Informationen enthält, die Sie für die Verwendung von quickReject() benötigen.
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. Entfernen Sie in onDraw() die Auskommentierung des Aufrufs von drawQuickRejectExample().
  2. Wenn Sie die App ausführen, sehen Sie ein schwarzes Rechteck, das den gefüllten Clipping-Bereich darstellt, und Teile von inClipRectangle, da sich die beiden Rechtecke überlappen. 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() gibt jetzt true zurück und der Clipping-Bereich wird 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, entzippen und in Android Studio öffnen.

Zip herunterladen

  • Der Context einer Aktivität behält einen Status bei, der Transformationen und Clipping-Bereiche für den Canvas beibehält.
  • Mit canvas.save() und canvas.restore() können Sie zeichnen und zum ursprünglichen Zustand des Arbeitsbereichs zurückkehren.
  • Wenn Sie mehrere Formen auf einem Canvas zeichnen möchten, können Sie entweder ihre Position berechnen oder den Ursprung der Zeichenfläche verschieben (translieren). Letzteres kann es erleichtern, Hilfsmethoden für wiederholte Zeichensequenzen zu erstellen.
  • Beschneidungsbereiche können beliebige Formen, Kombinationen von Formen oder Pfade sein.
  • Sie können Clipping-Regionen hinzufügen, subtrahieren und schneiden, um genau die Region zu erhalten, die Sie benötigen.
  • Sie können Transformationen auf Text anwenden, indem Sie die Arbeitsfläche 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 liegt.

Udacity-Kurs:

Android-Entwicklerdokumentation:

In der Artikelreihe Grafikarchitektur wird ausführlich beschrieben, wie das Android-Framework auf dem Bildschirm zeichnet.

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:

  • Weisen Sie bei Bedarf Aufgaben zu.
  • Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.

Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.

Beantworten Sie diese Fragen

Frage 1

Welche Methode rufen Sie auf, um Formen effizient vom Zeichnen auszuschließen?

▢ excludeFromDrawing()

▢ quickReject()

▢ onDraw()

▢ clipRect()

Frage 2

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

▢ Farbe, Linienstärke usw.

▢ Nur aktuelle Transformationen

▢ Aktuelle Transformationen und Clipping-Region

▢ Nur aktueller Zuschneidebereich

Frage 3

Paint.Align gibt Folgendes an:

▢ So richten Sie die folgenden Zeichenformen aus

▢ Von welcher Seite des Ursprungs der Text stammt

▢ Wo in der Beschneidungsregion es ausgerichtet ist

▢ Welche Seite des Texts soll am Ursprung ausgerichtet werden?

Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Codelabs zum Thema „Android für Fortgeschrittene mit Kotlin“.