Benutzerdefinierte Ansichtskomponenten erstellen

Schreiben Sie jetzt
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Hier erfahren Sie, wie Sie in „Compose“ mit Layouts arbeiten.

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:

  1. Erweitern Sie eine vorhandene View-Klasse oder eine abgeleitete Klasse um Ihre eigene Klasse.
  2. Überschreiben Sie einige der Methoden der übergeordneten Klasse. Die zu überschreibenden übergeordneten Methoden beginnen mit on, z. B. onDraw(), onMeasure() und onKeyDown(). Dies ähnelt den on-Ereignissen in Activity oder ListActivity, die Sie für Lebenszyklus- und andere Funktions-Hooks überschreiben.
  3. 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 auch onDraw(), wenn die Komponente etwas anzeigen soll. Beide haben ein Standardverhalten, aber die Standardeinstellung onDraw() hat keine Auswirkung und die Standardeinstellung onMeasure() 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 Parameter widthMeasureSpec und heightMeasureSpec 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 unter View.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 das onMeasure() 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.
onFinishInflate() Wird aufgerufen, nachdem eine Ansicht und alle ihre untergeordneten Elemente aus XML aufgebläht wurden.
Layout onMeasure(int, int) Wird aufgerufen, um die Größenanforderungen für diese Ansicht und alle ihre untergeordneten Elemente zu bestimmen.
onLayout(boolean, int, int, int, int) Wird aufgerufen, wenn diese Ansicht allen untergeordneten Elementen eine Größe und Position zuweisen muss.
onSizeChanged(int, int, int, int) Wird aufgerufen, wenn die Größe dieser Ansicht geändert wird.
Zeichnung onDraw(Canvas) Wird aufgerufen, wenn der Inhalt der Ansicht gerendert werden muss.
Ereignisverarbeitung onKeyDown(int, KeyEvent) Wird aufgerufen, wenn ein Key-Down-Ereignis auftritt.
onKeyUp(int, KeyEvent) Wird aufgerufen, wenn ein Schlüsselereignis eintritt.
onTrackballEvent(MotionEvent) Wird aufgerufen, wenn ein Trackball-Bewegungsereignis auftritt.
onTouchEvent(MotionEvent) Wird aufgerufen, wenn ein Touchscreen-Bewegungsereignis auftritt.
Fokus onFocusChanged(boolean, int, Rect) Wird aufgerufen, wenn der Aufruf zu- oder abnimmt.
onWindowFocusChanged(boolean) Wird aufgerufen, wenn das Fenster, das die Ansicht enthält, verstärkt oder hervorgehoben wird.
Wird angehängt onAttachedToWindow() Wird aufgerufen, wenn die Ansicht an ein Fenster angehängt wird.
onDetachedFromWindow() Wird aufgerufen, wenn die Ansicht vom Fenster getrennt wird.
onWindowVisibilityChanged(int) 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 Art Layout. Erstellen Sie also eine Klasse, die eine Layout erweitert. Im Fall eines Kombinationsfelds können Sie ein LinearLayout 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() und onMeasure() überschreiben. Dies ist normalerweise nicht erforderlich, wenn ein Layout erweitert wird, da das Layout ein Standardverhalten hat, das wahrscheinlich problemlos funktioniert.
  • Optional können Sie andere on-Methoden wie onKeyDown() ü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() und onMeasure() sowie die meisten anderen on-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:

  1. Die Definition

    Die Klasse wird mit der folgenden Zeile definiert:
    public static class LinedEditText extends EditText

    LinedEditText ist als innere Klasse innerhalb der NoteEditor-Aktivität definiert, ist aber öffentlich, sodass als NoteEditor.LinedEditText von außerhalb der NoteEditor-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 mit NoteEditor 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 erweitert EditText. Dies ist die Ansicht, die in diesem Fall angepasst werden soll. Wenn Sie fertig sind, kann die neue Klasse eine normale EditText-Ansicht ersetzen.

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

  3. Ü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 im EditText-Ansichts-Canvas zeichnen. Der Canvas wird an die überschriebene Methode onDraw() übergeben. Die Methode super.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.

  4. 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 Ordner res/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 Klasse NoteEditor 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 eine EditText-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.