Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
Vorwort
Teil I Grundlagen
1 Android – eine offene, mobile Plattform
2 Hallo Android!
3 Von der Idee zur Veröffentlichung
Teil II Elementare Anwendungsbausteine
4 Wichtige Grundbausteine von Apps
5 Benutzeroberflächen
6 Multitasking
Teil III Gerätefunktionen nutzen
7 Telefonieren und surfen
8 Sensoren, GPS und Bluetooth
Teil IV Dateien und Datenbanken
9 Dateien lesen, schreiben und drucken
10 Datenbanken
Teil V Multimedia und Produktivität
11 Multimedia
12 Kontakte und Organizer
A Einführung in Kotlin
B Jetpack Compose
C Häufig benötigte Codebausteine
D Literaturverzeichnis
E Die Begleitmaterialien
Stichwortverzeichnis

Ihre Meinung?
Spacer
<< zurück
Android 11 von Thomas Künneth
Das Praxisbuch für App-Entwickler
Buch: Android 11

Android 11
Pfeil 4 Wichtige Grundbausteine von Apps
Pfeil 4.1 Was sind Activities?
Pfeil 4.1.1 Struktur von Apps
Pfeil 4.1.2 Lebenszyklus von Activities
Pfeil 4.2 Kommunikation zwischen Anwendungsbausteinen
Pfeil 4.2.1 Intents
Pfeil 4.2.2 Kommunikation zwischen Activities
Pfeil 4.2.3 Broadcast Receiver
Pfeil 4.3 Fragmente
Pfeil 4.3.1 Grundlagen
Pfeil 4.3.2 Ein Fragment in eine Activity einbetten
Pfeil 4.3.3 Mehrspaltenlayouts
Pfeil 4.4 Berechtigungen
Pfeil 4.4.1 Normale und gefährliche Berechtigungen
Pfeil 4.4.2 Tipps und Tricks zu Berechtigungen
Pfeil 4.5 Navigation
Pfeil 4.5.1 Jetpack Navigation
Pfeil 4.5.2 Die Klasse »BottomNavigationView«
Pfeil 4.6 Zusammenfassung
 
Zum Seitenanfang

4.3    Fragmente Zur vorigen ÜberschriftZur nächsten Überschrift

Wie Sie wissen, strukturieren Activities eine Anwendung. Aufgrund der ihnen zugrunde liegenden Idee, nur genau so viel anzuzeigen oder abzufragen, wie zum Erledigen einer ganz bestimmten Aufgabe nötig ist, lassen sie sich hervorragend wiederverwenden. Ich habe Ihnen dies im vorherigen Abschnitt anhand der Eingabe und späteren Darstellung eines Kontakts demonstriert. Im Gegensatz zu Smartphones bieten Tablets sehr viel Anzeigeplatz. Die auf kleinen Bildschirmen notwendige Trennung zwischen Übersicht und Detaildarstellung ist hier nicht mehr nötig, aus Sicht des Benutzers sogar störend.

 
Zum Seitenanfang

4.3.1    Grundlagen Zur vorigen ÜberschriftZur nächsten Überschrift

Hierzu ein Beispiel: Viele Android-Apps enthalten eine Hauptaktivität, die ihre Funktionen oder Module als Menü (oftmals in Gestalt einer scrollbaren Liste) anbietet. Das Antippen einer Funktion startet eine neue Activity, die die gewünschte Operation ausführt. Tippt der Anwender auf Zurück oder führt die entsprechende Wischgeste aus, erscheint wieder die Übersichtsseite.

Auf Geräten mit großem Display ist diese zeitliche Abfolge vollkommen unnötig. Es bietet sich an, stattdessen die Modulauswahl am linken Rand des Bildschirms ständig sichtbar zu lassen. Der Benutzer kann jederzeit eine neue Funktion auswählen, ohne das aktuell ausgeführte Modul explizit »verlassen« zu müssen. Dabei ist es natürlich wünschenswert, Fachlogik und Oberflächenbeschreibungen wiederverwenden zu können. Tatsächlich gibt es mit android.app.ActivityGroup eine Klasse, die mehrere eingebettete Activities anzeigen und ausführen kann. Allerdings hat Google diese schon mit API-Level 13 für veraltet erklärt. Entwickler sollen stattdessen die mit Honeycomb eingeführten Fragmente nutzen.

