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
Activityund führen sie mit Android Studio aus. - So erstellen Sie eine
Canvasund zeichnen darauf. - Benutzerdefiniertes
Viewerstellen undonDraw()undonSizeChanged()überschreiben
Lerninhalte
- Objekte zuschneiden, um auf einer
Canvaszu 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
- Erstellen Sie ein Kotlin-Projekt mit dem Namen
ClippingExamplemit der Vorlage Empty Activity. Verwenden Siecom.example.androidals Präfix für den Paketnamen. - Öffnen Sie
MainActivity.kt. - Ersetzen Sie in der Methode
onCreate()die Standardinhaltsansicht und legen Sie die Inhaltsansicht auf eine neue Instanz vonClippedViewfest. Das ist Ihre benutzerdefinierte Ansicht für die Clipping-Beispiele, die Sie als Nächstes erstellen.
setContentView(ClippedView(this))- Erstellen Sie auf derselben Ebene wie
MainActivity.kteine neue Kotlin-Datei und -Klasse für eine benutzerdefinierte Ansicht mit dem NamenClippedView, dieViewerweitert. Geben Sie die unten gezeigte Signatur ein. Der Rest Ihrer Arbeit wird in diesemClippedViewerledigt. Die Annotation@JvmOverloadsweist 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
- 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.
- Klicken Sie in Android Studio mit der rechten Maustaste auf den Ordner values und wählen Sie New > Values resource file aus.
- Geben Sie im Dialogfeld Neue Ressourcendatei den Namen
dimensfü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.

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

- 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.

- Ersetzen Sie den Standardinhalt der Datei
values-sw480dp/dimens.xmldurch 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>- Fügen Sie in
strings.xmldie 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
- Wechseln Sie zurück zur Android-Ansicht Ihres Projekts.
- Definieren Sie in
ClippedVieweinePaint-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)
}- Erstellen und initialisieren Sie in
ClippedVieweinePath, um den Pfad des Gezeichneten lokal zu speichern. Importieren Sieandroid.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.
- Fügen Sie in
ClippedViewunterpathVariablen 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)- 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)- 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)- 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.
- Koordinaten für zwei Spalten einrichten
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight- 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)- 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:
- Rufen Sie in
onDraw()eine Funktion auf, umCanvasmit der grauen Hintergrundfarbe zu füllen und die ursprünglichen Formen zu zeichnen. - Rufen Sie eine Funktion für jedes ausgeschnittene Rechteck und den zu zeichnenden Text auf.
Für jedes Rechteck oder jeden Text:
- Speichern Sie den aktuellen Zustand von
Canvas, damit Sie ihn später wiederherstellen können. - Verschieben Sie den
Origindes Canvas an die Stelle, an der Sie zeichnen möchten. - Beschneidungsformen und ‑pfade anwenden
- Zeichnen Sie das Rechteck oder den Text.
- Stellen Sie den Status des
Canvaswieder her.
Schritt: onDraw() überschreiben
- Ü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)
}- 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
- Erstellen Sie eine
drawClippedRectangle()-Methode, die ein Argumentcanvasvom TypCanvasakzeptiert.
private fun drawClippedRectangle(canvas: Canvas) {
}- 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.
- Fülle das
canvasmit 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)- Ä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
)- 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
)- 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
- Um die
drawClippedRectangle()-Methode in Aktion zu sehen, zeichnen Sie das erste nicht zugeschnittene Rechteck, indem Sie diedrawBackAndUnclippedRectangle()-Methode wie unten gezeigt implementieren. Speichern Sie diecanvas, übersetzen Sie sie in die erste Zeilen- und Spaltenposition, zeichnen Sie sie, indem SiedrawClippedRectangle()aufrufen, und stellen Sie dann diecanvasin ihrem vorherigen Zustand wieder her.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
canvas.drawColor(Color.GRAY)
canvas.save()
canvas.translate(columnOne,rowOne)
drawClippedRectangle(canvas)
canvas.restore()
}- 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.
- 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.

- 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.
- Wenden Sie ggf. Transformationen auf die
pathan. - Clipping anwenden:
canvas.clipPath(path) - Zeichnen Sie die Formen:
drawClippedRectangle() or drawText() - 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:
- Speichern Sie das Canvas.
- Verschieben Sie den Ursprung des Arbeitsbereichs in den leeren Bereich in der ersten Zeile, zweiten Spalte, rechts neben dem ersten Rechteck.
- Zwei Clipping-Rechtecke anwenden Der Operator
DIFFERENCEsubtrahiert das zweite Rechteck vom ersten.
- Rufen Sie die Methode
drawClippedRectangle()auf, um den geänderten Canvas zu zeichnen. - 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()
}- 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.

- Erstellen und initialisieren Sie auf der obersten Ebene eine Rechteckvariable.
RectFist eine Klasse, die Rechteckkoordinaten als Gleitkommazahlen enthält.
private var rectF = RectF(
rectInset,
rectInset,
clipRectRight - rectInset,
clipRectBottom - rectInset
)- Implementieren Sie die Funktion
drawRoundedRectangleClippingExample(). Die FunktionaddRoundRect()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. MitPath.Directionwird angegeben, wie geschlossene Formen (z.B. Rechtecke, Ovale) ausgerichtet werden, wenn sie einem Pfad hinzugefügt werden.CCWsteht 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.

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

- 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()
}- 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()gibttruezurü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 entwederAA(Antialiased: Kanten werden durch Abrunden behandelt, da sie möglicherweise Antialiasing aufweisen) oderBW(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.
| quickReject |
| quickReject |
| quickReject |
In dieser Übung zeichnen Sie wie zuvor eine neue Zeile unter den Text und innerhalb des clipRect.
- Zuerst rufen Sie
quickReject()mit einem RechteckinClipRectangleauf, das sich mitclipRectüberschneidet.quickReject()gibt also „false“ zurück,clipRectwird mitBLACKgefüllt und das RechteckinClipRectanglewird gezeichnet.

- Ändern Sie dann den Code und rufen Sie
quickReject()mitnotInClipRectangleauf.quickReject()gibt jetzt „true“ zurück,clipRectwird mitWHITEgefüllt undnotInClipRectanglewird 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
- Erstellen Sie auf der obersten Ebene eine Variable für die y-Koordinaten einer zusätzlichen Zeile.
private val rejectRow = rowFour + rectInset + 2*clipRectBottom- Fügen Sie
ClippedViewdie folgende FunktiondrawQuickRejectExample()hinzu. Lesen Sie den Code, da er alle Informationen enthält, die Sie für die Verwendung vonquickReject()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()
}- Entfernen Sie in
onDraw()die Auskommentierung des Aufrufs vondrawQuickRejectExample(). - 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 alsofalsezurück undinClipRectanglewird gezeichnet.

- Ändern Sie in
drawQuickRejectExample()den Code so, dassquickReject()fürnotInClipRectangle.ausgeführt wird.quickReject()gibt jetzttruezurü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.
- Der
Contexteiner Aktivität behält einen Status bei, der Transformationen und Clipping-Bereiche für denCanvasbeibehält. - Mit
canvas.save()undcanvas.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()Canvaskö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:
Canvas-KlasseBitmap-KlasseView-KlassePaint-KlasseBitmap.config-KonfigurationenRegion.Op-OperatorenPath-KlasseCanvas-KlasseBitmap-KlasseView-KlassePaint-KlasseBitmap.config-KonfigurationenRegion.Op-OperatorenPath-Klasseandroid.graphicsGrafiktoolsBitmap.ConfigCanvas-Konfigurationen- Canvas und Drawables
- Was macht canvas.translate()?
- save() und restore() für den Canvas-Kontext
- Clipping
- Überziehung
@JvmOverloads
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“.