Android bietet ein ausgereiftes und leistungsstarkes komponentenbasiertes Modell zum Erstellen der UI, das auf den grundlegenden Layoutklassen View
und ViewGroup
basiert. Die Plattform enthält verschiedene vordefinierte View
- und ViewGroup
-Unterklassen – die sogenannten Widgets bzw. Layouts –, mit denen Sie Ihre UI erstellen können.
Eine unvollständige Liste verfügbarer Widgets umfasst Button
, TextView
, EditText
, ListView
, CheckBox
, RadioButton
, Gallery
, Spinner
und die speziellen Widgets AutoCompleteTextView
, ImageSwitcher
und TextSwitcher
.
Zu den verfügbaren Layouts gehören LinearLayout
, FrameLayout
, RelativeLayout
und weitere. Weitere Beispiele finden Sie unter Gängige Layouts.
Wenn keines der vordefinierten Widgets oder Layouts Ihre Anforderungen erfüllt, können Sie Ihre eigene abgeleitete View
-Klasse erstellen. Wenn Sie nur kleine Anpassungen an einem vorhandenen Widget oder Layout vornehmen müssen, können Sie eine Unterklasse des Widgets oder Layouts erstellen und die zugehörigen Methoden überschreiben.
Wenn Sie eigene abgeleitete View
-Klassen erstellen, können Sie das Erscheinungsbild und die Funktion eines Bildschirmelements präzise steuern. Hier sind einige Beispiele dafür, was Sie mit benutzerdefinierten Ansichten alles tun können:
-
Du kannst einen vollständig benutzerdefiniert gerenderten
View
-Typ erstellen, z. B. einen 2D-Grafikregler für die Lautstärkeregelung, der einer analogen elektronischen Steuerung ähnelt. -
Sie können eine Gruppe von
View
-Komponenten zu einer neuen Komponente kombinieren, z. B. ein Kombinationsfeld (eine Kombination aus einer Pop-up-Liste und einem Textfeld für die kostenlose Eingabe), ein Steuerelement mit zwei Bereichen (einen linken und rechten Bereich mit jeweils einer Liste, in der Sie neu zuweisen können, welches Element sich in welcher Liste befindet) usw. -
Sie können die Darstellung einer
EditText
-Komponente auf dem Bildschirm überschreiben. In der Beispiel-App NotePad lässt sich damit eine mit Linien versehene Notepad-Seite erstellen. - Sie können andere Ereignisse wie Tastendrücke erfassen und individuell verarbeiten, z. B. für ein Spiel.
In den folgenden Abschnitten wird erläutert, wie Sie benutzerdefinierte Ansichten erstellen und in Ihrer Anwendung verwenden. Ausführliche Referenzinformationen finden Sie in der Klasse View
.
Der grundlegende Ansatz
Hier finden Sie eine allgemeine Übersicht darüber, was Sie wissen müssen, um Ihre eigenen View
-Komponenten zu erstellen:
-
Erweitern Sie eine vorhandene
View
-Klasse oder eine abgeleitete Klasse um Ihre eigene Klasse. -
Überschreiben Sie einige der Methoden der übergeordneten Klasse. Die zu überschreibenden übergeordneten Methoden beginnen mit
on
, z. B.onDraw()
,onMeasure()
undonKeyDown()
. Dies ähnelt denon
-Ereignissen inActivity
oderListActivity
, die Sie für Lebenszyklus- und andere Funktions-Hooks überschreiben. - Verwenden Sie die neue Erweiterungsklasse. Anschließend können Sie die neue Erweiterungsklasse anstelle der Ansicht verwenden, auf der sie basiert.
Vollständig angepasste Komponenten
Sie können vollständig benutzerdefinierte grafische Komponenten erstellen, die Ihren Vorstellungen entsprechen. Vielleicht möchten Sie ein grafisches VU-Messgerät, das wie eine alte analoge Anzeige aussieht, oder eine Textansicht zum Mitsingen, in der sich eine hüpfende Kugel über die Wörter bewegt, während Sie an einer Karaokemaschine mitsingen. Möglicherweise möchten Sie etwas, das die integrierten Komponenten unabhängig von ihrer Kombination nicht können.
Glücklicherweise können Sie Komponenten erstellen, die so aussehen und funktionieren, wie Sie möchten, und nur durch Ihre Vorstellungskraft, die Größe des Bildschirms und die verfügbare Prozessorleistung eingeschränkt werden, da Ihre Anwendung unter Umständen auf etwas mit deutlich weniger Energie als Ihre Desktop-Workstation ausgeführt werden muss.
Berücksichtigen Sie beim Erstellen einer vollständig benutzerdefinierten Komponente Folgendes:
-
Die allgemeinste Ansicht, die erweitert werden kann, ist
View
. In der Regel erweitern Sie diese Ansicht in der Regel, um Ihre neue Superkomponente zu erstellen. - Sie können einen Konstruktor angeben, der Attribute und Parameter aus der XML-Datei annehmen kann, und Sie können eigene Attribute und Parameter verwenden, z. B. Farbe und Bereich des VU-Messtools oder die Breite und Dämpfung der Nadel.
- Sie möchten wahrscheinlich eigene Ereignis-Listener, Attribut-Zugriffsfunktionen und Modifikatoren sowie komplexere Funktionen in Ihrer Komponentenklasse erstellen.
-
Sie müssen mit hoher Wahrscheinlichkeit
onMeasure()
überschreiben und auchonDraw()
, wenn die Komponente etwas anzeigen soll. Beide haben ein Standardverhalten, aber die StandardeinstellungonDraw()
hat keine Auswirkung und die StandardeinstellungonMeasure()
legt immer eine Größe von 100 × 100 fest, was wahrscheinlich nicht gewünscht ist. -
Bei Bedarf können Sie auch andere
on
-Methoden überschreiben.
onDraw() und onMeasure() erweitern
Die Methode onDraw()
liefert eine Canvas
, auf der Sie beliebige Elemente implementieren können: 2D-Grafiken, andere Standard- oder benutzerdefinierte Komponenten, Text mit benutzerdefiniertem Stil oder alles andere, was Ihnen einfällt.
onMeasure()
ist ein bisschen komplizierter. onMeasure()
ist ein wichtiger Teil des Renderingvertrags zwischen der Komponente und ihrem Container. onMeasure()
muss überschrieben werden, um die Messungen der darin enthaltenen Teile effizient und genau zu melden. Dies wird etwas komplexer durch die Limitanforderungen des übergeordneten Elements, die an die Methode onMeasure()
übergeben werden, und durch die Anforderung, die Methode setMeasuredDimension()
nach ihrer Berechnung mit der gemessenen Breite und Höhe aufzurufen. Wenn Sie diese Methode nicht über eine überschriebene onMeasure()
-Methode aufrufen, wird zum Zeitpunkt der Messung eine Ausnahme ausgelöst.
Im Großen und Ganzen sieht die Implementierung von onMeasure()
in etwa so aus:
-
Die überschriebene
onMeasure()
-Methode wird mit Spezifikationen für Breite und Höhe aufgerufen, die als Anforderungen für die Einschränkungen der von Ihnen erzeugten Breiten- und Höhenmessungen gelten. Die ParameterwidthMeasureSpec
undheightMeasureSpec
sind beides Ganzzahlcodes, die Dimensionen darstellen. Eine vollständige Referenz zu den Einschränkungen, die für diese Spezifikationen erforderlich sind, finden Sie in der Referenzdokumentation unterView.onMeasure(int, int)
. In dieser Referenzdokumentation wird auch der gesamte Messvorgang erläutert. -
Mit der
onMeasure()
-Methode deiner Komponente werden Breite und Höhe berechnet, die zum Rendern der Komponente erforderlich sind. Sie muss versuchen, die übergebenen Spezifikationen einzuhalten, auch wenn sie möglicherweise überschritten werden. In diesem Fall kann das übergeordnete Element auswählen, was geschehen soll, z. B. Zuschneiden, Scrollen, Auslösen einer Ausnahme, oder dasonMeasure()
bitten, es noch einmal zu versuchen, möglicherweise mit anderen Messspezifikationen. -
Wenn Breite und Höhe berechnet werden, rufen Sie die Methode
setMeasuredDimension(int width, int height)
mit den berechneten Maßen auf. Andernfalls wird eine Ausnahme ausgelöst.
Im Folgenden finden Sie eine Zusammenfassung weiterer Standardmethoden, die das Framework für Ansichten aufruft:
Kategorie | Methoden | Beschreibung |
---|---|---|
Erstellung | Konstruktoren | Es gibt eine Form des Konstruktors, die aufgerufen wird, wenn die Ansicht aus Code erstellt wird, und ein Formular, das aufgerufen wird, wenn die Ansicht aufgrund einer Layoutdatei aufgebläht wird. Das zweite Formular parst und wendet die in der Layoutdatei definierten Attribute an. |
|
Wird aufgerufen, nachdem eine Ansicht und alle ihre untergeordneten Elemente aus XML aufgebläht wurden. | |
Layout |
|
Wird aufgerufen, um die Größenanforderungen für diese Ansicht und alle ihre untergeordneten Elemente zu bestimmen. |
|
Wird aufgerufen, wenn diese Ansicht allen untergeordneten Elementen eine Größe und Position zuweisen muss. | |
|
Wird aufgerufen, wenn die Größe dieser Ansicht geändert wird. | |
Zeichnung |
|
Wird aufgerufen, wenn der Inhalt der Ansicht gerendert werden muss. |
Ereignisverarbeitung |
|
Wird aufgerufen, wenn ein Key-Down-Ereignis auftritt. |
|
Wird aufgerufen, wenn ein Schlüsselereignis eintritt. | |
|
Wird aufgerufen, wenn ein Trackball-Bewegungsereignis auftritt. | |
|
Wird aufgerufen, wenn ein Touchscreen-Bewegungsereignis auftritt. | |
Fokus |
|
Wird aufgerufen, wenn der Aufruf zu- oder abnimmt. |
|
Wird aufgerufen, wenn das Fenster, das die Ansicht enthält, verstärkt oder hervorgehoben wird. | |
Wird angehängt |
|
Wird aufgerufen, wenn die Ansicht an ein Fenster angehängt wird. |
|
Wird aufgerufen, wenn die Ansicht vom Fenster getrennt wird. | |
|
Wird aufgerufen, wenn die Sichtbarkeit des Fensters mit der Ansicht geändert wird. |
Zusammengesetzte Steuerelemente
Wenn Sie keine vollständig benutzerdefinierte Komponente erstellen, sondern eine wiederverwendbare Komponente zusammenstellen möchten, die aus einer Gruppe vorhandener Steuerelemente besteht, ist es am besten, eine zusammengesetzte Komponente (oder mehrstufige Steuerung) zu erstellen. Zusammenfassend lässt sich so eine Reihe von mehr atomaren Steuerelementen oder Ansichten in einer logischen Gruppe von Elementen zusammenfassen, die als eine Sache behandelt werden können.
Ein Kombinationsfeld kann beispielsweise eine Kombination aus einem einzeiligen EditText
-Feld und einer angrenzenden Schaltfläche mit einer angehängten Pop-up-Liste sein. Wenn der Nutzer auf die Schaltfläche tippt und etwas aus der Liste auswählt, wird das Feld EditText
ausgefüllt. Er kann aber auch etwas direkt in EditText
eingeben.
In Android sind dafür zwei andere Ansichten verfügbar: Spinner
und AutoCompleteTextView
. Unabhängig davon ist dieses Konzept für ein Kombinationsfeld ein gutes Beispiel.
So erstellen Sie eine zusammengesetzte Komponente:
-
Verwenden Sie wie bei einem
Activity
entweder den deklarativen (XML-basierten) Ansatz, um die enthaltenen Komponenten zu erstellen, oder verschachteln Sie sie programmatisch aus Ihrem Code. Der übliche Ausgangspunkt ist irgendeine ArtLayout
. Erstellen Sie also eine Klasse, die eineLayout
erweitert. Im Fall eines Kombinationsfelds können Sie einLinearLayout
mit horizontaler Ausrichtung verwenden. Du kannst andere Layouts darin verschachteln, sodass die zusammengesetzte Komponente beliebig komplex und strukturiert sein kann. -
Übergeben Sie im Konstruktor für die neue Klasse zuerst alle von der übergeordneten Klasse erwarteten Parameter an den übergeordneten Konstruktor. Anschließend können Sie die anderen Ansichten zur Verwendung in Ihrer neuen Komponente einrichten. Hier erstellen Sie das Feld
EditText
und die Pop-up-Liste. Sie können eigene Attribute und Parameter in die XML-Datei einfügen, die Ihr Konstruktor abrufen und verwenden kann. -
Optional können Sie Listener für Ereignisse erstellen, die von den enthaltenen Ansichten generiert werden könnten. Ein Beispiel hierfür ist eine Listener-Methode für den Klick-Listener für Listenelemente, mit der der Inhalt von
EditText
aktualisiert wird, wenn eine Listenauswahl getroffen wird. -
Optional können Sie eigene Attribute mit Zugriffsfunktionen und Modifikatoren erstellen. Lassen Sie beispielsweise zu, dass der Wert
EditText
anfänglich in der Komponente festgelegt wird und bei Bedarf den Inhalt abgefragt wird. -
Optional können Sie
onDraw()
undonMeasure()
überschreiben. Dies ist normalerweise nicht erforderlich, wenn einLayout
erweitert wird, da das Layout ein Standardverhalten hat, das wahrscheinlich problemlos funktioniert. -
Optional können Sie andere
on
-Methoden wieonKeyDown()
überschreiben, um beispielsweise bestimmte Standardwerte aus der Pop-up-Liste eines Kombinationsfelds auszuwählen, wenn auf eine bestimmte Taste getippt wird.
Die Verwendung eines Layout
als Grundlage für ein benutzerdefiniertes Steuerelement bietet unter anderem folgende Vorteile:
- Du kannst das Layout mithilfe der deklarativen XML-Dateien wie bei einem Aktivitätsbildschirm angeben oder Ansichten programmatisch erstellen und im Layout aus deinem Code verschachteln.
-
Die Methoden
onDraw()
undonMeasure()
sowie die meisten anderenon
-Methoden haben ein geeignetes Verhalten, sodass Sie sie nicht überschreiben müssen. - Sie können schnell beliebig komplexe zusammengesetzte Ansichten erstellen und sie so wiederverwenden, als wären sie eine einzelne Komponente.
Vorhandenen Ansichtstyp ändern
Wenn es eine Komponente gibt, die Ihren Vorstellungen ähnelt, können Sie diese Komponente erweitern und das Verhalten überschreiben, das Sie ändern möchten. Alles, was Sie mit einer vollständig benutzerdefinierten Komponente tun, steht Ihnen kostenlos zur Verfügung. Wenn Sie jedoch mit einer spezialisierteren Klasse in der View
-Hierarchie beginnen, erhalten Sie Verhaltensweisen, die genau das tun, was Sie möchten.
Die Beispiel-App NotePad demonstriert beispielsweise viele Aspekte der Verwendung der Android-Plattform. Unter anderem wird eine EditText
-Ansicht erweitert, um einen linienförmigen Notizblock zu erstellen. Dies ist kein perfektes Beispiel und die APIs dafür können sich ändern, aber es veranschaulicht die Prinzipien.
Falls noch nicht geschehen, importieren Sie das NotePad-Beispiel in Android Studio oder sehen Sie sich die Quelle über den bereitgestellten Link an. Sehen Sie sich insbesondere die Definition von LinedEditText
in der Datei NoteEditor.java
an.
Beachten Sie in dieser Datei Folgendes:
-
Die Definition
Die Klasse wird mit der folgenden Zeile definiert:
public static class LinedEditText extends EditText
LinedEditText
ist als innere Klasse innerhalb derNoteEditor
-Aktivität definiert, ist aber öffentlich, sodass alsNoteEditor.LinedEditText
von außerhalb derNoteEditor
-Klasse darauf zugegriffen werden kann.Außerdem ist
LinedEditText
static
, was bedeutet, dass keine sogenannten „synthetischen Methoden“ generiert werden, mit denen auf Daten aus der übergeordneten Klasse zugegriffen werden kann. Das bedeutet, dass sie sich wie eine separate Klasse verhält und nicht eng mitNoteEditor
zusammenhängt. Dies ist eine sauberere Methode, um innere Klassen zu erstellen, wenn sie keinen Zugriff auf den Zustand der äußeren Klasse benötigen. Die generierte Klasse bleibt klein und kann problemlos aus anderen Klassen verwendet werden.LinedEditText
erweitertEditText
. Dies ist die Ansicht, die in diesem Fall angepasst werden soll. Wenn Sie fertig sind, kann die neue Klasse eine normaleEditText
-Ansicht ersetzen. -
Klasseninitialisierung
Wie immer wird der Super zuerst aufgerufen. Dies ist kein Standardkonstruktor, sondern ein parametrisierter Konstruktor. Die
EditText
wird mit diesen Parametern erstellt, wenn sie aus einer XML-Layoutdatei überhöht wird. Daher muss der Konstruktor sie übernehmen und auch an den Konstruktor der Basisklasse übergeben. -
Überschriebene Methoden
In diesem Beispiel wird nur die Methode
onDraw()
überschrieben. Beim Erstellen Ihrer eigenen benutzerdefinierten Komponenten müssen Sie jedoch möglicherweise andere Methoden überschreiben.In diesem Beispiel können Sie durch das Überschreiben der
onDraw()
-Methode die blauen Linien imEditText
-Ansichts-Canvas zeichnen. Der Canvas wird an die überschriebene MethodeonDraw()
übergeben. Die Methodesuper.onDraw()
wird aufgerufen, bevor die Methode endet. Die Methode der übergeordneten Klasse muss aufgerufen werden. Rufen Sie ihn in diesem Fall am Ende auf, nachdem Sie die zu berücksichtigenden Linien gezeichnet haben. -
Benutzerdefinierte Komponente
Sie haben jetzt Ihre benutzerdefinierte Komponente, aber wie können Sie sie verwenden? Im NotePad-Beispiel wird die benutzerdefinierte Komponente direkt aus dem deklarativen Layout verwendet. Sehen Sie sich daher
note_editor.xml
im Ordnerres/layout
an:<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
Die benutzerdefinierte Komponente wird als generische Ansicht in der XML-Datei erstellt und die Klasse wird mithilfe des vollständigen Pakets angegeben. Auf die von Ihnen definierte innere Klasse wird mit der
NoteEditor$LinedEditText
-Notation verwiesen. Dies ist eine Standardmethode, um in der Programmiersprache Java auf innere Klassen zu verweisen.Wenn Ihre benutzerdefinierte Ansichtskomponente nicht als innere Klasse definiert ist, können Sie die Ansichtskomponente mit dem XML-Elementnamen deklarieren und das Attribut
class
ausschließen. Beispiel:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
Beachten Sie, dass die Klasse
LinedEditText
jetzt eine separate Klassendatei ist. Wenn die Klasse in der KlasseNoteEditor
verschachtelt ist, funktioniert diese Technik nicht.Die anderen Attribute und Parameter in der Definition sind diejenigen, die an den Konstruktor für benutzerdefinierte Komponenten und dann an den
EditText
-Konstruktor übergeben werden. Es handelt sich also um dieselben Parameter, die Sie für eineEditText
-Ansicht verwenden. Sie können auch eigene Parameter hinzufügen.
Das Erstellen benutzerdefinierter Komponenten ist immer komplizierter.
Eine komplexere Komponente kann noch mehr on
-Methoden überschreiben und eigene Hilfsmethoden einführen, deren Eigenschaften und Verhalten im Wesentlichen angepasst werden. Die einzige Grenze ist Ihre Vorstellungskraft und
was Sie die Komponente benötigen.