Fragmente sind Komponenten mit eigener Benutzeroberfläche und eigenem Lebenszyklus. Sie sind Bausteine innerhalb von Activities. Sie werden wie Views und ViewGroups entweder in Layoutdateien definiert oder per Code erzeugt. Fragmente werden stets im Kontext einer Activity ausgeführt und können nicht losgelöst von ihr verwendet werden. Wird also eine Activity gestoppt, so halten auch ihre Fragmente an, und das Zerstören einer Activity führt auch zur Zerstörung ihrer Fragmente.

Ein Beispielfragment

Die Klasse TestFragment1 des Projekts FragmentDemo1 stellt Ihnen den grundsätzlichen Aufbau von Fragmenten vor. Die App ist in Abbildung 4.8 zu sehen. Google hat mit API-Level 28 (Android 9) die Basisklasse android.app.Fragment für veraltet erklärt und rät stattdessen zur Nutzung der Jetpack-Komponente Fragments. Alle weiteren Apps in diesem Kapitel tun dies. Fragmente leiten von der Basisklasse androidx.fragment.app.Fragment oder einer ihrer Kinder ab.

androidx.fragment.app.ListFragment zeigt scrollbare Listen an. Und mit androidx. fragment.app.DialogFragment können Sie Dialoge darstellen. Wir werden uns beide Klassen später genauer ansehen. TestFragment1 überschreibt nur eine Methode. onStart() ruft die Implementierung der Elternklasse auf (das ist wichtig, damit deren Code ebenfalls ausgeführt wird) und setzt dann den Text einer TextView. Analog zu Activities nutzt Android die Methode, um mitzuteilen, dass ein Fragment für den Benutzer sichtbar wird.

package com.thomaskuenneth.androidbuch.fragmentdemo1

import android.widget.TextView
import androidx.fragment.app.Fragment

class TestFragment1 : Fragment(R.layout.fragment_layout) {

override fun onStart() {
super.onStart()
val textview = view as TextView
textview.text = getString(R.string.text1)
}
}

Listing 4.14    Die Klasse »TestFragment1«

Dem Konstruktor der Elternklasse wird nur ein Parameter übergeben: R.layout.fragment_layout. Damit teilen Sie Fragment mit, welche Layoutdatei entfaltet werden soll. In meinem Beispiel ist dies fragment_layout.xml. Das Layout definiert als einziges Bedienelement eine TextView:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

Listing 4.15    »fragment_layout.xml«

Sind für das Erzeugen des Komponentenbaums mehr Vorarbeiten nötig, können Sie stattdessen den parameterlosen Konstruktor Fragment() aufrufen und die Methode onCreateView() überschreiben. Das zeige ich Ihnen etwas später. Um etwaige zusätzliche Ressourcen, die Sie dort reserviert haben, wieder freizugeben, überschreiben Sie gegebenenfalls die Methode onDestroyView(). Sofern Sie nur eine XML-Datei entfaltet haben oder wie in meinem Beispiel der Elternklasse die Arbeit überlassen, ist dies natürlich nicht nötig.

Die App »FragmentDemo1«

Abbildung 4.8    Die App »FragmentDemo1«

Übrigens müssen Fragmente nicht unbedingt eine Benutzeroberfläche haben. Sie können sich das beispielsweise zunutze machen, um Daten nachzuladen oder Berechnungen auszuführen. In so einem Fall liefert Ihre Implementierung von onCreateView() einfach den Wert null. Die Basisklasse androidx.fragment.app.Fragment tut dies, außer ihr wird im Konstruktor die ID eines Layouts übergeben, denn dann wird ja dieses entfaltet.

 
Zum Seitenanfang

4.3.2    Ein Fragment in eine Activity einbetten Zur vorigen ÜberschriftZur nächsten Überschrift

Um zu verstehen, wie ein Fragment einer Activity zugeordnet wird, sehen Sie sich bitte die Klasse FragmentDemo1Activity an. Sie leitet von androidx.appcompat.app.AppCompatActivity ab.

package com.thomaskuenneth.androidbuch.fragmentdemo1

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class FragmentDemo1Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val view = findViewById<TextView>(R.id.textview)
view.text = getString(R.string.text2)
}
}

Listing 4.16    Die Klasse »FragmentDemo1Activity«

Die Benutzeroberfläche der Activity wird mittels setContentView() aus der Layoutdatei activity_main.xml entfaltet und angezeigt. Außerdem gibt die App in einer Textkomponente die Meldung »Ich bin eine Activity« aus. Das Layout ist folgendermaßen aufgebaut:

<?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/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<fragment
android:id="@+id/fragment"
android:name=
"com.thomaskuenneth.androidbuch.fragmentdemo1.TestFragment1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

Listing 4.17    »activity_main.xml«

Die Datei activity_main.xml ordnet in einem LinearLayout eine TextView oberhalb eines Fragments an. Die beiden Elemente erhalten die IDs fragment und textview. Das Attribut android:name des <fragment />-Elements legt fest, welche Klasse das hier definierte Fragment implementiert. Hierzu wird der voll qualifizierte Klassenname angegeben.

Lebenszyklus von Fragmenten

Im Gegensatz zu Activities, Services oder Broadcast Receivern werden Fragmente nicht in die Manifestdatei eingetragen. Fragmente verbinden Sie mit einer Activity, indem Sie sie in die Benutzeroberfläche der Activity integrieren. Dies geschieht, wie Sie gesehen haben, normalerweise deklarativ in Layoutdateien. Insofern verhalten sich Fragmente in diesem Punkt analog zu »normalen« Bedienelementen, die ich Ihnen in Kapitel 5, »Benutzeroberflächen«, ausführlich vorstellen werde. Im Gegensatz zu den normalen Bedienelementen haben Fragmente ein bestimmtes Verhalten – sie implementieren »Fachlogik«, was sie zu Grundbausteinen von Apps macht.

Der Lebenszyklus von Fragmenten ist eng mit dem von Activities verknüpft. Abbildung 4.9 zeigt Ihnen, welche Methoden eines Fragments aufgerufen werden, wenn sich die Activity in den Zuständen Erzeugt, Gestartet, Fortgesetzt, Pausiert, Gestoppt und Zerstört befindet. Beispielsweise wird onAttach() aufgerufen, wenn ein Fragment an eine Activity oder einen Kontext angehängt wurde. Ihr folgt onCreate(). Prinzipiell können Sie diese Methode überschreiben, um erste Initialisierungen vorzunehmen. Allerdings muss die zugeordnete Activity zu diesem Zeitpunkt nicht vollständig initialisiert sein. Aus diesem Grund steht onActivityCreated() zur Verfügung. Sie wird erst nach der vollständigen Abarbeitung der Activity-Methode onCreate() aufgerufen. Leider hat Google sie aber für veraltet erklärt. Eine Alternative ist onViewCreated(). Sie wird unmittelbar nach onCreateView(), aber vor dem Wiederherstellen eines gespeicherten Zustands aufgerufen.

Auch am Ende des Lebenszyklus eines Fragments werden zahlreiche Callbacks durchlaufen. onPause() signalisiert, dass ein Fragment nicht mehr mit dem Benutzer interagiert, zum Beispiel weil die gleichnamige Activity-Methode aufgerufen wurde. Nach onStop() ist ein Fragment nicht mehr für den Anwender sichtbar. Abschließende Aufräumarbeiten sollten Sie in onDestroy() durchführen. onDetach() kündigt die Abkopplung eines Fragments von der ihm zugeordneten Activity an. Bitte beachten Sie, dass alle Callbacks ihre Arbeit stets so schnell wie möglich abschließen sollten. Aufwendige Operationen wie Netzwerkzugriffe gehören in separate Threads.

Zustand einer Activity und dabei aufgerufene Fragmentmethoden

Abbildung 4.9    Zustand einer Activity und dabei aufgerufene Fragmentmethoden

Es gibt noch eine weitere Verzahnung zwischen diesen beiden Grundbausteinen: Fragmente können nämlich in den Zurück-Stapel von Activities integriert werden. Wie das geht, zeige ich Ihnen im folgenden Abschnitt.

Fragment-Transaktionen

