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
Android bietet eine Vielzahl von View-Unterklassen, z. B. Button, TextView, EditText, ImageView, CheckBox oder RadioButton. Mit diesen Unterklassen können Sie eine Benutzeroberfläche erstellen, die Nutzerinteraktionen ermöglicht und Informationen in Ihrer App anzeigt. Wenn keine der View-Unterklassen Ihren Anforderungen entspricht, können Sie eine View-Unterklasse erstellen, die als benutzerdefinierte Ansicht bezeichnet wird.
Wenn Sie eine benutzerdefinierte Ansicht erstellen möchten, können Sie entweder eine vorhandene View-Unterklasse (z. B. Button oder EditText) erweitern oder eine eigene Unterklasse von View erstellen. Wenn Sie View direkt erweitern, können Sie ein interaktives UI-Element beliebiger Größe und Form erstellen, indem Sie die Methode onDraw() für View überschreiben, um es zu zeichnen.
Nachdem Sie eine benutzerdefinierte Ansicht erstellt haben, können Sie sie Ihren Aktivitätslayouts auf dieselbe Weise hinzufügen wie ein TextView- oder Button-Element.
In dieser Lektion erfahren Sie, wie Sie eine benutzerdefinierte Ansicht von Grund auf neu erstellen, indem Sie View erweitern.
Was Sie bereits wissen sollten
- So erstellen Sie eine App mit einer Aktivität und führen sie mit Android Studio aus.
Lerninhalte
Viewerweitern, um eine benutzerdefinierte Ansicht zu erstellen- So zeichnen Sie eine benutzerdefinierte Ansicht, die kreisförmig ist.
- So verwenden Sie Listener, um Nutzerinteraktionen mit der benutzerdefinierten Ansicht zu verarbeiten.
- Benutzerdefinierte Ansicht in einem Layout verwenden
Aufgaben
- Erweitern Sie
View, um eine benutzerdefinierte Ansicht zu erstellen. - Initialisieren Sie die benutzerdefinierte Ansicht mit Zeichen- und Malwerten.
- Überschreiben Sie
onDraw(), um die Ansicht zu zeichnen. - Verwenden Sie Listener, um das Verhalten der benutzerdefinierten Ansicht zu definieren.
- Fügen Sie die benutzerdefinierte Ansicht einem Layout hinzu.
Die App CustomFanController zeigt, wie eine benutzerdefinierte Ansicht-Unterklasse durch Erweitern der Klasse View erstellt wird. Die neue Unterklasse heißt DialView.
In der App wird ein kreisförmiges UI-Element angezeigt, das einer physischen Lüftersteuerung ähnelt, mit Einstellungen für „Aus“ (0), „Niedrig“ (1), „Mittel“ (2) und „Hoch“ (3). Wenn der Nutzer auf die Ansicht tippt, bewegt sich die Auswahlmarkierung zur nächsten Position: 0–1–2–3 und zurück zu 0. Wenn die Auswahl 1 oder höher ist, ändert sich die Hintergrundfarbe des kreisförmigen Teils der Ansicht von Grau zu Grün (was darauf hinweist, dass die Lüfterleistung aktiviert ist).


Ansichten sind die grundlegenden Bausteine der Benutzeroberfläche einer App. Die Klasse View bietet viele Unterklassen, die als UI-Widgets bezeichnet werden und viele Anforderungen der Benutzeroberfläche einer typischen Android-App abdecken.
UI-Bausteine wie Button und TextView sind abgeleitete Klassen, die die Klasse View erweitern. Um Zeit und Entwicklungsaufwand zu sparen, können Sie eine dieser View-Unterklassen erweitern. Die benutzerdefinierte Ansicht erbt das Aussehen und Verhalten des übergeordneten Elements. Sie können das Verhalten oder das Aussehen überschreiben, das Sie ändern möchten. Wenn Sie beispielsweise EditText erweitern, um eine benutzerdefinierte Ansicht zu erstellen, verhält sich die Ansicht wie eine EditText-Ansicht, kann aber auch so angepasst werden, dass beispielsweise eine X-Schaltfläche angezeigt wird, mit der Text aus dem Texteingabefeld gelöscht wird.
Sie können jede View-Unterklasse wie EditText erweitern, um eine benutzerdefinierte Ansicht zu erhalten. Wählen Sie diejenige aus, die Ihren Anforderungen am nächsten kommt. Sie können die benutzerdefinierte Ansicht dann wie jede andere View-Unterklasse in einem oder mehreren Layouts als XML-Element mit Attributen verwenden.
Wenn Sie eine eigene benutzerdefinierte Ansicht von Grund auf erstellen möchten, erweitern Sie die Klasse View. In Ihrem Code werden View-Methoden überschrieben, um das Erscheinungsbild und die Funktionen der Ansicht zu definieren. Der Schlüssel zum Erstellen einer eigenen benutzerdefinierten Ansicht besteht darin, dass Sie dafür verantwortlich sind, das gesamte UI-Element in beliebiger Größe und Form auf dem Bildschirm zu zeichnen. Wenn Sie eine vorhandene Ansicht wie Button unterteilen, übernimmt diese Klasse das Zeichnen für Sie. (Mehr zum Zeichnen erfahren Sie später in diesem Codelab.)
So erstellen Sie eine benutzerdefinierte Ansicht:
- Erstellen Sie eine benutzerdefinierte Ansichtsklasse, die
Viewoder eineView-Unterklasse (z. B.ButtonoderEditText) erweitert. - Wenn Sie eine vorhandene
View-Unterklasse erweitern, überschreiben Sie nur das Verhalten oder die Aspekte der Darstellung, die Sie ändern möchten. - Wenn Sie die Klasse
Viewerweitern, zeichnen Sie die Form der benutzerdefinierten Ansicht und steuern Sie ihre Darstellung, indem SieView-Methoden wieonDraw()undonMeasure()in der neuen Klasse überschreiben. - Fügen Sie Code hinzu, um auf Nutzerinteraktionen zu reagieren und die benutzerdefinierte Ansicht bei Bedarf neu zu zeichnen.
- Verwenden Sie die benutzerdefinierte Ansichtsklasse als UI-Widget im XML-Layout Ihrer Aktivität. Sie können auch benutzerdefinierte Attribute für die Ansicht definieren, um sie in verschiedenen Layouts anzupassen.
In dieser Aufgabe werden Sie:
- Erstellen Sie eine App mit einem
ImageViewals temporären Platzhalter für die benutzerdefinierte Ansicht. - Erweitern Sie
View, um die benutzerdefinierte Ansicht zu erstellen. - Initialisieren Sie die benutzerdefinierte Ansicht mit Zeichen- und Malwerten.
Schritt 1: App mit einem ImageView-Platzhalter erstellen
- Erstellen Sie eine Kotlin-App mit dem Titel
CustomFanControllermithilfe der Vorlage „Empty Activity“. Achten Sie darauf, dass der Paketnamecom.example.android.customfancontrollerist. - Öffnen Sie
activity_main.xmlauf dem Tab Text, um den XML-Code zu bearbeiten. - Ersetzen Sie den vorhandenen
TextViewdurch diesen Code. Dieser Text dient als Label in der Aktivität für die benutzerdefinierte Ansicht.
<TextView
android:id="@+id/customViewLabel"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:textColor="@android:color/black"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="24dp"
android:text="Fan Control"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>- Fügen Sie dem Layout dieses
ImageView-Element hinzu. Dies ist ein Platzhalter für die benutzerdefinierte Ansicht, die Sie in diesem Codelab erstellen.
<ImageView
android:id="@+id/dialView"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/darker_gray"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"/>- Extrahieren Sie String- und Dimensionsressourcen in beiden UI-Elementen.
- Klicken Sie auf den Tab Design. Das Layout sollte so aussehen:

Schritt 2: Benutzerdefinierte Ansichtsklasse erstellen
- Erstellen Sie eine neue Kotlin-Klasse mit dem Namen
DialView. - Ändern Sie die Klassendefinition, um
Viewzu erweitern. Importieren Sieandroid.view.View, wenn Sie dazu aufgefordert werden. - Klicken Sie auf
Viewund dann auf die rote Glühbirne. Wählen Sie Add Android View constructors using '@JvmOverloads' (Android-View-Konstruktoren mit „@JvmOverloads“ hinzufügen) aus. In Android Studio wird der Konstruktor aus der KlasseViewhinzugefügt. Die Annotation@JvmOverloadsweist den Kotlin-Compiler an, Überladungen für diese Funktion zu generieren, die Standardparameterwerte ersetzen.
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {- Fügen Sie über der
DialView-Klassendefinition, direkt unter den Importen, eineenum-Klasse auf oberster Ebene hinzu, um die verfügbaren Lüftergeschwindigkeiten darzustellen. Beachten Sie, dassenumvom TypIntist, da die Werte Stringressourcen und keine tatsächlichen Strings sind. Android Studio zeigt für die fehlenden String-Ressourcen in jedem dieser Werte Fehler an. Diese beheben Sie in einem späteren Schritt.
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
}- Fügen Sie unter
enumdie folgenden Konstanten hinzu. Sie werden für das Zeichnen der Messinstrumente und Labels verwendet.
private const val RADIUS_OFFSET_LABEL = 30
private const val RADIUS_OFFSET_INDICATOR = -35- Definieren Sie in der Klasse
DialViewmehrere Variablen, die Sie zum Zeichnen der benutzerdefinierten Ansicht benötigen. Importieren Sieandroid.graphics.PointF, falls Sie dazu aufgefordert werden.
private var radius = 0.0f // Radius of the circle.
private var fanSpeed = FanSpeed.OFF // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)- Die
radiusist der aktuelle Radius des Kreises. Dieser Wert wird festgelegt, wenn die Ansicht auf dem Bildschirm gerendert wird. - Die
fanSpeedist die aktuelle Lüftergeschwindigkeit, die einer der Werte in der AufzählungFanSpeedist. Der Standardwert istOFF. - Schließlich
postPositionist ein X,Y-Punkt, der zum Zeichnen mehrerer Elemente der Ansicht auf dem Bildschirm verwendet wird.
Diese Werte werden hier erstellt und initialisiert, anstatt wenn die Ansicht tatsächlich gezeichnet wird, damit der eigentliche Zeichenvorgang so schnell wie möglich abläuft.
- Initialisieren Sie außerdem in der
DialView-Klassendefinition einPaint-Objekt mit einigen grundlegenden Stilen. Importieren Sieandroid.graphics.Paintundandroid.graphics.Typeface, wenn Sie dazu aufgefordert werden. Wie bei den Variablen werden diese Stile hier initialisiert, um den Zeichenvorgang zu beschleunigen.
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create( "", Typeface.BOLD)
}- Öffnen Sie
res/values/strings.xmlund fügen Sie die String-Ressourcen für die Lüftergeschwindigkeiten hinzu:
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>Nachdem Sie eine benutzerdefinierte Ansicht erstellt haben, müssen Sie sie zeichnen können. Wenn Sie eine View-Unterklasse wie EditText erweitern, definiert diese Unterklasse das Erscheinungsbild und die Attribute der Ansicht und zeichnet sich selbst auf dem Bildschirm. Daher müssen Sie keinen Code schreiben, um die Ansicht zu zeichnen. Stattdessen können Sie Methoden des übergeordneten Elements überschreiben, um die Ansicht anzupassen.
Wenn Sie eine eigene Ansicht von Grund auf erstellen (durch Erweitern von View), sind Sie dafür verantwortlich, die gesamte Ansicht bei jeder Aktualisierung des Displays zu zeichnen und die View-Methoden zu überschreiben, die das Zeichnen verarbeiten. Damit eine benutzerdefinierte Ansicht, die View erweitert, richtig gezeichnet wird, müssen Sie Folgendes tun:
- Berechnen Sie die Größe der Ansicht, wenn sie zum ersten Mal angezeigt wird, und jedes Mal, wenn sich die Größe der Ansicht ändert, indem Sie die Methode
onSizeChanged()überschreiben. - Überschreiben Sie die Methode
onDraw(), um die benutzerdefinierte Ansicht mit einemCanvas-Objekt zu zeichnen, das mit einemPaint-Objekt formatiert wird. - Rufen Sie die Methode
invalidate()auf, wenn Sie auf einen Nutzerklick reagieren, der die Darstellung der Ansicht ändert, um die gesamte Ansicht ungültig zu machen und so einen Aufruf vononDraw()zu erzwingen, um die Ansicht neu zu zeichnen.
Die onDraw()-Methode wird jedes Mal aufgerufen, wenn der Bildschirm aktualisiert wird. Das kann mehrmals pro Sekunde geschehen. Aus Leistungsgründen und zur Vermeidung visueller Fehler sollten Sie in onDraw() so wenig wie möglich tun. Platzieren Sie Zuweisungen insbesondere nicht in onDraw(), da sie zu einer Garbage Collection führen können, die ein visuelles Ruckeln verursacht.
Die Klassen Canvas und Paint bieten eine Reihe nützlicher Zeichenkürzel:
- Text mit
drawText()zeichnen Geben Sie die Schriftart mitsetTypeface()und die Textfarbe mitsetColor()an. - Zeichnen Sie einfache Formen mit
drawRect(),drawOval()unddrawArc(). MitsetStyle()können Sie festlegen, ob die Formen gefüllt, umrandet oder beides sein sollen. - Bitmaps mit
drawBitmap()zeichnen
Weitere Informationen zu Canvas und Paint finden Sie in einem späteren Codelab. Weitere Informationen dazu, wie Android Ansichten rendert
In dieser Aufgabe zeichnen Sie die benutzerdefinierte Ansicht des Lüftercontrollers auf den Bildschirm – das Zifferblatt selbst, die Anzeige der aktuellen Position und die Anzeigelabels – mit den Methoden onSizeChanged() und onDraw(). Außerdem erstellen Sie eine Hilfsmethode, computeXYForSpeed(),,um die aktuelle X- und Y-Position des Indikatorlabels auf dem Zifferblatt zu berechnen.
Schritt 1: Positionen berechnen und Ansicht zeichnen
- Überschreiben Sie in der Klasse
DialViewunter den Initialisierungen die MethodeonSizeChanged()aus der KlasseView, um die Größe für das Zifferblatt der benutzerdefinierten Ansicht zu berechnen. Importieren Siekotlin.math.min, wenn Sie dazu aufgefordert werden.
Die MethodeonSizeChanged()wird immer dann aufgerufen, wenn sich die Größe der Ansicht ändert, einschließlich des ersten Zeichnens, wenn das Layout aufgebläht wird. Überschreiben SieonSizeChanged(), um Positionen, Dimensionen und alle anderen Werte zu berechnen, die sich auf die Größe Ihrer benutzerdefinierten Ansicht beziehen, anstatt sie bei jedem Zeichnen neu zu berechnen. In diesem Fall verwenden SieonSizeChanged(), um den aktuellen Radius des Kreiselements des Zifferblatts zu berechnen.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}- Fügen Sie unter
onSizeChanged()diesen Code hinzu, um einecomputeXYForSpeed()-Erweiterungsfunktion für die KlassePointFzu definieren. Importieren Siekotlin.math.cosundkotlin.math.sin, wenn Sie dazu aufgefordert werden. Diese Erweiterungsfunktion für die KlassePointFberechnet die X- und Y-Koordinaten auf dem Bildschirm für das Textlabel und den aktuellen Indikator (0, 1, 2 oder 3) anhand der aktuellenFanSpeed-Position und des Radius des Zifferblatts. Sie verwenden diese Funktion inonDraw()..
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
// Angles are in radians.
val startAngle = Math.PI * (9 / 8.0)
val angle = startAngle + pos.ordinal * (Math.PI / 4)
x = (radius * cos(angle)).toFloat() + width / 2
y = (radius * sin(angle)).toFloat() + height / 2
}- Überschreiben Sie die Methode
onDraw(), um die Ansicht mit den KlassenCanvasundPaintauf dem Bildschirm zu rendern. Importieren Sieandroid.graphics.Canvas, wenn Sie dazu aufgefordert werden. Das ist die Skelettüberschreibung:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}- Fügen Sie in
onDraw()diese Zeile hinzu, um die Farbe auf Grau (Color.GRAY) oder Grün (Color.GREEN) festzulegen, je nachdem, ob die LüftergeschwindigkeitOFFoder ein anderer Wert ist. Importieren Sieandroid.graphics.Color, wenn Sie dazu aufgefordert werden.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN- Fügen Sie diesen Code hinzu, um mit der Methode
drawCircle()einen Kreis für das Zifferblatt zu zeichnen. Bei dieser Methode werden die aktuelle Breite und Höhe der Ansicht verwendet, um den Mittelpunkt und den Radius des Kreises sowie die aktuelle Malfarbe zu ermitteln. Die Attributewidthundheightgehören zur SuperklasseViewund geben die aktuellen Dimensionen der Ansicht an.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)- Fügen Sie den folgenden Code hinzu, um mit der Methode
drawCircle()einen kleineren Kreis für die Markierung der Lüftergeschwindigkeit zu zeichnen. In diesem Teil wirdPointFverwendet.Die ErweiterungsmethodecomputeXYforSpeed()berechnet die X- und Y-Koordinaten für den Mittelpunkt des Indikators basierend auf der aktuellen Lüftergeschwindigkeit.
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)- Zeichne schließlich die Labels für die Lüftergeschwindigkeit (0, 1, 2, 3) an den entsprechenden Positionen um das Zifferblatt herum. In diesem Teil der Methode wird
PointF.computeXYForSpeed()noch einmal aufgerufen, um die Position für jedes Label abzurufen. DaspointPosition-Objekt wird jedes Mal wiederverwendet, um Zuweisungen zu vermeiden. Verwenden SiedrawText(), um die Labels zu zeichnen.
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}Die fertige onDraw()-Methode sieht so aus:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
}Schritt 2: Ansicht zum Layout hinzufügen
Wenn Sie der Benutzeroberfläche einer App eine benutzerdefinierte Ansicht hinzufügen möchten, geben Sie sie als Element im XML-Layout der Aktivität an. Sie können das Aussehen und Verhalten mit XML-Elementattributen steuern, wie bei jedem anderen UI-Element.
- Ändern Sie in
activity_main.xmldasImageView-Tag fürdialViewincom.example.android.customfancontroller.DialViewund löschen Sie das Attributandroid:background. SowohlDialViewals auch das ursprünglicheImageViewübernehmen die Standardattribute aus der KlasseView. Daher müssen Sie keine der anderen Attribute ändern. Das neueDialView-Element sieht so aus:
<com.example.android.customfancontroller.DialView
android:id="@+id/dialView"
android:layout_width="@dimen/fan_dimen"
android:layout_height="@dimen/fan_dimen"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin" />- Führe die App aus. Die Ansicht zur Lüftersteuerung wird in der Aktivität angezeigt.

Die letzte Aufgabe besteht darin, Ihrer benutzerdefinierten Ansicht zu ermöglichen, eine Aktion auszuführen, wenn der Nutzer auf die Ansicht tippt. Bei jedem Tippen sollte der Auswahlindikator zur nächsten Position wechseln: Aus – 1 – 2 – 3 und zurück zu Aus. Wenn die Auswahl auf 1 oder höher steht, sollte sich der Hintergrund von Grau zu Grün ändern, um anzuzeigen, dass die Lüfterleistung aktiviert ist.
Damit Ihre benutzerdefinierte Ansicht anklickbar ist, müssen Sie:
- Legen Sie das Attribut
isClickableder Ansicht auftruefest. So kann Ihre benutzerdefinierte Ansicht auf Klicks reagieren. - Implementieren Sie die
performClick()-Methode derView-Klasse, um Vorgänge auszuführen, wenn auf die Ansicht geklickt wird. - Rufen Sie die Methode
invalidate()auf. Dadurch wird das Android-System angewiesen, die MethodeonDraw()aufzurufen, um die Ansicht neu zu zeichnen.
Normalerweise implementieren Sie bei einer Standard-Android-Ansicht OnClickListener(), um eine Aktion auszuführen, wenn der Nutzer auf diese Ansicht klickt. Bei einer benutzerdefinierten Ansicht implementieren Sie stattdessen die Methode performClick() der Klasse View und rufen super auf.performClick(). Die Standardmethode performClick() ruft auch onClickListener() auf. Sie können Ihre Aktionen also performClick() hinzufügen und onClickListener() für weitere Anpassungen durch Sie oder andere Entwickler, die Ihre benutzerdefinierte Ansicht verwenden, verfügbar lassen.
- Fügen Sie in
DialView.ktin der AufzählungFanSpeedeine Erweiterungsfunktionnext()hinzu, die die aktuelle Lüftergeschwindigkeit in die nächste Geschwindigkeit in der Liste ändert (vonOFFzuLOW,MEDIUMundHIGHund dann zurück zuOFF). Die vollständige Aufzählung sieht jetzt so aus:
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
fun next() = when (this) {
OFF -> LOW
LOW -> MEDIUM
MEDIUM -> HIGH
HIGH -> OFF
}
}- Fügen Sie in der Klasse
DialViewdirekt vor der MethodeonSizeChanged()eineninit()-Block ein. Wenn die EigenschaftisClickableder Ansicht auf „true“ gesetzt wird, kann die Ansicht Nutzereingaben akzeptieren.
init {
isClickable = true
}- Überschreiben Sie unter
init(),die MethodeperformClick()mit dem folgenden Code.
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
contentDescription = resources.getString(fanSpeed.label)
invalidate()
return true
}Der Anruf bei super.performClick() muss zuerst erfolgen, wodurch Bedienungshilfe-Ereignisse sowie Aufrufe von onClickListener() aktiviert werden.
In den nächsten beiden Zeilen wird die Geschwindigkeit des Ventilators mit der Methode next() erhöht und die Inhaltsbeschreibung der Ansicht auf die String-Ressource für die aktuelle Geschwindigkeit (Aus, 1, 2 oder 3) festgelegt.
Schließlich wird mit der Methode invalidate() die gesamte Ansicht ungültig gemacht, sodass onDraw() aufgerufen werden muss, um die Ansicht neu zu zeichnen. Wenn sich aus irgendeinem Grund etwas in Ihrer benutzerdefinierten Ansicht ändert, z. B. durch Nutzerinteraktion, und die Änderung angezeigt werden muss, rufen Sie invalidate(). auf.
- Führen Sie die App aus. Tippen Sie auf das Element
DialView, um den Indikator von „Aus“ auf „1“ zu stellen. Das Zifferblatt sollte grün werden. Bei jedem Tippen sollte sich die Markierung auf die nächste Position verschieben. Wenn die Anzeige wieder aus ist, sollte das Zifferblatt wieder grau werden.


In diesem Beispiel wird die grundlegende Funktionsweise der Verwendung benutzerdefinierter Attribute mit Ihrer benutzerdefinierten Ansicht gezeigt. Sie definieren benutzerdefinierte Attribute für die Klasse DialView mit einer anderen Farbe für jede Position des Lüfterrads.
- Erstellen und öffnen Sie
res/values/attrs.xml. - Fügen Sie in
<resources>ein<declare-styleable>-Ressourcenelement hinzu. - Fügen Sie dem
<declare-styleable>-Ressourcenelement dreiattr-Elemente hinzu, eines für jedes Attribut, mit einemnameund einemformat.formatist wie ein Typ und in diesem Fallcolor.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DialView">
<attr name="fanColor1" format="color" />
<attr name="fanColor2" format="color" />
<attr name="fanColor3" format="color" />
</declare-styleable>
</resources>- Öffnen Sie die Layoutdatei
activity_main.xml. - Fügen Sie im
DialViewAttribute fürfanColor1,fanColor2undfanColor3hinzu und legen Sie ihre Werte auf die unten gezeigten Farben fest. Verwenden Sieapp:als Präfix für das benutzerdefinierte Attribut (wie inapp:fanColor1) anstelle vonandroid:, da Ihre benutzerdefinierten Attribute zum Namespaceschemas.android.com/apk/res/your_app_package_nameund nicht zum Namespaceandroidgehören.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"Wenn Sie die Attribute in Ihrer DialView-Klasse verwenden möchten, müssen Sie sie abrufen. Sie werden in einem AttributeSet gespeichert, das Ihrer Klasse bei der Erstellung übergeben wird, sofern es vorhanden ist. Sie rufen die Attribute in init ab und weisen die Attributwerte lokalen Variablen zum Zwischenspeichern zu.
- Öffnen Sie die Klassendatei
DialView.kt. - Deklarieren Sie im
DialViewVariablen, um die Attributwerte im Cache zu speichern.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0- Fügen Sie im Block
initden folgenden Code mit der ErweiterungsfunktionwithStyledAttributeshinzu. Sie geben die Attribute und die Ansicht an und legen die lokalen Variablen fest. Beim Importieren vonwithStyledAttributeswird auch die richtigegetColor()-Funktion importiert.
context.withStyledAttributes(attrs, R.styleable.DialView) {
fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}- Verwende die lokalen Variablen in
onDraw(), um die Zifferblattfarbe basierend auf der aktuellen Lüftergeschwindigkeit festzulegen. Ersetzen Sie die Zeile, in der die Farbe festgelegt wird (paint.color=if(fanSpeed== FanSpeed.OFF) Color.GRAYelseColor.GREEN), durch den folgenden Code.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSeedMaxColor
} as Int- Führen Sie die App aus, klicken Sie auf das Zifferblatt und die Farbeinstellung sollte für jede Position unterschiedlich sein, wie unten dargestellt.
|
|
|
|
Weitere Informationen zu benutzerdefinierten Attributen für Ansichten finden Sie unter View-Klasse erstellen.
Barrierefreiheit umfasst eine Reihe von Design-, Implementierungs- und Testtechniken, mit denen Ihre App für alle Nutzer, einschließlich Menschen mit Behinderungen, nutzbar ist.
Häufige Behinderungen, die die Nutzung eines Android-Geräts beeinträchtigen, sind Blindheit, eingeschränktes Sehvermögen, Farbenblindheit, Taubheit oder Hörverlust und eingeschränkte motorische Fähigkeiten. Wenn Sie Ihre Apps mit Blick auf die Barrierefreiheit entwickeln, verbessern Sie die Nutzerfreundlichkeit nicht nur für Nutzer mit diesen Behinderungen, sondern auch für alle anderen Nutzer.
Android bietet standardmäßig mehrere Bedienungshilfen in den Standard-UI-Ansichten wie TextView und Button. Wenn Sie eine benutzerdefinierte Ansicht erstellen, müssen Sie jedoch berücksichtigen, wie diese benutzerdefinierte Ansicht barrierefreie Funktionen wie gesprochene Beschreibungen von Inhalten auf dem Bildschirm bereitstellt.
In dieser Aufgabe lernen Sie den Android-Screenreader TalkBack kennen und ändern Ihre App so, dass sie für die benutzerdefinierte Ansicht DialView vorlesbare Hinweise und Beschreibungen enthält.
Schritt 1: TalkBack kennenlernen
TalkBack ist der integrierte Screenreader von Android. Wenn TalkBack aktiviert ist, kann der Nutzer sein Android-Gerät bedienen, ohne auf den Bildschirm zu sehen, da Android Bildschirmelemente laut vorliest. Nutzer mit Sehbehinderung verwenden möglicherweise TalkBack, um Ihre App zu nutzen.
In dieser Aufgabe aktivieren Sie TalkBack, um zu verstehen, wie Screenreader funktionieren und wie Sie Apps bedienen.
- Rufen Sie auf einem Android-Gerät oder -Emulator Einstellungen > Bedienungshilfen > TalkBack auf.
- Tippen Sie auf den Schalter Ein/Aus, um TalkBack zu aktivieren.
- Tippen Sie zum Bestätigen der Berechtigungen auf OK.
- Bestätigen Sie Ihr Gerätepasswort, wenn Sie dazu aufgefordert werden. Wenn Sie TalkBack zum ersten Mal ausführen, wird eine Anleitung gestartet. Das Tutorial ist möglicherweise nicht auf älteren Geräten verfügbar.
- Es kann hilfreich sein, die Anleitung mit geschlossenen Augen zu durchlaufen. Wenn Sie die Anleitung später noch einmal aufrufen möchten, gehen Sie zu Einstellungen > Bedienungshilfen > TalkBack > Einstellungen > TalkBack-Anleitung starten.
- Kompiliere und starte die
CustomFanControllerApp oder öffne sie über die Schaltfläche Übersicht oder Letzte auf deinem Gerät. Wenn TalkBack aktiviert ist, wird der Name der App sowie der Text des LabelsTextView(„Lüftersteuerung“) vorgelesen. Wenn Sie jedoch auf dieDialView-Ansicht selbst tippen, werden weder Informationen zum Status der Ansicht (die aktuelle Einstellung für das Zifferblatt) noch zur Aktion, die beim Tippen auf die Ansicht ausgeführt wird, ausgegeben.
Schritt 2: Inhaltsbeschreibungen für Zifferblatt-Labels hinzufügen
Inhaltsbeschreibungen beschreiben die Bedeutung und den Zweck der Ansichten in Ihrer App. Mit diesen Labels können Screenreader wie TalkBack von Android die Funktion jedes Elements genau erklären. Bei statischen Ansichten wie ImageView können Sie die Inhaltsbeschreibung mit dem Attribut contentDescription in der Layoutdatei hinzufügen. Bei Textansichten (TextView und EditText) wird der Text in der Ansicht automatisch als Inhaltsbeschreibung verwendet.
Für die Ansicht zur benutzerdefinierten Lüftersteuerung müssen Sie die Inhaltsbeschreibung jedes Mal, wenn auf die Ansicht geklickt wird, dynamisch aktualisieren, um die aktuelle Lüftereinstellung anzugeben.
- Deklarieren Sie unten in der Klasse
DialVieweine FunktionupdateContentDescription()ohne Argumente oder Rückgabetyp.
fun updateContentDescription() {
}- Ändern Sie in
updateContentDescription()das AttributcontentDescriptionfür die benutzerdefinierte Ansicht in die String-Ressource, die der aktuellen Lüftergeschwindigkeit (Aus, 1, 2 oder 3) zugeordnet ist. Dies sind dieselben Labels, die inonDraw()verwendet werden, wenn das Zifferblatt auf dem Display angezeigt wird.
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}- Scrollen Sie nach oben zum
init()-Block und fügen Sie am Ende dieses Blocks einen Aufruf vonupdateContentDescription()hinzu. Dadurch wird die Inhaltsbeschreibung initialisiert, wenn die Ansicht initialisiert wird.
init {
isClickable = true
// ...
updateContentDescription()
}- Fügen Sie in der Methode
performClick()einen weiteren Aufruf vonupdateContentDescription()direkt vorinvalidate()hinzu.
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}- Kompilieren Sie die App, führen Sie sie aus und achten Sie darauf, dass TalkBack aktiviert ist. Tippen Sie, um die Einstellung für die Zifferblattansicht zu ändern. TalkBack gibt jetzt das aktuelle Label (Aus, 1, 2, 3) sowie den Hinweis „Doppeltippen zum Aktivieren“ aus.
Schritt 3: Weitere Informationen für die Klickaktion hinzufügen
An dieser Stelle könnten Sie aufhören. Die Ansicht wäre in TalkBack nutzbar. Es wäre jedoch hilfreich, wenn in der Ansicht nicht nur angezeigt würde, dass sie aktiviert werden kann („Zum Aktivieren doppeltippen“), sondern auch, was passiert, wenn die Ansicht aktiviert wird („Zum Ändern doppeltippen“ oder „Zum Zurücksetzen doppeltippen“).
Dazu fügen Sie einem Objekt mit Informationen zum Barrierefreiheitsknoten über einen Barrierefreiheits-Delegate Informationen zur Aktion der Ansicht hinzu (hier: eine Klick- oder Tippaktion). Mit einem Bedienungshilfedelegaten können Sie die Bedienungshilfefunktionen Ihrer App über Komposition (anstatt Vererbung) anpassen.
Für diese Aufgabe verwenden Sie die Bedienungshilfenklassen in den Android Jetpack-Bibliotheken (androidx.*), um die Abwärtskompatibilität sicherzustellen.
- Legen Sie in
DialView.ktim Blockiniteinen Barrierefreiheits-Delegate für die Ansicht als neuesAccessibilityDelegateCompat-Objekt fest. Importieren Sieandroidx.core.view.ViewCompatundandroidx.core.view.AccessibilityDelegateCompat, wenn Sie dazu aufgefordert werden. Diese Strategie bietet die größte Abwärtskompatibilität für Ihre App.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})- Überschreiben Sie im
AccessibilityDelegateCompat-Objekt die FunktiononInitializeAccessibilityNodeInfo()mit einemAccessibilityNodeInfoCompat-Objekt und rufen Sie die Methode des Super-Objekts auf. Importieren Sieandroidx.core.view.accessibility.AccessibilityNodeInfoCompat, wenn Sie dazu aufgefordert werden.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
}
})Jede Ansicht hat einen Baum von Barrierefreiheitsknoten, die den tatsächlichen Layoutkomponenten der Ansicht entsprechen können oder auch nicht. Die Bedienungshilfen von Android navigieren durch diese Knoten, um Informationen zur Ansicht zu finden, z. B. vorlesbare Inhaltsbeschreibungen oder mögliche Aktionen, die in dieser Ansicht ausgeführt werden können. Wenn Sie eine benutzerdefinierte Ansicht erstellen, müssen Sie möglicherweise auch die Knoteninformationen überschreiben, um benutzerdefinierte Informationen für die Barrierefreiheit bereitzustellen. In diesem Fall überschreiben Sie die Knoteninformationen, um anzugeben, dass benutzerdefinierte Informationen für die Aktion der Ansicht vorhanden sind.
- Erstellen Sie in
onInitializeAccessibilityNodeInfo()ein neuesAccessibilityNodeInfoCompat.AccessibilityActionCompat-Objekt und weisen Sie es der VariablencustomClickzu. Übergeben Sie die KonstanteAccessibilityNodeInfo.ACTION_CLICKund einen Platzhalterstring an den Konstruktor. Importieren SieAccessibilityNodeInfo, wenn Sie dazu aufgefordert werden.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
"placeholder"
)
}
})Die Klasse AccessibilityActionCompat stellt eine Aktion für eine Ansicht für Bedienungshilfen dar. Eine typische Aktion ist ein Klick oder Tipp, wie Sie ihn hier verwenden. Andere Aktionen können das Erhalten oder Verlieren des Fokus, ein Zwischenablagevorgang (Ausschneiden/Kopieren/Einfügen) oder das Scrollen in der Ansicht sein. Der Konstruktor für diese Klasse erfordert eine Aktionskonstante (hier AccessibilityNodeInfo.ACTION_CLICK) und einen String, der von TalkBack verwendet wird, um anzugeben, was die Aktion ist.
- Ersetzen Sie den String
"placeholder"durch einen Aufruf voncontext.getString(), um eine String-Ressource abzurufen. Prüfe für die jeweilige Ressource die aktuelle Lüftergeschwindigkeit. Wenn die Geschwindigkeit derzeitFanSpeed.HIGHbeträgt, ist der String"Reset". Wenn die Lüftergeschwindigkeit einen anderen Wert hat, ist der String"Change.". Diese String-Ressourcen erstellen Sie in einem späteren Schritt.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
context.getString(if (fanSpeed != FanSpeed.HIGH) R.string.change else R.string.reset)
)
}
})- Fügen Sie nach der schließenden Klammer für die
customClick-Definition die neue Barrierefreiheitsaktion mit der MethodeaddAction()dem Knoteninfo-Objekt hinzu.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
context.getString(if (fanSpeed != FanSpeed.HIGH)
R.string.change else R.string.reset)
)
info.addAction(customClick)
}
})- Fügen Sie in
res/values/strings.xmldie String-Ressourcen für „Ändern“ und „Zurücksetzen“ hinzu.
<string name="change">Change</string>
<string name="reset">Reset</string>- Kompilieren Sie die App, führen Sie sie aus und achten Sie darauf, dass TalkBack aktiviert ist. Die Aufforderung „Zum Aktivieren doppeltippen“ lautet jetzt entweder „Zum Ändern doppeltippen“ (wenn die Lüftergeschwindigkeit niedriger als „Hoch“ oder „3“ ist) oder „Zum Zurücksetzen doppeltippen“ (wenn die Lüftergeschwindigkeit bereits „Hoch“ oder „3“ ist). Der Hinweis „Doppeltippen zum…“ wird vom TalkBack-Dienst selbst bereitgestellt.
Laden Sie den Code für das fertige Codelab herunter.
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views
Alternativ können Sie das Repository als ZIP-Datei herunterladen, entzippen und in Android Studio öffnen.
- Wenn Sie eine benutzerdefinierte Ansicht erstellen möchten, die das Aussehen und Verhalten einer
View-Unterklasse wieEditTextübernimmt, fügen Sie eine neue Klasse hinzu, die diese Unterklasse erweitert, und nehmen Sie Anpassungen vor, indem Sie einige der Methoden der Unterklasse überschreiben. - Wenn Sie eine benutzerdefinierte Ansicht mit einer beliebigen Größe und Form erstellen möchten, fügen Sie eine neue Klasse hinzu, die
Viewerweitert. - Überschreiben Sie
View-Methoden wieonDraw(), um die Form und das grundlegende Erscheinungsbild der Ansicht zu definieren. - Verwenden Sie
invalidate(), um das Zeichnen oder Neuzeichnen der Ansicht zu erzwingen. - Um die Leistung zu optimieren, sollten Sie Variablen zuweisen und alle erforderlichen Werte für das Zeichnen und Malen bevor zuweisen, Sie sie in
onDraw()verwenden, z. B. bei der Initialisierung von Membervariablen. - Überschreiben Sie
performClick()anstelle vonOnClickListener(), um das interaktive Verhalten der benutzerdefinierten Ansicht zu definieren. So können Sie oder andere Android-Entwickler, die Ihre benutzerdefinierte Ansichtsklasse verwenden, mitonClickListener()zusätzliches Verhalten bereitstellen. - Fügen Sie die benutzerdefinierte Ansicht einer XML-Layoutdatei mit Attributen hinzu, um ihr Erscheinungsbild zu definieren, wie Sie es auch bei anderen UI-Elementen tun würden.
- Erstellen Sie die Datei
attrs.xmlim Ordnervalues, um benutzerdefinierte Attribute zu definieren. Anschließend können Sie die benutzerdefinierten Attribute für die benutzerdefinierte Ansicht in der XML-Layoutdatei verwenden.
Udacity-Kurs:
Android-Entwicklerdokumentation:
- Benutzerdefinierte Ansichten erstellen
@JvmOverloads- Benutzerdefinierte Komponenten
- So rendert Android Ansichten
onMeasure()onSizeChanged()onDraw()CanvasPaintdrawText()setTypeface()setColor()drawRect()drawOval()drawArc()drawBitmap()setStyle()invalidate()- Ansicht
- Eingabeereignisse
- Paint
- Kotlin-Erweiterungsbibliothek android-ktx
withStyledAttributes- Android KTX-Dokumentation
- Originalankündigung im Android KTX-Blog
- Benutzerdefinierte Ansichten barrierefreier gestalten
AccessibilityDelegateCompatAccessibilityNodeInfoCompatAccessibilityNodeInfoCompat.AccessibilityActionCompat
Videos:
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.
Frage 1
Welche Methode überschreiben Sie, um die Positionen, Dimensionen und alle anderen Werte zu berechnen, wenn der benutzerdefinierten Ansicht zum ersten Mal eine Größe zugewiesen wird?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ onDraw()
Frage 2
Welche Methode rufen Sie im UI-Thread auf, nachdem sich ein Attributwert geändert hat, um anzugeben, dass die Ansicht mit onDraw() neu gezeichnet werden soll?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ getVisibility()
Frage 3
Welche View-Methode sollten Sie überschreiben, um Ihrer benutzerdefinierten Ansicht Interaktivität hinzuzufügen?
▢ setOnClickListener()
▢ onSizeChanged()
▢ isClickable()
▢ performClick()
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Codelabs zum Thema „Android für Fortgeschrittene mit Kotlin“.



