Layouts in Ansichten

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

Ein Layout definiert die Struktur einer Benutzeroberfläche in Ihrer App, z. B. in einer Aktivität. Alle Elemente im Layout werden mit einer Hierarchie aus View- und ViewGroup-Objekten erstellt. Eine View zeichnet in der Regel etwas, das der Nutzer sehen und mit dem er interagieren kann. Ein ViewGroup ist ein unsichtbarer Container, der die Layoutstruktur für View und andere ViewGroup-Objekte definiert (siehe Abbildung 1).

Abbildung 1: Abbildung einer Ansichtshierarchie, die ein UI-Layout definiert.

View-Objekte werden häufig als Widgets bezeichnet und können eine von vielen abgeleiteten Klassen sein, z. B. Button oder TextView. ViewGroup-Objekte werden in der Regel als Layouts bezeichnet und können einer von vielen Typen sein, die eine andere Layoutstruktur haben, z. B. LinearLayout oder ConstraintLayout.

Sie können ein Layout auf zwei Arten deklarieren:

  • UI-Elemente in XML deklarieren Android bietet ein einfaches XML-Vokabular, das den View-Klassen und abgeleiteten Klassen entspricht, z. B. denen für Widgets und Layouts. Du kannst auch den Layout-Editor von Android Studio verwenden, um dein XML-Layout über eine Drag-and-drop-Oberfläche zu erstellen.

  • Instanziieren Sie Layoutelemente zur Laufzeit. Ihre Anwendung kann View- und ViewGroup-Objekte erstellen und ihre Eigenschaften programmatisch bearbeiten.

Wenn Sie die UI in XML deklarieren, können Sie die Präsentation Ihrer Anwendung vom Code trennen, der ihr Verhalten steuert. Mit XML-Dateien ist es auch einfacher, unterschiedliche Layouts für unterschiedliche Bildschirmgrößen und -ausrichtungen bereitzustellen. Dies wird unter Unterstützung verschiedener Bildschirmgrößen näher erläutert.

Das Android-Framework bietet Ihnen die Flexibilität, eine oder beide dieser Methoden zum Erstellen der Benutzeroberfläche Ihrer App zu verwenden. Sie können die Standardlayouts Ihrer Anwendung beispielsweise in XML deklarieren und dann das Layout zur Laufzeit ändern.

XML schreiben

Mit dem XML-Vokabular von Android können Sie schnell UI-Layouts und die darin enthaltenen Bildschirmelemente entwerfen, so wie Sie Webseiten in HTML mit einer Reihe verschachtelter Elemente erstellen.

Jede Layoutdatei muss genau ein Stammelement enthalten, bei dem es sich um ein View- oder ViewGroup-Objekt handeln muss. Nachdem Sie das Stammelement definiert haben, können Sie weitere Layoutobjekte oder Widgets als untergeordnete Elemente hinzufügen, um nach und nach eine View-Hierarchie zu erstellen, die Ihr Layout definiert. Hier siehst du beispielsweise ein XML-Layout, das ein vertikales LinearLayout für TextView und Button verwendet:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>

Nachdem du dein Layout im XML-Format deklariert hast, speichere die Datei mit der Erweiterung .xml im Verzeichnis res/layout/ deines Android-Projekts, damit sie korrekt kompiliert wird.

Weitere Informationen zur Syntax einer Layout-XML-Datei finden Sie unter Layoutressource.

XML-Ressource laden

Beim Kompilieren Ihrer Anwendung wird jede XML-Layoutdatei in einer View-Ressource kompiliert. Laden Sie die Layoutressource in der Callback-Implementierung Activity.onCreate() Ihrer App. Rufen Sie dazu setContentView() auf und übergeben Sie den Verweis auf Ihre Layoutressource in folgendem Format: R.layout.layout_file_name. Wenn Ihr XML-Layout beispielsweise als main_layout.xml gespeichert ist, laden Sie es so für Activity:

Kotlin

fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_layout)
}

Java

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

Das Android-Framework ruft die Callback-Methode onCreate() in der Activity auf, wenn das Activity gestartet wird. Weitere Informationen zu Aktivitätslebenszyklen finden Sie unter Einführung in Aktivitäten.

Merkmale

Jedes View- und ViewGroup-Objekt unterstützt seine eigenen XML-Attribute. Einige Attribute gelten nur für ein View-Objekt. Beispielsweise unterstützt TextView das Attribut textSize. Diese Attribute werden jedoch auch von allen View-Objekten übernommen, die diese Klasse erweitern. Einige sind allen View-Objekten gleich, da sie von der Stammklasse View wie dem Attribut id übernommen werden. Andere Attribute werden als Layoutparameter bezeichnet. Dabei handelt es sich um Attribute, die bestimmte Layoutausrichtungen des View-Objekts beschreiben, die durch das übergeordnete ViewGroup-Objekt dieses Objekts definiert sind.

ID

Jedem View-Objekt kann eine Ganzzahl-ID zugeordnet werden, um das View-Objekt im Baum eindeutig zu identifizieren. Beim Kompilieren der Anwendung wird auf diese ID als Ganzzahl verwiesen. Die ID wird jedoch in der Regel in der Layout-XML-Datei als String im Attribut id zugewiesen. Dies ist ein für alle View-Objekte gemeinsames XML-Attribut, das von der Klasse View definiert wird. Sie verwenden es sehr oft. Die Syntax für eine ID in einem XML-Tag lautet so:

android:id="@+id/my_button"

Das at-Symbol (@) am Anfang des Strings zeigt an, dass der XML-Parser den Rest des ID-Strings parst und erweitert und ihn als ID-Ressource identifiziert. Das Plussymbol (+) bedeutet, dass dies ein neuer Ressourcenname ist, der erstellt und den Ressourcen in der Datei R.java hinzugefügt werden muss.

Das Android-Framework bietet viele weitere ID-Ressourcen. Wenn Sie auf eine Android-Ressourcen-ID verweisen, benötigen Sie das Plussymbol nicht. Sie müssen aber den Namespace des android-Pakets so hinzufügen:

android:id="@android:id/empty"

Der Namespace des Pakets android gibt an, dass Sie auf eine ID aus der Ressourcenklasse android.R und nicht auf die lokale Ressourcenklasse verweisen.

Um Ansichten zu erstellen und in Ihrer App darauf zu verweisen, können Sie ein gängiges Muster verwenden:

  1. Definieren Sie eine Ansicht in der Layoutdatei und weisen Sie ihr eine eindeutige ID zu, wie im folgenden Beispiel gezeigt:
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. Erstellen Sie eine Instanz des Ansichtsobjekts und erfassen Sie sie aus dem Layout, normalerweise in der Methode onCreate(), wie im folgenden Beispiel gezeigt:

    Kotlin

    val myButton: Button = findViewById(R.id.my_button)
    

    Java

    Button myButton = (Button) findViewById(R.id.my_button);
    

Beim Erstellen einer RelativeLayout ist es wichtig, IDs für Ansichtsobjekte zu definieren. In einem relativen Layout können gleichgeordnete Ansichten ihr Layout relativ zu einer anderen gleichgeordneten Ansicht definieren, auf die durch die eindeutige ID verwiesen wird.

Eine ID muss nicht innerhalb der gesamten Baumstruktur eindeutig sein, aber innerhalb des Teils des gesuchten Baums eindeutig. Oft handelt es sich dabei um den gesamten Baum. Deshalb sollte er möglichst einzigartig sein.

Layoutparameter

XML-Layoutattribute mit dem Namen layout_something definieren Layoutparameter für das View, die für die ViewGroup geeignet sind, in der es sich befindet.

Jede ViewGroup-Klasse implementiert eine verschachtelte Klasse, die ViewGroup.LayoutParams erweitert. Diese abgeleitete Klasse enthält Property-Typen, die die Größe und Position jeder untergeordneten Ansicht entsprechend der Ansichtsgruppe definieren. Wie in Abbildung 2 dargestellt, definiert die übergeordnete Ansichtsgruppe Layoutparameter für jede untergeordnete Ansicht, einschließlich der untergeordneten Ansichtsgruppe.

Abbildung 2: Visualisierung einer Ansichtshierarchie mit Layoutparametern, die jeder Ansicht zugeordnet sind.

Jede abgeleitete LayoutParams-Klasse hat eine eigene Syntax zum Festlegen von Werten. Jedes untergeordnete Element muss eine LayoutParams definieren, die für das übergeordnete Element geeignet ist. Es kann aber auch eine andere LayoutParams für seine eigenen untergeordneten Elemente definieren.

Alle Ansichtsgruppen enthalten eine Breite und Höhe mithilfe von layout_width und layout_height, die jeweils für jede Ansicht definiert werden müssen. Viele LayoutParams haben optionale Ränder und Rahmen.

Sie können Breite und Höhe mit genauen Maßen angeben, dies ist jedoch nicht immer sinnvoll. Häufiger verwenden Sie eine dieser Konstanten, um die Breite oder Höhe festzulegen:

  • wrap_content: Die Ansicht wird automatisch an die für ihren Inhalt erforderlichen Abmessungen angepasst.
  • match_parent: Damit wird die Ansicht so groß, wie die übergeordnete Ansichtsgruppe zulässt.

Im Allgemeinen raten wir davon ab, die Breite und Höhe des Layouts in absoluten Einheiten wie Pixeln anzugeben. Ein besserer Ansatz ist die Verwendung relativer Messwerte wie dichteunabhängige Pixeleinheiten (dp), wrap_content oder match_parent, da Ihre App so auf einer Vielzahl von Bildschirmgrößen korrekt angezeigt wird. Die zulässigen Messtypen sind in der Layoutressource definiert.

Layout position

Eine Ansicht weist eine rechteckige Geometrie auf. Sie besteht aus einem Standort, der durch left- und top-Koordinaten ausgedrückt wird, und zwei Dimensionen, die als Breite und Höhe ausgedrückt werden. Die Einheit für Standort und Abmessungen ist das Pixel.

Sie können den Speicherort einer Ansicht abrufen, indem Sie die Methoden getLeft() und getTop() aufrufen. Ersteres gibt die linke Koordinate (x) des Rechtecks zurück, das die Ansicht darstellt. Letzteres gibt die oberste Koordinate (y) des Rechtecks zurück, das die Ansicht darstellt. Diese Methoden geben den Speicherort der Ansicht in Bezug auf die übergeordnete Ansicht zurück. Wenn getLeft() beispielsweise 20 zurückgibt, befindet sich die Ansicht 20 Pixel rechts vom linken Rand des direkten übergeordneten Elements.

Darüber hinaus gibt es praktische Methoden, um unnötige Berechnungen zu vermeiden: getRight() und getBottom(). Diese Methoden geben die Koordinaten des rechten und unteren Rands des Rechtecks zurück, das die Ansicht darstellt. Das Aufrufen von getRight() erfolgt beispielsweise ähnlich wie die folgende Berechnung: getLeft() + getWidth().

Größe, Abstand und Ränder

Die Größe einer Ansicht wird mit einer Breite und Höhe ausgedrückt. Eine Ansicht hat zwei Wertepaare für Breite und Höhe.

Das erste Paar wird als gemessene Breite und gemessene Höhe bezeichnet. Diese Dimensionen bestimmen, wie groß eine Ansicht innerhalb der übergeordneten Ansicht sein soll. Sie können die gemessenen Dimensionen abrufen, indem Sie getMeasuredWidth() und getMeasuredHeight() aufrufen.

Das zweite Paar wird als Breite und Höhe oder manchmal als Zeichenbreite und Zeichenhöhe bezeichnet. Diese Abmessungen definieren die tatsächliche Größe der Ansicht auf dem Bildschirm zum Zeitpunkt der Zeichnung und nach dem Layout. Diese Werte können von der gemessenen Breite und Höhe abweichen. Das ist aber nicht zwingend erforderlich. Breite und Höhe erhalten Sie durch Aufrufen von getWidth() und getHeight().

Zur Messung der Abmessungen wird bei einer Ansicht der Abstand berücksichtigt. Der Abstand wird für den linken, oberen, rechten und unteren Teil der Ansicht in Pixeln angegeben. Mithilfe von Innenabständen können Sie den Inhalt der Ansicht um eine bestimmte Anzahl von Pixeln verschieben. Beispielsweise wird bei einem linken Abstand von zwei Pixeln der Inhalt der Ansicht um zwei Pixel nach rechts vom linken Rand verschoben. Sie können mit der Methode setPadding(int, int, int, int) einen Abstand festlegen und ihn durch Aufrufen von getPaddingLeft(), getPaddingTop(), getPaddingRight() und getPaddingBottom() abfragen.

Obwohl für eine Ansicht ein Abstand definiert werden kann, werden in dieser Ansicht keine Ränder unterstützt. Ränder werden jedoch von Ansichtsgruppen unterstützt. Weitere Informationen finden Sie unter ViewGroup und ViewGroup.MarginLayoutParams.

Weitere Informationen zu Dimensionen finden Sie unter Dimension.

Neben dem programmatischen Festlegen von Rändern und Innenabständen können Sie sie auch in Ihren XML-Layouts festlegen, wie im folgenden Beispiel gezeigt:

  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
      <TextView android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="16dp"
                android:padding="8dp"
                android:text="Hello, I am a TextView" />
      <Button android:id="@+id/button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginTop="16dp"
              android:paddingBottom="4dp"
              android:paddingEnd="8dp"
              android:paddingStart="8dp"
              android:paddingTop="4dp"
              android:text="Hello, I am a Button" />
  </LinearLayout>
  

Das vorherige Beispiel zeigt, wie Rand und Padding angewendet werden. Bei TextView werden überall einheitliche Ränder und Abstände angewendet. Button zeigt, wie Sie diese unabhängig auf verschiedene Kanten anwenden können.

Gängige Layouts

Jede abgeleitete Klasse der ViewGroup-Klasse bietet eine einzigartige Möglichkeit, die darin verschachtelten Ansichten anzuzeigen. ConstraintLayout ist der flexibelste Layouttyp und derjenige, der die besten Tools für eine flache Layouthierarchie bietet.

Im Folgenden findest du einige der gängigen Layouttypen, die in die Android-Plattform integriert sind.

Lineares Layout erstellen

Ordnet die untergeordneten Elemente in einer einzelnen horizontalen oder vertikalen Zeile an und erstellt eine Bildlaufleiste, wenn die Länge des Fensters die Länge des Bildschirms überschreitet.

Dynamische Listen erstellen

Wenn der Inhalt für Ihr Layout dynamisch oder nicht vordefiniert ist, können Sie RecyclerView oder eine abgeleitete Klasse von AdapterView verwenden. RecyclerView ist in der Regel die bessere Option, da sie Arbeitsspeicher effizienter nutzt als AdapterView.

Zu den gängigen Layouts, die mit RecyclerView und AdapterView möglich sind, gehören:

Liste

Zeigt eine scrollbare Liste mit einer Spalte an.

Raster

Zeigt ein scrollbares Raster aus Spalten und Zeilen an.

RecyclerView bietet mehr Möglichkeiten und die Möglichkeit, einen benutzerdefinierten Layout-Manager zu erstellen.

Adapteransicht mit Daten füllen

Sie können ein AdapterView wie ListView oder GridView füllen, indem Sie die AdapterView-Instanz an eine Adapter binden, die Daten aus einer externen Quelle abruft und einen View erstellt, der jeden Dateneintrag darstellt.

Android bietet mehrere abgeleitete Klassen von Adapter, die zum Abrufen verschiedener Arten von Daten und zum Erstellen von Ansichten für eine AdapterView nützlich sind. Die beiden gängigsten Adapter sind:

ArrayAdapter
Verwenden Sie diesen Adapter, wenn Ihre Datenquelle ein Array ist. Standardmäßig erstellt ArrayAdapter eine Ansicht für jedes Array-Element. Dazu wird toString() für jedes Element aufgerufen und der Inhalt in eine TextView platziert.

Wenn Sie beispielsweise ein Array mit Strings haben, das Sie in einem ListView darstellen möchten, initialisieren Sie ein neues ArrayAdapter-Objekt. Verwenden Sie dazu einen Konstruktor, um das Layout für jeden String und das String-Array anzugeben:

Kotlin

    val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)
    

Java

    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
            android.R.layout.simple_list_item_1, myStringArray);
    

Die Argumente für diesen Konstruktor sind folgende:

  • Deine App Context
  • Das Layout, das ein TextView für jeden String im Array enthält
  • String-Array

Rufen Sie dann setAdapter() auf Ihrem ListView auf:

Kotlin

    val listView: ListView = findViewById(R.id.listview)
    listView.adapter = adapter
    

Java

    ListView listView = (ListView) findViewById(R.id.listview);
    listView.setAdapter(adapter);
    

Wenn Sie die Darstellung der einzelnen Elemente anpassen möchten, können Sie die Methode toString() für die Objekte in Ihrem Array überschreiben. Um für jedes Element, das nicht TextView ist, eine Ansicht zu erstellen, beispielsweise ein ImageView für jedes Arrayelement, erweitern Sie die ArrayAdapter-Klasse und überschreiben Sie getView(), um für jedes Element den gewünschten Ansichtstyp zurückzugeben.

SimpleCursorAdapter
Verwenden Sie diesen Adapter, wenn Ihre Daten aus einem Cursor stammen. Wenn du SimpleCursorAdapter verwendest, musst du ein Layout angeben, das für jede Zeile in Cursor verwendet werden soll und welche Spalten in Cursor in die Ansichten des gewünschten Layouts eingefügt werden sollen. Wenn Sie beispielsweise eine Liste mit den Namen und Telefonnummern von Personen erstellen möchten, können Sie eine Abfrage ausführen, die ein Cursor zurückgibt, das eine Zeile für jede Person und Spalten für die Namen und Nummern enthält. Anschließend erstellen Sie ein String-Array, das angibt, welche Spalten aus Cursor im Layout für die einzelnen Ergebnisse verwendet werden sollen, und ein Ganzzahl-Array, das die entsprechenden Ansichten für die Platzierung jeder Spalte angibt:

Kotlin

    val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME,
                              ContactsContract.CommonDataKinds.Phone.NUMBER)
    val toViews = intArrayOf(R.id.display_name, R.id.phone_number)
    

Java

    String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                            ContactsContract.CommonDataKinds.Phone.NUMBER};
    int[] toViews = {R.id.display_name, R.id.phone_number};
    

Wenn Sie das SimpleCursorAdapter instanziieren, übergeben Sie das für jedes Ergebnis zu verwendende Layout, das Cursor mit den Ergebnissen und diese beiden Arrays:

Kotlin

    val adapter = SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0)
    val listView = getListView()
    listView.adapter = adapter
    

Java

    SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
    ListView listView = getListView();
    listView.setAdapter(adapter);
    

Das SimpleCursorAdapter erstellt dann eine Ansicht für jede Zeile im Cursor unter Verwendung des angegebenen Layouts, indem jedes fromColumns-Element in die entsprechende toViews-Ansicht eingefügt wird.

Wenn Sie im Laufe der Lebensdauer Ihrer App die zugrunde liegenden Daten ändern, die von Ihrem Adapter gelesen werden, rufen Sie notifyDataSetChanged() auf. Dadurch wird die angehängte Ansicht darüber informiert, dass die Daten geändert wurden, und die Ansicht wird aktualisiert.

Klickereignisse verarbeiten

Mithilfe der AdapterView.OnItemClickListener-Schnittstelle können Sie auf Klickereignisse für jedes Element in einem AdapterView reagieren. Beispiel:

Kotlin

listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
    // Do something in response to the click.
}

Java

// Create a message handling object as an anonymous class.
private OnItemClickListener messageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something in response to the click.
    }
};

listView.setOnItemClickListener(messageClickedHandler);

Weitere Informationen

Informationen zur Verwendung von Layouts findest du in der Sunflower-Demo-App auf GitHub.