Wenn ein Fragment nicht immer sichtbar ist, sondern nur unter bestimmten Umständen angezeigt wird, ist es für eine durchgängige Bedienung wichtig, dass der Anwender das Fragment auf eine ihm vertraute Weise wieder ausblenden kann. Hierzu hat Google das Konzept der Fragment-Transaktionen eingeführt. Sie können (im Prinzip beliebig viele) Fragmente zur Laufzeit Activities hinzufügen und wieder von ihnen entfernen. Diese Operationen werden zu logischen Schritten zusammengefasst. Jeder dieser Schritte wiederum kann auf den Zurück-Stapel gepackt werden.

Die App »FragmentDemo2«

Abbildung 4.10    Die App »FragmentDemo2«

Vielleicht fragen Sie sich, wozu das nötig ist. Nehmen Sie an, das Anklicken einer Schaltfläche führt dazu, dass eine Activity drei zusätzliche Fragmente anzeigt. Drückt der Anwender die Zurück-Schaltfläche, möchte er sehr wahrscheinlich nicht jedes Fragment einzeln schließen, sondern alle drei auf einmal. Das geht mithilfe der Fragment-Transaktionen mit ganz wenigen Zeilen Code. Wie, das zeige ich Ihnen anhand des Projekts FragmentDemo2 (Abbildung 4.10), dessen Hauptklasse FragmentDemo2Activity in Listing 4.18 zu sehen ist.

Der grundsätzliche Aufbau einer Activity ist Ihnen mittlerweile bekannt: In der Methode onCreate() wird die Benutzeroberfläche mit setContentView() erzeugt und angezeigt. Durch das Registrieren eines View.OnClickListener mit setOnClickListener() können wir beim Anklicken der Schaltfläche Hinzufügen einem LinearLayout, das über R.id.ll angesprochen wird, nach Belieben Fragmente hinzufügen.

Hierzu greift die App mit supportFragmentManager auf ein Objekt des Typs androidx. fragment.app.FragmentManager zu. Wir beginnen durch Aufruf dessen Methode beginTransaction() eine Fragment-Transaktion (androidx.fragment.app.FragmentTransaction) und fügen ihr mit add() drei Fragmente hinzu. addToBackStack() sorgt dafür, dass der Inhalt der Transaktion »in einem Rutsch« vom Zurück-Stapel entfernt wird. Sie können hier auf Wunsch einen Namen übergeben, der den Zustand des Stapels benennt, oder, wie in meinem Beispiel, einfach null. commit() schließt die Transaktion ab.

package com.thomaskuenneth.androidbuch.fragmentdemo2

import android.os.Bundle
import android.view.LayoutInflater
import android.view.*
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.*

class FragmentDemo2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
val ft = supportFragmentManager.beginTransaction()
for (i in 0..2) {
val fragment = EinfachesFragment()
ft.add(R.id.ll, fragment)
}
ft.addToBackStack(null)
ft.commit()
}
}
}

class EinfachesFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(
R.layout.einfaches_fragment,
container, false
)
}
}

Listing 4.18    Die Klasse »FragmentDemo2Activity«

Die drei Fragmente werden durch die Klasse EinfachesFragment realisiert. Ihre einzige selbst implementierte Methode onCreateView() entfaltet durch Aufruf von inflate() der übergebenen LayoutInflater-Instanz den Inhalt der Layoutdatei einfaches_fragment.xml. Statt onCreateView() zu implementieren, könnten Sie den Konstruktor der Elternklasse Fragment mit R.layout.einfaches_fragment aufrufen.

»So ganz nebenbei« haben Sie gelernt, wie man einer Activity Fragmente per Code hinzufügen kann. Für das Entfernen und Austauschen von Fragmenten gibt es die Methoden remove() und replace(). Dass Sie remove() nicht selbst aufrufen mussten, liegt daran, dass Android Ihnen beim Drücken bzw. Antippen der Zurück-Schaltfläche (oder natürlich mit der entsprechenden Wischgeste) die gesamte Arbeit abnimmt.

 
Zum Seitenanfang

4.3.3    Mehrspaltenlayouts Zur vorigen ÜberschriftZur nächsten Überschrift

In diesem Abschnitt zeige ich Ihnen, wie Sie mit Fragmenten Benutzeroberflächen gestalten, die den auf dem Bildschirm zur Verfügung stehenden Platz optimal nutzen. Das Projekt FragmentDemo3 demonstriert dies anhand einer klassischen Master-Detail-Ansicht. Der Anwender wählt aus einer Liste ein Element aus und sieht anschließend dessen Details. Auf Smartphones werden beide Teile, also Liste und Detailansicht, in einer eigenen Activity dargestellt. Auf Tablets oder Smartphones im Quermodus passen hingegen beide zusammen auf den Bildschirm. Die Verteilung von Fragmenten auf Activities ist in Abbildung 4.11 zu sehen.

Verteilung von Fragmenten auf Activities

Abbildung 4.11    Verteilung von Fragmenten auf Activities

Die Hauptaktivität FragmentDemo3Activity der App ist in Listing 4.19 dargestellt. Sie zeigt nur mit setContentView() die Benutzeroberfläche an.

package com.thomaskuenneth.androidbuch.fragmentdemo3

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class FragmentDemo3Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragmentdemo3)
}
}

Listing 4.19    Die Klasse »FragmentDemo3Activity«

Die Klasse AuswahlFragment (siehe Listing 4.20) ist da schon spannender. Sie leitet von androidx.fragment.app.ListFragment ab und kümmert sich um das Anzeigen einer Auswahlliste. Hierzu wird in der Methode onCreate() eine Liste mit drei Elementen erzeugt und als ArrayAdapter an das ListFragment mit listAdapter = ArrayAdapter( ... ) übergeben. requireContext() stellt sicher, dass ein Kotext gesetzt ist. Andernfalls wird eine Ausnahme geworfen. Wird ein Listenelement angetippt, erscheint eine passende Detailansicht. Hierzu habe ich onListItemClick() überschrieben. Der zuletzt ausgewählte Eintrag wird in onSaveInstanceState() mit putInt() gespeichert und in onViewCreated() mit getInt() wiederhergestellt.

Das Anzeigen der Details-Seite (DetailsFragment) geschieht in der privaten Methode showDetails(). Falls das Fragment nicht in der Hauptaktivität angezeigt werden kann (in diesem Fall hat twoColumnMode den Wert false), erscheint es in einer eigenen mit startActivity() gestarteten Activity, nämlich der DetailsActivity. Um sie kümmern wir uns etwas später. Die Prüfung, ob der Zweispaltenmodus (also die Anzeige in der Hauptaktivität) aktiv ist, erfolgt in onViewStateRestored() mittels findViewById(). Damit das Details-Fragment eingebunden werden kann, muss es im Layout ein Element mit der ID container geben. Hierfür kann beispielsweise ein FrameLayout verwendet werden.

Die In-place-Anzeige des Fragments (twoColumnMode hat den Wert true) besteht aus folgenden Schritten: Als Erstes wird mit findFragmentById() geprüft, ob es aktuell zu sehen ist. Ist dies nicht der Fall, wird eine neue Instanz erzeugt. Außerdem übergebe ich dem Fragment den Index des Listenelement-Eintrags, den der Benutzer angeklickt hat. Er wird in DetailsFragment für die Anzeige verwendet. replace() ersetzt das aktuell angezeigte Fragment durch das eben instanziierte. Dies findet innerhalb einer Fragment-Transaktionsklammer (beginTransaction() und commit()) statt. setTransition() sorgt für einen plattformkonformen Übergang.

[+]  Tipp

Sie können mit listView.choiceMode = ... festlegen, wie viele Elemente gleichzeitig selektiert werden können. Wenn nur die Listenansicht zu sehen ist, sollte überhaupt kein Eintrag als markiert erscheinen (CHOICE_MODE_NONE). Stellt die App hingegen Auswahl und Details dar, ist es sinnvoll, genau einen (CHOICE_MODE_SINGLE) zu selektieren, nämlich den Eintrag, der die Details repräsentiert. Hierfür ist die Methode setItemChecked() zuständig.

package com.thomaskuenneth.androidbuch.fragmentdemo3

import android.content.Intent
import android.os.Bundle
import android.view.*
import android.widget.*
import androidx.fragment.app.*

private const val STR_LAST_SELECTED = "lastSelected"
class AuswahlFragment : ListFragment() {

private var twoColumnMode = false
private var lastSelected = 0

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
listAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_list_item_activated_1,
arrayOf("eins", "zwei", "drei")
)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (savedInstanceState != null) {
lastSelected = savedInstanceState.getInt(STR_LAST_SELECTED, 0)
}
}

override fun onListItemClick(l: ListView, v: View,
position: Int, id: Long) {
showDetails(position)
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(STR_LAST_SELECTED, lastSelected)
}

override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
activity?.run {
twoColumnMode =
findViewById<ViewGroup>(R.id.container) != null
if (twoColumnMode) {
listView.choiceMode = ListView.CHOICE_MODE_SINGLE
showDetails(lastSelected)
} else {
listView.choiceMode = ListView.CHOICE_MODE_NONE
}
}
}

private fun showDetails(index: Int) {
lastSelected = index
if (twoColumnMode) {
listView.setItemChecked(index, true)
fragmentManager?.run {
var details =
findFragmentById(R.id.container) as DetailsFragment?
if (details?.getIndex() ?: -1 != index) {
// neues Fragment passend zum selektierten
// Eintrag erzeugen und anzeigen
details = DetailsFragment()
val args = Bundle()
args.putInt(INDEX, index)
details.arguments = args
beginTransaction()
.replace(R.id.container, details)
// einen Übergang darstellen
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit()
}
}
} else {
val intent = Intent()
intent.setClass(requireActivity(),
DetailsActivity::class.java)
intent.putExtra(INDEX, index)
startActivity(intent)
}
}
}

Listing 4.20    Die Klasse »AuswahlFragment«

Die Klasse DetailsFragment ist wieder sehr einfach gehalten. In onCreateView() wird die Oberfläche erzeugt, sofern das Fragment in einem Layout enthalten ist. In diesem Fall ist der Methodenparameter container ungleich null. Was es anzeigt, ergibt sich aus einem Wert, der dem Fragment in der Eigenschaft arguments (android.os.Bundle) übergeben wird. Es handelt sich dabei um den Index des angeklickten Listenelements. Er bestimmt, welches »Detail« angezeigt werden soll.

package com.thomaskuenneth.androidbuch.fragmentdemo3

import android.os.Bundle
import android.view.*
import android.widget.*
import androidx.fragment.app.Fragment

const val INDEX = "index"
class DetailsFragment : Fragment() {

override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View? {
container?.let {
val text = TextView(context)
text.text = getString(R.string.template, 1 + getIndex())
val scroller = ScrollView(context)
scroller.addView(text)
return scroller
}
return null
}

fun getIndex(): Int {
return arguments?.getInt(INDEX, 0) ?: -1
}
}

Listing 4.21    Die Klasse »DetailsFragment«

Die Benutzeroberfläche der Hauptaktivität FragmentDemo3Activity wird in der Layoutdatei fragmentdemo3.xml definiert und sieht so aus:

<?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:baselineAligned="false"
android:orientation="horizontal"
android:weightSum="1.0">
<fragment
android:id="@+id/auswahl"
class="com.thomaskuenneth.androidbuch.fragmentdemo3.AuswahlFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.2" />
<FrameLayout
android:id="@+id/container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.8"
android:background="?android:attr/detailsElementBackground">
</FrameLayout>
</LinearLayout>

Listing 4.22    Die Layoutdatei »fragmentdemo3.xml«

Ein LinearLayout ordnet ein Fragment sowie ein FrameLayout horizontal an. Letzteres nimmt die Detailansicht auf. Auf diese Weise entsteht ein Zweispaltenlayout, das wunderbar auf Tablets zugeschnitten ist. Mit den Attributen android:weightSum und android:layout_weight können Sie übrigens die Größe (in meinem Fall die Breite) der beiden Kindelemente steuern. Die Summe der layout_weight-Werte aller Kinder muss dem Wert in weightSum entsprechen. Um den Hintergrund für Detailelemente so zu setzen, wie ihn auch Android darstellt, setzen Sie das Attribut android:background auf ?android:attr/detailsElementBackground.

Aber was geschieht eigentlich auf Smartphones? Müsste das Layout dort nicht eher wie in Listing 4.23 aussehen? Bei kleinen Bildschirmen ist doch gar kein Platz, um das FrameLayout und mit ihm das DetailsFragment zu beherbergen ...

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/auswahl"
class="com.thomaskuenneth.androidbuch.fragmentdemo3.AuswahlFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

Listing 4.23    Die Datei »fragmentdemo3.xml« (alternative Version)

Tatsächlich kommen beide Varianten in der App zum Einsatz, weil Android einen Mechanismus kennt, der je nach Bildschirmgröße und -format unterschiedliche Layoutdateien nutzt. Diesen Mechanismus sehen wir uns in Kapitel 5, »Benutzeroberflächen«, ausführlicher an. Fürs Erste müssen Sie nur wissen, dass alle Varianten den gleichen Dateinamen haben, aber in unterschiedlichen Verzeichnissen abgelegt werden. Das Zweispaltenlayout gehört in den Ordner layout-land. Hierhin gehören Layouts, die im Quermodus angezeigt werden sollen. Die Variante, die als Wurzelelement ein FrameLayout enthält, landet im »normalen« layout-Verzeichnis.

Fassen wir den bisherigen Stand kurz zusammen: Die Hauptaktivität lädt »nur« die Oberfläche, die sich im Hochkant- und Quermodus unterscheidet. Im ersten Fall wird nur ein AuswahlFragment sichtbar, im TwoColumnMode sind Auswahl und Details zu sehen. Die Klasse AuswahlFragment stellt eine Liste mit den drei Einträgen eins, zwei und drei dar. Im TwoColumnMode wird die zuletzt selektierte Zeile farbig hervorgehoben, und beim Drehen des Geräts wird dieser Wert als Instance State zwischengespeichert. Von zentraler Bedeutung ist showDetails(), denn diese Methode der Klasse AuswahlFragment kümmert sich um das Anzeigen der Details, entweder eingebettet in die aktuelle Activity oder in einer neu gestarteten. Hierfür wird der FragmentManager genutzt, den Sie in Abschnitt 4.3.2, »Ein Fragment in eine Activity einbetten«, kennengelernt haben.

Die letzte Klasse, die ich Ihnen vorstellen möchte, ist DetailsActivity (siehe Listing 4.24). Sie wird in der Methode showDetails() von AuswahlFragment gestartet, wenn der Bildschirm kein Zweispaltenlayout ermöglicht. Dann muss die Detailansicht in einer eigenen Activity angezeigt werden.

package com.thomaskuenneth.androidbuch.fragmentdemo3

import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class DetailsActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (resources.configuration.orientation
== Configuration.ORIENTATION_LANDSCAPE
) {
finish()
}
if (savedInstanceState == null) {
val details = DetailsFragment()
details.arguments = intent.extras
supportFragmentManager.beginTransaction()
.add(android.R.id.content, details).commit()
}
}
}

Listing 4.24    Die Klasse »DetailsActivity«

Der Ausdruck

resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE

prüft, ob sich die Activity tatsächlich darstellen muss oder sich wieder mit finish() beenden kann. Letzteres ist der Fall, wenn Sie im Emulator oder auf einem echten Gerät durch Drehen in den Quermodus wechseln. Wie dies aussehen kann, ist in Abbildung 4.12 dargestellt.

Die App »FragmentDemo3« im Quermodus

Abbildung 4.12    Die App »FragmentDemo3« im Quermodus

Im folgenden Abschnitt beschäftigen wir uns mit einem weiteren wichtigen Baustein von Apps, den Berechtigungen. Diese kommen ins Spiel, wenn Ihre App auf Systemressourcen, zum Beispiel die Kamera oder das Mikrofon, zugreifen oder Benutzerdaten wie Kontakte und Termine verarbeiten möchte.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
Zur Rheinwerk-Konferenz für Kotlin
 Buchempfehlungen
Zum Rheinwerk-Shop: Kotlin

Kotlin


Zum Rheinwerk-Shop: Praxisbuch Usability und UX

Praxisbuch Usability und UX


Zum Rheinwerk-Shop: Flutter und Dart

Flutter und Dart


Zum Rheinwerk-Shop: App-Design

App-Design


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2023
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern