Benutzerdefinierte Ansichtskomponenten erstellen

Jetpack Compose ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Informationen zum Arbeiten mit Layouts in Compose

Android bietet ein ausgefeiltes und leistungsstarkes komponentenbasiertes Modell zum Erstellen von Benutzeroberflächen, das auf den grundlegenden Layoutklassen View und ViewGroup basiert. Die Plattform umfasst eine Vielzahl von vorgefertigten View und ViewGroup Unterklassen, die als Widgets bzw. Layouts bezeichnet werden und mit denen Sie Ihre Benutzeroberfläche erstellen können.

Eine unvollständige Liste der verfügbaren Widgets umfasst Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner, und die spezielleren Widgets AutoCompleteTextView, ImageSwitcher, und TextSwitcher.

Zu den verfügbaren Layouts gehören LinearLayout, FrameLayout und RelativeLayout. Weitere Beispiele finden Sie unter Häufig verwendete Layouts.

Wenn keines der vorgefertigten Widgets oder Layouts Ihren Anforderungen entspricht, können Sie Ihre eigene View-Unterklasse 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 Ihre eigenen View-Unterklassen erstellen, haben Sie die volle Kontrolle über das Aussehen und die Funktion eines Bildschirmelements. Um Ihnen eine Vorstellung davon zu geben, wie viel Kontrolle Sie mit benutzerdefinierten Ansichten haben, finden Sie hier einige Beispiele für die Möglichkeiten:

  • Sie können einen vollständig benutzerdefinierten View Typ erstellen, z. B. einen "volume control"-Regler, der mit 2D-Grafiken gerendert wird und einem analogen elektronischen Regler ähnelt.
  • Sie können eine Gruppe von View Komponenten zu einer neuen einzelnen Komponente kombinieren, vielleicht um etwas wie ein Kombinationsfeld (eine Kombination aus einer Pop-up-Liste und einem Textfeld für die kostenlose Eingabe) oder ein Steuerelement mit zwei Bereichen (ein linker und ein rechter Bereich mit einer Liste in jedem Bereich, in der Sie neu zuweisen können, welches Element sich in welcher Liste befindet) zu erstellen.
  • Sie können die Art und Weise überschreiben, wie eine EditText-Komponente auf dem Bildschirm gerendert wird. Die Beispiel-App NotePad nutzt dies effektiv, um eine linierte Notizblockseite zu erstellen.
  • Sie können andere Ereignisse wie Tastendrücke erfassen und auf benutzerdefinierte Weise verarbeiten, z. B. für ein Spiel.

In den folgenden Abschnitten wird erläutert, wie Sie benutzerdefinierte Ansichten erstellen und in Ihrer Anwendung verwenden. Detaillierte Referenzinformationen finden Sie in der View-Klasse.

Grundlegender Ansatz

Hier finden Sie eine allgemeine Übersicht über die Schritte, die Sie ausführen müssen, um Ihre eigenen View-Komponenten zu erstellen:

  1. Erstellen Sie eine Unterklasse einer vorhandenen View-Klasse oder -Unterklasse.
  2. Überschreiben Sie einige der Methoden aus der Basisklasse. Die Methoden der Basisklasse, die überschrieben werden müssen, 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 Ihre neue Erweiterungsklasse. Nachdem Sie die Erweiterungsklasse erstellt haben, können Sie sie anstelle von der Ansicht verwenden, auf der sie basiert.

Vollständig benutzerdefinierte Komponenten

Sie können vollständig benutzerdefinierte grafische Komponenten erstellen, die so aussehen, wie Sie es möchten. Vielleicht möchten Sie ein grafisches VU-Meter, das wie ein altes analoges Messgerät aussieht, oder eine Textansicht zum Mitsingen, in der sich ein springender Ball entlang der Wörter bewegt, während Sie mit einer Karaoke-Maschine mitsingen. Möglicherweise möchten Sie etwas, das die integrierten Komponenten nicht leisten können, unabhängig davon, wie Sie sie kombinieren.

Glücklicherweise können Sie Komponenten erstellen, die so aussehen und sich so verhalten, wie Sie es möchten. Die einzigen Einschränkungen sind Ihre Fantasie, die Größe des Bildschirms und die verfügbare Rechenleistung. Beachten Sie dabei, dass Ihre Anwendung möglicherweise auf einem Gerät mit deutlich weniger Leistung als Ihre Desktop-Workstation ausgeführt werden muss.

Wenn Sie eine vollständig benutzerdefinierte Komponente erstellen möchten, beachten Sie Folgendes:

  • Die allgemeinste Ansicht, die Sie erweitern können, ist View. Daher beginnen Sie in der Regel damit, diese zu erweitern, um Ihre neue Superkomponente zu erstellen.
  • Sie können einen Konstruktor angeben, der Attribute und Parameter aus dem XML-Code übernehmen kann. Außerdem können Sie Ihre eigenen Attribute und Parameter verwenden, z. B. die Farbe und den Bereich des VU-Meters oder die Breite und Dämpfung der Nadel.
  • Sie sollten auch eigene Ereignis-Listener, Property-Accessors und -Modifizierer sowie ein komplexeres Verhalten in Ihrer Komponentenklasse erstellen.
  • Sie müssen fast immer onMeasure() überschreiben und wahrscheinlich auch onDraw() überschreiben, wenn die Komponente etwas anzeigen soll. Beide haben zwar ein Standardverhalten, aber die Standardmethode onDraw() tut nichts und die Standardmethode onMeasure() legt immer eine Größe von 100 × 100 fest, was Sie wahrscheinlich nicht möchten.
  • Sie können bei Bedarf auch andere on-Methoden überschreiben.

onDraw() und onMeasure() erweitern

Die Methode onDraw() liefert ein Canvas, auf dem Sie alles implementieren können, was Sie möchten: 2D-Grafiken, andere Standard- oder benutzerdefinierte Komponenten, formatierten Text oder alles andere, was Ihnen einfällt.

onMeasure() ist etwas komplizierter. onMeasure() ist ein wichtiger Bestandteil des Rendering-Vertrags zwischen Ihrer Komponente und ihrem Container. onMeasure() muss überschrieben werden, um die Maße der enthaltenen Teile effizient und genau zu melden. Das wird durch die Anforderungen des übergeordneten Elements, die an die Methode onMeasure() übergeben werden, und durch die Anforderung, die Methode setMeasuredDimension() mit der gemessenen Breite und Höhe aufzurufen, sobald diese berechnet wurden, etwas komplexer. Wenn Sie diese Methode nicht über eine überschriebene onMeasure()-Methode aufrufen, führt das zur Messzeit zu einer Ausnahme.

Im Allgemeinen sieht die Implementierung von onMeasure() so aus:

  • Die überschriebene onMeasure() Methode wird mit Breiten- und Höhen Spezifikationen aufgerufen, die als Anforderungen für die Einschränkungen der von Ihnen erstellten Breiten- und Höhen Messungen behandelt werden. widthMeasureSpec und heightMeasureSpec Parameter sind beides Ganzzahlcodes, die Dimensionen darstellen. Eine vollständige Referenz zu den Arten von Einschränkungen, die diese Spezifikationen erfordern können, finden Sie in der Referenzdokumentation unter View.onMeasure(int, int) In dieser Referenzdokumentation wird auch der gesamte Messvorgang erläutert.
  • Die Methode onMeasure() Ihrer Komponente berechnet eine Messbreite und -höhe, die zum Rendern der Komponente erforderlich sind. Sie muss versuchen, die übergebenen Spezifikationen einzuhalten kann sie aber auch überschreiten. In diesem Fall kann das übergeordnete Element entscheiden, was zu tun ist, z. B. Clipping, Scrollen, eine Ausnahme auslösen oder onMeasure() auffordern, es noch einmal zu versuchen, möglicherweise mit anderen Messspezifikationen.
  • Wenn die Breite und Höhe berechnet wurden, rufen Sie die setMeasuredDimension(int width, int height) Methode mit den berechneten Maßen auf. Andernfalls wird eine Ausnahme ausgelöst.

Hier finden Sie eine Zusammenfassung anderer 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 eine Form, die aufgerufen wird, wenn die Ansicht aus einer Layoutdatei aufgeblasen wird. Die zweite Form analysiert und wendet Attribute an, die in der Layoutdatei definiert sind.
onFinishInflate() Wird aufgerufen, nachdem eine Ansicht und alle ihre untergeordneten Elemente aus XML aufgeblasen wurden.
Layout onMeasure(int, int) Wird aufgerufen, um die Größenanforderungen für diese Ansicht und alle ihrer untergeordneten Elemente zu bestimmen.
onLayout(boolean, int, int, int, int) Wird aufgerufen, wenn diese Ansicht allen ihren 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 die Ansicht ihren Inhalt rendern muss.
Ereignisverarbeitung onKeyDown(int, KeyEvent) Wird aufgerufen, wenn ein Tastendruckereignis auftritt.
onKeyUp(int, KeyEvent) Wird aufgerufen, wenn ein Tastendruckereignis auftritt.
onTrackballEvent(MotionEvent) Wird aufgerufen, wenn ein Trackball-Motion-Event auftritt.
onTouchEvent(MotionEvent) Wird aufgerufen, wenn ein Touchscreen-Motion-Event auftritt.
Fokus onFocusChanged(boolean, int, Rect) Wird aufgerufen, wenn die Ansicht den Fokus erhält oder verliert.
onWindowFocusChanged(boolean) Wird aufgerufen, wenn das Fenster, das die Ansicht enthält, den Fokus erhält oder verliert.
Anhängen onAttachedToWindow() Wird aufgerufen, wenn die Ansicht an ein Fenster angehängt wird.
onDetachedFromWindow() Wird aufgerufen, wenn die Ansicht von ihrem Fenster getrennt wird.
onWindowVisibilityChanged(int) Wird aufgerufen, wenn die Sichtbarkeit des Fensters, das die Ansicht enthält, geändert wird.

Zusammengesetzte Steuerelemente

Wenn Sie keine vollständig benutzerdefinierte Komponente erstellen möchten, sondern eine wiederverwendbare Komponente aus einer Gruppe vorhandener Steuerelemente zusammenstellen möchten, ist es möglicherweise am besten, eine zusammengesetzte Komponente (oder ein zusammengesetztes Steuerelement) zu erstellen. Zusammenfassend werden dabei mehrere atomare Steuerelemente oder Ansichten zu einer logischen Gruppe von Elementen zusammengefasst, die als eine Einheit behandelt werden können. Eine 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 EditText-Feld ausgefüllt. Er kann aber auch direkt etwas in das EditText-Feld eingeben.

In Android stehen zwei weitere Ansichten zur Verfügung, um dies zu tun: 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 zum Erstellen der enthaltenen Komponenten oder verschachteln Sie sie programmatisch aus Ihrem Code. Der übliche Ausgangspunkt ist ein Layout. Erstellen Sie also eine Klasse, die ein Layout erweitert. Im Fall eines Kombinationsfelds können Sie ein LinearLayout mit horizontaler Ausrichtung verwenden. Sie können andere Layouts darin verschachteln, sodass die zusammengesetzte Komponente beliebig komplex und strukturiert sein kann.
  • Übernehmen Sie im Konstruktor für die neue Klasse alle Parameter, die die Basisklasse erwartet, und übergeben Sie sie zuerst an den Konstruktor der Basisklasse. Anschließend können Sie die anderen Ansichten für die Verwendung in Ihrer neuen Komponente einrichten. Hier erstellen Sie das EditText Feld und die Pop-up-Liste. Sie können eigene Attribute und Parameter in das XML-Dokument 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önnen. Ein Beispiel ist eine Listener-Methode für den Click Listener für Listenelemente, um den Inhalt von EditText zu aktualisieren, wenn eine Listenauswahl getroffen wird.
  • Optional können Sie eigene Properties mit Accessors und Modifizierern erstellen. Lassen Sie beispielsweise den EditText Wert anfänglich in der Komponente festlegen und fragen Sie bei Bedarf nach dem Inhalt.
  • Optional können Sie onDraw() und onMeasure() überschreiben. Das ist in der Regel nicht erforderlich, wenn Sie ein Layout erweitern , da das Layout ein Standardverhalten hat, das wahrscheinlich gut funktioniert.
  • Optional können Sie andere on-Methoden überschreiben, z. B. onKeyDown(), um bestimmte Standardwerte aus der Pop-up-Liste eines Kombinationsfelds auszuwählen, wenn eine bestimmte Taste gedrückt wird.

Die Verwendung eines Layout als Grundlage für ein benutzerdefiniertes Steuerelement bietet mehrere Vorteile:

  • Sie können das Layout mit den deklarativen XML-Dateien angeben, genau wie bei einem Aktivitätsbildschirm, oder Sie können Ansichten programmatisch erstellen und sie aus Ihrem Code in das Layout 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 beliebig komplexe zusammengesetzte Ansichten schnell erstellen und sie so wiederverwenden, als wären sie eine einzelne Komponente.

Vorhandenen Ansichtstyp ändern

Wenn es eine Komponente gibt, die dem ähnelt, was Sie möchten, können Sie diese Komponente erweitern und das Verhalten überschreiben, das Sie ändern möchten. Sie können alles tun, was Sie mit einer vollständig benutzerdefinierten Komponente tun können. Wenn Sie jedoch mit einer spezialisierteren Klasse in der View-Hierarchie beginnen, erhalten Sie einige Verhaltensweisen, die Ihre Anforderungen erfüllen.

Die Beispiel-App NotePad veranschaulicht viele Aspekte der Verwendung der Android-Plattform. Dazu gehört auch das Erweitern einer EditText-Ansicht, um einen linierten Notizblock zu erstellen. Das ist kein perfektes Beispiel und die APIs dafür können sich ändern, aber es veranschaulicht die Prinzipien.

Wenn Sie das noch nicht getan haben, importieren Sie das NotePad-Beispiel in Android Studio oder sehen Sie sich den Quellcode über den Link an. Sehen Sie sich insbesondere die Definition von LinedEditText in der NoteEditor.java Datei an.

Hier sind einige Punkte, die Sie in dieser Datei beachten sollten:

  1. Die Definition

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

    LinedEditText wird als innere Klasse in der NoteEditor Aktivität definiert, ist aber öffentlich, sodass von außerhalb der NoteEditor Klasse als NoteEditor.LinedEditText darauf zugegriffen werden kann.

    Außerdem ist LinedEditText static. Das bedeutet, dass keine sogenannten "synthetischen Methoden" generiert werden, mit denen auf Daten aus der Basisklasse zugegriffen werden kann. Daher verhält sie sich eher wie eine separate Klasse als wie etwas, das eng mit NoteEditor verbunden ist. Das ist eine sauberere Möglichkeit, innere Klassen zu erstellen, wenn sie keinen Zugriff auf den Status der äußeren Klasse benötigen. Dadurch bleibt die generierte Klasse klein und kann problemlos von anderen Klassen verwendet werden.

    LinedEditText erweitert EditText, 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 zuerst die Basisklasse aufgerufen. Das ist kein Standardkonstruktor, sondern ein parametrisierter Konstruktor. Das EditText wird mit diesen Parametern erstellt, wenn es aus einer XML-Layoutdatei aufgeblasen 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. Möglicherweise müssen Sie jedoch andere überschreiben, wenn Sie Ihre eigenen benutzerdefinierten Komponenten erstellen.

    In diesem Beispiel können Sie durch Überschreiben der onDraw() Methode die blauen Linien auf das EditText Ansichts-Canvas zeichnen. Das Canvas wird an die überschriebene onDraw() Methode übergeben. Die super.onDraw() Methode wird aufgerufen, bevor die Methode endet. Die Methode der Basisklasse muss aufgerufen werden. In diesem Fall rufen Sie sie am Ende auf, nachdem Sie die gewünschten Linien gezeichnet haben.

  4. Benutzerdefinierte Komponente

    Sie haben jetzt Ihre benutzerdefinierte Komponente. Wie können Sie sie verwenden? Im NotePad-Beispiel wird die benutzerdefinierte Komponente direkt aus dem deklarativen Layout verwendet. Sehen Sie sich also note_editor.xml im res/layout Ordner 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 im XML-Code erstellt und die Klasse wird angegeben mit dem vollständigen Paket. Auf die von Ihnen definierte innere Klasse wird mit der NoteEditor$LinedEditText Notation verwiesen. Das 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 Ansicht Komponente mit dem XML-Elementnamen deklarieren und das class Attribut 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 NoteEditor Klasse verschachtelt ist, funktioniert diese Technik nicht.

    Die anderen Attribute und Parameter in der Definition werden an den Konstruktor der benutzerdefinierten Komponente übergeben und dann an den EditText Konstruktor weitergeleitet. Es sind also dieselben Parameter, die Sie für eine EditText Ansicht verwenden. Sie können auch eigene Parameter hinzufügen.

Das Erstellen benutzerdefinierter Komponenten ist nur so kompliziert, wie Sie es benötigen.

Eine komplexere Komponente kann noch mehr on-Methoden überschreiben und eigene Hilfsmethoden einführen, wodurch ihre Eigenschaften und ihr Verhalten erheblich angepasst werden. Die einzigen Einschränkungen sind Ihre Fantasie und die Anforderungen an die Komponente.