Stand: 28.07.2020
In diesem Modul beschäftigen wir uns mit dem Default-Layout ConstraintLayout und schauen uns außerdem das Einbinden von Bildern mit Hilfe des ImageView genauer an.
8.1 ConstraintLayout (API 9+)
Heutzutage muss man natürlich ein Layout so anlegen, dass es sowohl auf einem kleinen Smartphone als auch auf einem großen Tablet funktioniert. Wenn das der Fall ist, spricht man von Responsive Design.
Das relativ neue ConstraintLayout soll dazu möglichst alle Möglichkeiten bieten. Ähnlich wie das RelativeLayout arbeitet es mit Constraints, um das Layout zu definieren. Man sollte keine verschachtelten Layouts mehr benötigen, da die Verschachtelung Performance-Einbußen zur Folge haben kann.
Sie können die Position einer Komponente in Bezug auf (a) der Elternkomponente oder (b) einer zweiten Komponente definieren.
8.1.1 Links-nach-rechts oder rechts-nach-links? (API 17+)
Warum ordnen wir Elemente links- oder rechtsbündig an? Das hat oft auch mit der Leserichtung der Beschriftung zu tun, in den meisten Sprachen ist das von links nach rechts. Wenn dem aber so ist, dann müsste sich ein Layout zumindest teilweise ändern, wenn wir die App auf eine Sprache umstellen, die von rechts nach links geschrieben wird, wie zum Beispiel Arabisch, Hebräisch oder Persisch.
Deswegen unterscheidet Android seit API 17 (Android 4.2) zwischen:
- left-to-right (LTR) layout und
- right-to-left (RTL) layout
Wenn wir uns im weiteren mit Constraints beschäftigen, können Sie entscheiden, ob Sie zwei Komponenten auf der linken Seite aneinander ausrichten oder auf der Start-Seite. Oder ob Sie zwei Komponenten auf der rechten Seite aneinander ausrichten oder auf der Ende-Seite.
Hierbei bedeutet:
- start
- links bei left-to-right (LTR)
- rechts bei right-to-left (RTL)
- end
- rechts bei left-to-right (LTR)
- links bei right-to-left (RTL)
Auch das können Sie in Android Studio testen: Verwenden Sie ein "start" oder "end" Constraint und ändern Sie die Sprache auf eine RTL-Sprache wie Arabisch.
8.1.2 Position in Bezug auf Elternkomponente
Eine Komponente kann in Bezug auf ihre Elternkomponente gesetzt werden. Die Elternkomponente ist meistens die komplette Screen, also geht es hier darum eine Komponente in Bezug auf den Bildschirmrand zu positionieren. Dazu stehen Ihnen entsprechende Constraints als (XML-)Eigenschaften Ihrer Komponente (z.B. eines Buttons) zur Verfügung.
Um auszudrücken, dass ein Button am oberen Rand des Elterncontainers positioniert sein, soll verwenden Sie:
<Button
...
app:layout_constraintTop_toTopOf="parent" />
Dieses Constraint hat zwei Teile. Der erste Teil "constraintTop" sagt aus, auf welchen Teil der betroffenen Komponente (hier ein Button) sich das Constraint bezieht (hier der obere Rand). Der zweite Teil "toTopOf" sagt aus, auf welchen Teil der in-Bezug-gesetzten Komponente (hier die Elternkomponente) sich das Constraint bezieht. Ingesamt heißt das: "Setze die obere Kante des Button an die obere Kante der Elternkomponente". Hier eine Übersicht der Constraints:
Wollen Sie den Button rechts-oben platzieren, ergänzen Sie einen zweiten Constraint, der sich ebenfalls auf den Elterncontainer bezieht:
<Button
...
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
Das neue Constraint sagt aus "Setze die rechte Kante des Button an die rechte Kante der Elternkomponente".
Hinweis: Wenn sich Ihr Layout bei RTL-Sprachen anpassen soll (s.o.), verwenden Sie "Start" statt "Left" und "End" statt "Right".
Zentrieren und Bias
Wollen Sie eine Komponente zentrieren, so setzen Sie zwei im Grund "unmögliche" Constraints (z.B. links und rechts) gleichzeitig an. Die Komponente müsste sich eigentlich strecken, damit Sie die Constraints erfüllt. Stattdessen kann man sich zwei Sprungfedern an jeder Seite vorstellen, die die Komponente gleichermaßen in ihre Richtung ziehen.
<Button
...
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
Hinweis: Es spielt keine Rolle, ob Sie left/right oder start/end nehmen, Sie dürfen die beiden bloß nicht mischen.
Wenn Sie das Element nicht genau in der Mitte haben wollen, sondern z.B. auf 25% von links aus gesehen, dann geben Sie einen horizontal bias an. HJier kann man sich vorstellen, dass die eine Feder stärker ist.
<Button
...
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.25" />
In der Vertikalen sieht das so aus (ebenfalls mit einem Bias, 20% vom unteren Rand entfernt).
<Button
...
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.8" />
Hier eine Übersicht von Beispielen für das Zentrieren (obere zwei) und das Festlegen eines Bias (untere zwei).
8.1.3 Position in Bezug auf andere Komponenten
Nehmen wir an, der Button aus dem vorigen Abschnitt hat den ID "buttonA":
<Button
android:id="@+id/buttonA"
...
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
Dann können wir die Position eines zweiten Button B in Relation zu Button A definieren.
<Button
android:id="@+id/buttonB"
...
app:layout_constraintTop_toBottomOf="@id/buttonA"/>
Das heißt jetzt "Setze die obere Kante von Button B and die untere Kante von Button A". Wenn Sie einen Abstand von 20dp setzen wollen, machen Sie das über den Margin:
<Button
android:id="@+id/buttonB"
...
app:layout_constraintTop_toBottomOf="@id/buttonA"
android:layout_marginTop="20dp" />
Schematisch:
Diese Constraints machen keine Aussage über die Position von Button B in der Horizontalen, also stehen Button A ganz rechts und Button B ganz links.
Sie können jetzt zusätzlich definieren, dass die linke Kante (oder "Startkante") von B auf gleicher Position wie die linke Kante von A sein soll:
<Button
android:id="@+id/buttonB"
...
app:layout_constraintTop_toBottomOf="@id/buttonA"
app:layout_constraintStart_toStartOf="@id/buttonA"
android:layout_marginTop="20dp" />
Hinweis: Wenn sich Ihr Layout bei RTL-Sprachen anpassen soll (s.o.), verwenden Sie "Start", ansonsten geht natürlich auch "Left".
Jetzt sind die zwei Buttons linksbündig ausgerichtet, auch wenn Sie z.B. in den Landscape-Modus gehen.
Genauso wie Sie mit "marginTop" den Abstand zur oberen Komponente kontrollieren können, können Sie mit marginStart einen Abstand zu eigentlich alignierten linken Kante (Start) definineren.
Hier ein Beispiel mit drei Buttons:
<Button
android:id="@+id/buttonA"
...
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonB"
...
app:layout_constraintTop_toBottomOf="@id/buttonA"
app:layout_constraintStart_toStartOf="@id/buttonA"
android:layout_marginTop="20dp"
android:layout_marginStart="30dp" />
<Button
android:id="@+id/buttonC"
...
app:layout_constraintTop_toBottomOf="@id/buttonB"
app:layout_constraintStart_toStartOf="@id/buttonB"
android:layout_marginTop="20dp"
android:layout_marginStart="30dp" />
8.1.4 Bezug auf die Baseline
Sie können auch die Baseline des Textes von zwei Komponenten alignieren. Wenn wir das Drei-Button-Beispiel von oben fortführen und einen Text hinzufügen:
<TextView
...
android:text="Hallo, Welt!"
android:textSize="12sp"
app:layout_constraintBaseline_toBaselineOf="@id/buttonC"
app:layout_constraintStart_toEndOf="@id/buttonC"
android:layout_marginStart="20dp"/>
Wir sehen, dass der Text von Button C und das "Hallo, Welt" an der Baseline aligniert sind.
8.1.5 Orientierungslinien
Es gibt zwei Möglichkeiten, "unsichtbare" Orientierungslinien herzustellen und daran Komponenten auszurichten.
Guidelines
Es können virtuelle Guidelines definiert werden, anhand derer Komponenten ausgerichtet werden. Sie erstellen eine neue Guideline über die Schaltfläche über der Vorschau (in der Design-Ansicht oder dem Preview):
Sie können dann die gestrichelte Linie (nur sichtbar beim Hovern) hin- und herziehen. In der Text-Ansicht sehen Sie:
<android.support.constraint.Guideline
android:id="@+id/guideline"
...
android:orientation="vertical"
app:layout_constraintGuide_begin="50dp" />
In diesem Fall haben Sie mit constraintGuide_begin
eine absolute Positionierung (50dp) vom linken Rand der Screen vorliegen. Es gibt insgesamt drei Möglichkeiten der Positionierung:
layout_constraintGuide_begin
Abstand vom oberen/linken Randlayout_constraintGuide_end
Abstand vom unteren/rechten Randlayout_constraintGuide_percent
Relativer Abstand vom oberen/linken Rand im Vergleich zur Gesamtspanne (Wert zwischen 0 und 1)
Außer diesen dreien sollten Sie keine anderen Positions-Constraints mit Guidelines verwenden!
Sie können dann z.B. den ersten der drei Buttons an diese Linie heften:
<Button
android:id="@+id/buttonA"
...
app:layout_constraintStart_toEndOf="@id/guideline" />
Jetzt sehen Sie beim Ziehen an der Guideline, dass sich alle Buttons und Textfeld mitbewegen.
Natürlich ist auch hier die Empfehlung, keine absoluten Angaben zu verwenden, sondern eine prozentuale Angabe. Dazu setzen Sie den Abstand zu begin und end auf Null und verwenden dann die Prozentangabe, z.B. hier bei 80%:
<android.support.constraint.Guideline
android:id="@+id/guideline"
...
android:orientation="vertical"
app:layout_constraintGuide_begin="0dp"
app:layout_constraintGuide_end="0dp"
app:layout_constraintGuide_percent="0.8" />
Barriere
Eine Barriere ist eine äußere Grenze einer Sammlung von Komponenten.
Nehmen wir unsere drei Buttons. Sie können von Android berechnen lassen, welches die rechteste Kante dieser drei Komponenten ist. Das geschieht natürlich automatisch.
Sie erstellen eine neue Barriere über die Schaltfläche über der Vorschau (in der Design-Ansicht oder dem Preview):
Im Komponenten-Baum können Sie Komponenten in die Barriere hineinziehen. Sie können das aber auch in der Text-Ansicht tun:
<android.support.constraint.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="right"
app:constraint_referenced_ids="buttonC,buttonB,buttonA" />
Wir müssen der Barriere mitteilen, welche Komponenten einbezogen werden sollen (referenced_ids). Außerdem müssen wir sagen, ob welche Kante (barrierDirection) wir definieren wollen.
Wir können zu Demozwecken einen Text mit dieser Grenze positionieren. Genauer gesagt, alignieren wir den Textanfang mit der Barriere. In y-Richtung verwenden wir Button B zur Ausrichtung (Baseline).
<TextView
android:id="@+id/textView2"
...
android:text="Grenze"
android:textSize="30sp"
app:layout_constraintBaseline_toBaselineOf="@id/buttonB"
app:layout_constraintStart_toEndOf="@id/barrier" />
Wenn Sie Button C nach links verschieben, sehen Sie sehr schön, dass sich die Grenze neu ausrichtet, denn dann ist Button B die Komponente, die am weitesten rechts steht.
8.1.6 Verteilen von Komponenten mit Chain
Sie wissen, dass Sie eine Komponente zentrieren können, indem Sie zwei im Grunde "unmögliche" Constraints angeben, z.B. für das horizontale Zentrieren:
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
Wie oben erwähnt, kann man sich zwei Federn links und rechts vorstellen, die gleich stark an der Komponente ziehen.
Wenn man mehrere Komponenten entlang der Horizontalen zentrieren möchte, ist unklar, wie man das definieren soll und auf welche Weise die Komponenten genau verteilt werden sollen.
Chain definieren
Deshalb gibt es das Konzept einer Chain (engl. für Kette). Man kann Komponenten zu einer horizontalen oder vertikalen Chain zusammenführen und diese Komponenten werden dann gleichmäßig verteilt.
Dazu muss man einer Reihe von Komponenten gegenseitige Constraints geben, so dass sie z.B. in einer Reihe angeordnet sind. Im Grunde ist das die logische Erweiterung des Zentrierens: Man gibt auch hier den Komponenten "unmögliche" Constraints zu beiden Seiten. Auch hier kann man sich Federn an den jeweiligen Enden der Chain und zwischen den Komponenten vorstellen.
<Button
android:id="@+id/buttonEins"
...
app:layout_constraintEnd_toStartOf="@+id/buttonZwei"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonZwei"
...
app:layout_constraintStart_toEndOf="@+id/buttonEins"
app:layout_constraintEnd_toStartOf="@+id/buttonDrei" />
<Button
android:id="@+id/buttonDrei"
...
app:layout_constraintStart_toEndOf="@+id/buttonZwei"
app:layout_constraintEnd_toEndOf="parent" />
In der Design-Ansicht kann man das auch bequem machen, indem man die Komponenten alle markiert und dann mit Rechtsklick Chains > Create Horizontal Chain auswählt.
Bei Chains muss man beachten, dass die zweite Achse (in den Beispielen also die y-Achse) damit nicht festgelegt ist. Dies muss mit weiteren Constraints in jeder Komponente einzeln festgelegt werden, z.B. durch Alignierung am oberen Rand, durch Zentrieren oder durch Alignierung entlang der Baseline der ersten Komponente.
Verteilung festlegen
Jetzt gibt es verschiedene Möglichkeiten, wie die Komponenten verteilt werden können. Sollen die Randkomponenten ganz an den Rand anstoßen oder nicht? Oder sollen die Komponenten doch gestreckt werden?
Der Verteilungsstil lässt sich in der Komponente ganz links (bzw. ganz oben), dem sogenannten Chain Head, festlegen. Dort lässt sich spezifizieren, welcher Stil verfolgt wird:
- spread: Komponenten gleichmäßig verteilt
- spread inside: Endkomponenten sind am Rand, sonst gleichmäßig verteilt
- weighted: jede Komponente hat ein Gewicht
- packed: alle Komponenten liegen aneinander
In unserem Beispiel wäre das "buttonEins". Wir schalten um auf "spread inside":
<Button
android:id="@+id/buttonEins"
...
app:layout_constraintHorizontal_chainStyle="spread_inside"
... />
Die Endkomponenten rutschen an den Rand:
8.2 Bilder mit ImageView
8.2.1 Einbinden als Ressource
Sie müssen zunächst Ihre Bilddateien (z.B. JPG oder PNG) in das richtige Ressourcenverzeichnis unter "drawable" kopieren. Der echte Pfad ist etwas versteckt: app/src/main/res/drawable (innerhalb Ihres Projektverzeichnis').
Wie wir schon beim Modul über Ressourcen gesehen haben, wählt Android automatisch passende Ressourcen je nach Kontext aus (z.B. je nach Orientierung: Portrait vs. Landscape). Bei Bildern ist jetzt auch die Pixeldichte interessant. Bei Bildschirmen mit hoher Dichte benötige ich Bilder mit höherer Auflösung, wohingegen ich bei solchen mit geringer Dichte "kleinere" Bilder nehmen kann (die entsprechend performanter sind).
Wie schon bei Portrait/Landscape kann man Android die Pixeldichte berücksichtigen lassen, indem man entsprechende Ressourcen-Parallelverzeichnisse (von drawable) anlegt und die Varianten dort hineinkopiert. Für die Pixeldichte gibt es die Varianten:
- ldpi: Low-density, ca. 120dpi
- mdpi: Medium-density, ca. 160dpi
- hdpi: High-density, ca. 240dpi
- xhdpi: Extra-high-density, ca. 320dpi (API 8+)
- xxhdpi: Extra-extra-high-density, ca. 480dpi (API 16+)
Wenn Sie z.B. zusätzlich zum Default-Bild welcome.png noch eine hochauflösende Variante hinzufügen wollen, richten Sie Ihre res-Verzeichnis so ein:
/res/drawable/welcome.png
/res/drawable-hdpi/welcome.png
Android wählt dann automatisch die passende Datei.
8.2.2 Skalierung
Egal, wie Sie die Seiten Ihres ImageView definieren, Sie müssen Android sagen, wie das Bild eingepasst werden soll, da Höhe und Breite des sichtbaren Bereichs i.d.R. nicht der Pixelauflösung des Bilds entsprechen.
Diese Einstellung erfolgt über die Eigenschaft scaleType
.
Die folgenden drei Optionen zentrieren das Bild, das aber teils geschnitten (engl. to crop) wird.
Die folgenden vier Optionen zeigen immer das vollständige Bild, das aber unterschiedlich "aufgehängt" wird (angedeutet mit dem roten Punkt). Vorsicht ist bei der Option "fitXY" geboten, weil dies die einzige Option ist, die das Bild i.d.R. verzerrt, da das Verhältnis Breite:Höhe (engl. aspect ratio) des Bildes ignoriert werden.
Bei den Skalierungsoptionen fitStart, fitEnd, fitCenter und centerInside bleibt der ImageView i.d.R. größer als das eigentliche Bild. Man kann dann die Größe des ImageView optimieren mit
android:adjustViewBounds="true"
8.2.3 Begrenzen
Wie kann man Bilder gut eingrenzen, wenn doch eine absolute Angabe wie "300dp" problematisch ist? Sinnvoller ist es z.B. eine prozentuale Angabe zu machen: "mein Bild soll 70% der Screenhöhe ausnutzen". In diesem Fall möchten Sie in der Breite die volle Spanne der Screen ausnutzen und schalten daher auf match_parent
. In der anderen Richtung möchten Sie das Bild auf 70% der Höhe begrenzen:
Hier bietet sich an, eine Guideline oder Barrier zu verwenden.
Begrenzen mit Guideline
Wir erzeugen eine horizontale Guideline mit 70% Abstand nach oben:
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7" />
Was muss ich jetzt als Constraint und als layout_height
angeben? Als Constraints verankert man das Bild oben mit der Kante der Screen und unten mit der Guideline. Bei layout_height
gibt man "0dp" an, was von Android als "richte dich nach den Constraints!" interpretiert wird.
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:contentDescription="Tippen am Notebook"
android:scaleType="centerCrop"
app:layout_constraintBottom_toTopOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/work_731198" />
Begrenzen mit Barrier
Schauen wir uns das gleiche Beispiel an, aber mit einer zusätzlichen Barrier. Eine Barrier ist sinnvoll, wenn wir eine Reihe von anderen Komponenten "abgrenzen" wollen. In unserem Beispiel haben wir zwei Buttons, die rechts neben dem Bild stehen sollen. Android soll automatisch die Grenze berechnen und das Bild daran ausrichten:
Zunächste die Buttons:
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:text="Button"
app:layout_constraintBottom_toTopOf="@+id/button2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
Dann die Barrier:
<android.support.constraint.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="left"
app:constraint_referenced_ids="button,button2" />
Im ImageView müssen wir noch Constraints hinzufügen und - wichtig - auch bei der Breite "0dp" angeben.
<ImageView
...
android:layout_width="0dp"
...
app:layout_constraintRight_toLeftOf="@+id/barrier"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginRight="20dp" />
Mit der Margin sorgen wir für 20dp Abstand zu den Buttons.
8.3 Layout als Ressource
Die können z.B. ein separates Layout für Landscape erstellen. Grafisch geht das so (in der Design-Ansicht des Layout-Files):
Android Studio erstellt dann ein zweites Verzeichnis und dort eine zweite Layout-Datei:
/res/layout/activity_main.xml
/res/layout-land/activity_main.xml
Beachten Sie, dass dies im grafischen Editor um der Lesbarkeit willen etwas anders dargestellt wird.
Sie können jetzt in der zweiten Datei in Ruhe Ihr Layout für das Landscape-Format optimieren. Bei laufender Applikation entscheidet Android dann, welches Layout es nimmt.
8.4 Zusammenfassung
Das ConstraintLayout erlaubt flexible Layouts ohne Verschachtelung. Man arbeitet mit einer Reihe von Constraints, die man mit Abständen (Margins) kombinert. Die Constraints sind u.a.
- Beziehungen zum Elterncontainer (u.a. auch zum Zentrieren)
- Beziehungen zu Nachbarkomponenten
Eine Reihe von Komponenten lassen sich zu einer Chain zusammenschließen (horizontal oder vertikal). Eine Chain erlaubt, die Verteilung der Komponenten (horizontal oder vertikal) zu regulieren.
Bilder können in verschiedenen Auflösungen vorgehalten werden, um der zunehmenden Pixeldichte der Endgeräte Rechnung zu tragen. Damit die jeweils passenden Dateien verwendet werden, werden die Bilder in Ressourcen-Verzeichnissen mit bestimmten Markierungen abgelegt (z.B. "-mdpi" oder "-hdpi"). Bilder werden in ImageView-Komponenten dargestellt, die verschiedene Skalierungstypen erlauben (z.B. centerCrop oder fitCenter).
Neben Strings und Bildern kann auch ein Layout eine Ressource sein. So ist es möglich, dass man z.B. für die Landscape-Orientierung ein komplett neues Layout anlegt (wenn man davon ausgeht, dass die Portrait-Orientierung als Standardfall gelayoutet wurde).
8.5 Übungen
(A) Label und Button
Erstellen Sie folgendes Layout unter Verwendung von ConstraintLayout:
(B) Bild und Bewertung
Erstellen Sie folgendes Layout unter Verwendung von ConstraintLayout:
(C) Responsive Layout
Erstellen Sie folgendes Layout, indem Sie für den Fall "Landscape" eine zweite Layout-Ressource anlegen: