Android_Tutorial_Lektion22_fotolia_RA_Studio_46292813

Programmier Tutorial: Apps für Android entwickeln – Teil 22: Mit einem SwipeRefreshLayout auf Benutzergesten reagieren

In den vorherigen drei Lektionen haben wir gelernt, wie man mit einer Android App auf Klicks der Benutzer reagiert. Dabei haben wir auch zwischen kurzen und langen Klicks unterschieden.

Nun werden wir uns einer weitere Art von Benutzereingaben zuwenden, den Gesten. Wir werden unsere Android App mit Hilfe eines SwipeRefreshLayouts auf die Wisch-Benutzergeste reagieren lassen.


Dazu werden wir zunächst im theoretischen Teil beschreiben was ein SwipeRefreshLayout eigentlich ist und wofür man es in einer Android App einsetzen kann. Wir werden zeigen welche Anforderungen erfüllt sein müssen und welche Schritte zur Implementierung erforderlich sind.

Danach werden wir einige Vorbereitungen am Quellcode unseres Android Projekts vornehmen, die für das Einfügen des SwipeRefreshLayouts notwendig sind. Dazu werden wir an den Dateien activity_main.xml und MainActivity.java kleine Änderungen vornehmen.

Anschließend fügen wir das SwipeRefreshLayout in das Layout unserer Anwendung ein und implementieren in der MainActivity-Klasse dessen Funktionalität, so dass unsere App in der Lage ist Benutzergesten zu erfassen und entsprechend darauf zu reagieren.

Abschließend werden wir unsere Android App im Emulator auf einem Android Virtual Device ausführen und die Funktionsweise des SwipeRefreshLayouts überprüfen.

1. Das SwipeRefreshLayout von Android

Das SwipeRefreshLayout ist, wie der Name schon erkennen lässt, ein Layout. Es wurde kurz vor Erscheinen von Android 5.0 der Android v4 Support Library hinzugefügt und ermöglicht das Verwenden des Swipe-to-Refresh Design Patterns, durch welches Nutzer mit einer vertikalen Wisch-Geste (swipe gesture) das Laden neuer Daten anfordern können.

Die Motivation hinter der Veröffentlichung des Layouts in der Support-Bibliothek war, dass die Benutzererfahrung für solche Swipe-to-Refresh Vorgänge auf allen Android Geräten, auch auf älteren, einheitlich sein sollte. Mit Erscheinen von Android API Version 29 wurde aus der Support-Bibliothek die AndroidX-Bibliothek. Seitdem befindet sich das SwipeRefreshLayout in der AndroidX Library.

Das Swipe-to-Refresh Pattern ist vollständig innerhalb des SwipeRefreshLayouts implementiert. Das Layout erkennt selbständig, ob eine vertikale Wisch-Geste ausgeführt wird, blendet daraufhin eine Fortschrittsanzeige ein und ruft die onRefresh() Callback-Methode auf, in welcher der Aktualisierungsvorgang gestartet werden kann.

Um ein SwipeRefreshLayout in der eigenen Android App zu verwenden, muss es in das Layout der Anwendung als Elternelement eines ListViews oder GridViews eingefügt werden. Zudem muss der Aktualisierungsvorgang implementiert werden, der durch die Swipe-Geste ausgelöst werden soll.

Somit besteht das Einfügen des SwipeRefreshLayouts aus folgenden Schritten:

  1. Einfügen des SwipeRefreshLayout-Widgets in das Layout der Activity – Das SwipeRefreshLayouts-Element wird als Elternelement des ListView-Elements in das Layout der Activity eingefügt. Es ist dabei darauf zu achten, dass es nur ein einziges Kindelement, den ListView, besitzt.

  2. Reagieren auf die Swipe-to-Refresh Geste mit einem OnRefreshListener – Implementieren eines OnRefreshListeners, durch den auf die Swipe-Geste reagiert wird. Der Listener erfasst die Geste und löst den Aktualisierungsvorgang aus. Dazu muss die Listener-Instanz zusätzlich für das SwipeRefreshLayout registriert werden.

1.1 Einfügen des SwipeRefreshLayout-Widgets in das Layout der Activity

Für das Einfügen eines SwipeRefreshLayouts in das Layout der eigenen Android App müssen einige Punkte beachtet werden. Das SwipeRefreshLayout muss das Elternelement eines ListView– oder GridView-Elements sein. Es darf nur ein einziges dieser Layout-Elemente als Kindelement in sich aufnehmen. In unserem Fall ist es das ListView-Element.

Würden mehrer Layout-Elemente in das SwipeRefreshLayout als Kindelemente eingefügt werden, würde die Wisch-Geste nicht optimal erkannt und die Scroll-Funktion unzuverlässig ausgeführt werden.

Weiterhin wird für das Verwenden des SwipeRefreshLayout die AndroidX SwipeRefreshLayout-Bibliothek benötigt. Diese AndroidX-Bibliothek muss daher dem Android Projekt als Dependency hinzugefügt werden. In unserem Fall geschieht dies automatisch, da wir die AndroidX AppCompat Library bereits in unserem Projekt eingebunden haben und diese selbst auf die AndroidX SwipeRefreshLayout-Bibliothek als Dependency verweist.

Mit folgendem XML-Code wird ein SwipeRefreshLayout als Elternelement eines ListViews verwendet:

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout_activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/listview_activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

Der oberen Code-Block kann nun an einer beliebigen Stelle in der Layout-Datei eingefügt werden. In unserem Fall wird das SwipeRefreshLayout mit dem ListView-Kindelement direkt nach der Toolbar eingefügt werden, wie wir später in der Lektion noch sehen werden.

Mit dem Attribut <android:id> wird eine Ressourcen-ID für das SwipeRefreshLayout-Element festgelegt. Dies ist zwingend erforderlich, damit später von der Activity aus auf das SwipeRefreshLayout-Objekt zugegriffen werden kann. Durch das Verwenden des @+ Symbols wird angezeigt, dass es sich im eine Ressourcen-ID handelt und diese von Android Studio automatisch angelegt werden soll.

Mit den Zeilen 4 und 5 wird festgelegt, dass Höhe und Breite des SwipeRefreshLayout den zur Verfügung stehenden Bildschirmplatz vollständig einnehmen sollen.

Das auf diese Weise hinzugefügte SwipeRefreshLayout ist zwar zu diesem Zeitpunkt bereits in das Layout der App integriert, es besitzt jedoch noch keine Funktionalität. Damit es auf die Swipe-Geste des Nutzers reagiert, muss nun als Nächstes ein OnRefreshListener implementiert und für das Layout registriert werden.

1.2 Reagieren auf die Swipe-to-Refresh Geste mit einem OnRefreshListener

Das eingefügte SwipeRefreshLayout kann die Swipe-Geste des Nutzers bereits erfassen und darauf die Fortschrittsanzeige einblenden. Der vom Layout umschlossene Inhalt wird jedoch noch nicht aktualisiert. Um dies zu ändern, muss das OnRefreshListener-Interface implementiert werden.

Das OnRefreshListener-Interface wird innerhalb der SwipeRefreshLayout-Klasse definiert und besitzt genau eine Methode, die onRefresh() Methode. Diese Methode wird vom Android Framework automatisch aufgerufen, sobald die Swipe-Down-Geste erkannt wird. Daher ist sie genau die Stelle im Quellcode, an welcher der Aktualisierungsvorgang gestartet werden muss.

Mit folgendem Quellcode wird das SwipeRefreshLayout.OnRefreshListener-Interface implementiert:

MainActivity.java

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout_activity_main);

    SwipeRefreshLayout.OnRefreshListener onRefreshListener;

    onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            // In der onRefresh() Methode sollte die Methode aufgerufen werden,
            // die das eigentliche Aktualisieren der Daten durchführt.
            refreshListView();
        }
    };

    mSwipeRefreshLayout.setOnRefreshListener(onRefreshListener);

    ...
}

Mit dem oberen Quellcode wird das OnRefreshListener-Interface implementiert und für das SwipeRefreshLayout registriert. Dabei wird die onRefresh() Methode überschrieben, in welcher die für das Aktualisieren der ListView-Daten verantwortliche Methode aufgerufen wird. In unserem Fall wäre dies die refreshListView() Methode.

In Zeile 5 wird eine Referenz auf die SwipeRefreshLayout-Instanz mit Hilfe der findViewById() Methode und Ressourcen-ID des SwipeRefreshLayout-Elements angefordert und in der Variable mSwipeRefreshLayout gespeichert. In Zeile 7 wir die Variable onRefreshListener vom OnRefreshListener Datentyp angelegt.

Anschließend wird in den Zeile 9 bis 16 das OnRefreshListener-Interface mit Hilfe einer anonymen Klasse implementiert und dabei gleichzeitig instanziiert. Eine Referenz auf die so erzeugte OnRefreshListener-Instanz wird in der Variable onRefreshListener gespeichert.

In Zeile 18 wird schließlich das erzeugte OnRefreshListener-Objekt für das SwipeRefreshLayout registriert. Dadurch wird das SwipeRefreshLayout vollständig aktiviert, so dass bei Swipe-Gesten des Benutzers die onRefresh() Callbak-Methode vom SwipeRefreshLayout automatisch aufgerufen wird.

Hinweis: Das eigentliche Aktualisieren der ListView-Daten wird nicht von der onRefresh() Methode durchgeführt, sondern von der von ihr aufgerufenen Methode. Auf diese Weise kann der Aktualisierungsvorgang auch von einer anderen Stelle angefordert werden, wie bspw. von einer Aktion der App Bar. Wie dies realisiert wird und was dabei zu beachten ist, zweigen wir später im praktischen Teil dieser Lektion.

Sobald die Aktualisierung der ListView-Daten abgeschlossen ist, muss setRefreshing(false) von der aktualisierenden Methode (hier die refreshListView() Methode) auf dem SwipeRefreshLayout-Objekt aufgerufen werden. Durch Aufrufen dieser Methode wird das SwipeRefreshLayout veranlasst die Fortschrittsanzeige auszublenden.

Mit folgendem Quellcode wird das SwipeRefreshLayout über das Beenden des Aktualisierungsvorgangs informiert:

MainActivity.java

private void refreshListView() {
    Collections.shuffle(mQuoteList);
    mQuoteArrayAdapter.notifyDataSetChanged();

    mSwipeRefreshLayout.setRefreshing(false);
}

In Zeile 5 des oberen Quellcodes wird das SwipeRefreshLayout über das Beenden des Aktualisierungsvorgangs informiert. Dazu wird die setRefreshing() Methode auf dem SwipeRefreshLayout-Objekt aufgerufen und erhält als Argument den Wert false übergeben. Das SwipeRefreshLayout blendet daraufhin die Fortschrittsanzeige, das Lade-Symbol, aus.

Wir haben nun erfahren was ein SwipeRefreshLayout in Android ist und wie es in das Layout einer Android App einbindet. Im nächsten Abschnitt werden wir zwei Dateien unseres Android Projekts überarbeiten und dadurch unsere App für das Einfügen des SwipeRefreshLayouts vorbereiten.

2. Den Quellcode unserer App vorbereiten

Wir werden nun einige Vorbereitungen am Quellcode unseres Android Projekts vornehmen, die für das Einfügen des SwipeRefreshLayouts notwendig sind. Dazu werden wir an den Dateien activity_main.xml und MainActivity.java kleine Änderungen vornehmen.

Und zwar werden wir den Hole Daten-Button aus unserer Android App entfernen, da wir das Holen neuer Daten von dem SwipeRefreshLayout übernehmen lassen wollen und daher den Button nicht mehr in unserer Anwendung benötigen.

Wir werden zuerst alle Zugriffe auf das Button-Element aus der MainActivity-Klasse entfernen und es danach aus der XML-Layout Ressourcen Datei activity_main.xml löschen. Anschließend werden wir unsere Anwendung im Android Emulator ausführen und prüfen, ob die Vorbereitungsmaßnahmen erfolgreich durchgeführt wurden.

Wir werden somit die folgenden beiden Schritte durchführen:

  1. Entfernen der Zugriffe auf das Button-Element aus der MainActivity.java Klassendatei.
  2. Entfernen des Button-Elements aus der activity_main.xml Layout-Datei.

2.1 Entfernen der Button-Zugriffe aus dem Quellcode der MainActivity-Klasse

Das Laden neuer Daten, genauer gesagt neuer Zitate, für den ListView soll zukünftig vom SwipeRefreshLayout übernommen werden. Aus diesem Grund wird der Hole Daten-Button nicht mehr in unserer App benötigt. Wir werden daher alle Zugriffe auf das Button-Element aus dem Quellcode der MainActivity-Klasse entfernen.

Um die notwendigen Änderungen durchzuführen, öffnen wir die Klassendatei MainActivity.java im Editor von Android Studio. Dazu klicken wir doppelt auf ihren Dateinamen im Project Tool Window. Die Klassendatei befindet sich im Package-Ordner de.codeyourapp.zitate unseres Projekts.

Der aktuelle Quellcode der MainActivity.java Datei ist unten aufgeführt:

An dieser Stelle endet der freie Inhalt dieser Lektion. Wir hoffen, sie hat dir bis hierher gefallen! Du kannst sie im geschützten Bereich von ProgrammierenLernenHQ fortsetzen, in welchem sich alle Lektionen unserer Android Online-Kurse befinden.

Unsere Android Kurse bestehen aus insgesamt 43 großen Lektionen und sind unterteilt in 13 frei zugängliche und 30 Premium-Lektionen. Die Premium-Lektionen befinden sich in dem geschützten Bereich und sind nur für Käufer unseres Android Online-Kurs Gesamtpaket zugänglich.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813 An dieser Stelle endet der freie Inhalt dieser Lektion. Wir hoffen, sie hat dir bis hierher gefallen! Du kannst sie durch Kauf unseres Android Online-Kurs Gesamtpakets freischalten.

In unserem Android Online-Kurs Gesamtpaket befinden sich 43 große Lektionen, in denen wir dir schrittweise zeigen, wie voll funktionstüchtige Android Apps programmiert werden.

Diese Lektion ist Teil unseres Android Gesamtpakets. Insgesamt sind unsere Online-Kurse unterteilt in 13 frei zugängliche und 30 Premium-Lektionen.

Die Premium-Lektionen befinden sich in dem geschützten Bereich und sind nur für Käufer des Android Online-Kurs Gesamtpakets zugänglich.



Welche Inhalte befinden sich im Android Online-Kurs Gesamtpaket?

  • Das Gesamtpaket enthält alle Android Online-Kurse von ProgrammierenLernenHQ.
  • Im Paket enthalten ist unser großer Android Apps Programmieren Online-Kurs. Er ist unser Hauptkurs und besteht aus 35 großen Lektionen. Die Grundlagen der Android App Entwicklung praxisnah und verständlich zu lehren, ist das Hauptziel des Android Apps Programmieren Kurses.
  • Im Paket enthalten ist auch unser SQLite Datenbank App Programmieren Online-Kurs. Dieser Spezialkurs besteht aus 8 großen Lektionen und ist als weiterführender Kurs konzipiert worden. Der Kurs schließt an unseren Android Apps Programmieren Hauptkurs an und widmet sich dem speziellen Thema der SQLite Datenbanken.
  • Durch den Kauf erhältst du unbegrenzten Zugang zu allen Inhalten unseres Android Online-Kurs Gesamtpakets. Wir werden in Zukunft weitere Lektionen hinzufügen. Auch auf alle zukünftigen Lektionen erhältst du vollen Zugriff.

Wir hoffen, Dich bald als neuen Kursteilnehmer unserer Android Online-Kurse begrüßen zu dürfen!

Einmal kaufen und dadurch zeitlich unbegrenzten Zugriff auf alle Inhalte unseres Android Online-Kurs Gesamtpakets erhalten.



Hinweis: Der untere Quellcode ist Teil des geschützten Bereichs von ProgrammierenLernenHQ. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte.
Erfahre mehr über unsere Android Online-Kurse.

android_programmieren_lernen_blurry_sourcecode

Der obere Quellcode ist Teil des geschützten Bereichs. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte. Klicke auf die Info-Box, um mehr zu erfahren.

Wir löschen jetzt die markierten Zeilen aus dem Quellcode der MainActivity.java Klassendatei.

Zuerst entfernen wir Zeile 41, in der die registerButtonListener() Methode aufgerufen wird. Anschließend löschen wir die Zeilen 101 bis 112, in denen die registerButtonListener() Methode definiert wird. Weitere Vorbereitungen müssen nicht getroffen werden, so dass wir im nächsten Abschnitt mit dem Einfügen des SwipeRefreshLayouts beginnen können.

In Android Studio sollte die MainActivity.java Klassendatei nun wie folgt aussehen:

android_programmieren_lernen_blurry_sourcecode

Der obere Quellcode ist Teil des geschützten Bereichs. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte. Klicke auf die Info-Box, um mehr zu erfahren.

Alle Quellcode-Zeilen aus der MainActivity-Klasse, die sich auf das Button-Element beziehen, wurden nun entfernt. Als Nächstes müssen wir noch das Button-Element aus dem Layout unserer App entfernen.

2.2 Entfernen des Button-Elements aus dem Layout unserer Android App

Die Zugriffe auf das Button-Element haben wir nun aus entfernt. Die Vorbereitungsmaßnahmen sind dadurch aber noch nicht abgeschlossen. Wir müssen als Nächstes den Hole Daten-Button aus dem Layout unserer Android App löschen.

Dazu öffnen wir die Layout-Datei activity_main.xml im Editor von Android Studio, indem wir doppelt auf ihren Dateinamen im Project Tool Window klicken. Die XML-Datei befindet sich im Ressourcen-Ordner res/layout/ unseres Projekts.

Der aktuelle Quellcode der activity_main.xml XML-Datei ist unten aufgeführt:

activity_main.xml

<?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"
    android:background="@color/colorDivider">

    <androidx.appcompat.widget.Toolbar
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/toolbar_main"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:elevation="@dimen/toolbar_elevation"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <Button
        android:id="@+id/button_activity_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/default_margin"
        android:textSize="@dimen/default_font_size"
        android:text="@string/text_button_activity_main" />

    <ListView
        android:id="@+id/listview_activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginEnd="@dimen/default_margin"
        android:layout_marginStart="@dimen/default_margin"
        android:divider="@color/colorDivider"
        android:dividerHeight="@dimen/default_margin" />

</LinearLayout>

In dem oberen Quellcode sind die Zeilen 18 bis 24, in denen das Button-Element definiert wird, grau markiert. Diese Zeilen werden wir nun löschen. Das Layout der MainActivity wird dann nur noch aus einem LinearLayout-Wurzelelement bestehen, welches ein Toolbar– und ein ListView-Element als Kindelemente im Inneren enthält.

In Android Studio sollte die activity_main.xml Layout-Datei nun wie folgt aussehen:

swipe_layout_vorbereitung

Der Hole Daten Button wurde aus dem Layout der MainActivity entfernt

In der oberen Abbildung ist die überarbeitete Layout-Datei activity_main.xml dargestellt. Das nicht mehr benötigte Button-Element wurde bereits entfernt.

Die Vorbereitungsmaßnahmen sind nun abgeschlossen. Bevor wir jedoch mit dem Implementieren des SwipeRefreshLayout beginnen, werden wir unsere Android App noch einmal zur Kontrolle ausführen und prüfen, ob das Button-Element ordnungsgemäß aus dem Layout entfernt wurde.

2.3 Ausführen und Testen unserer Android App

Wir werden nun unserer Android App auf einem Android Virtual Device im Emulator ausführen lassen. Auf diese Weise können wir direkt überprüfen, ob unsere App korrekt funktioniert und der Button ordnungsgemäß aus dem Layout der Activity entfernt worden ist.

Unsere App starten wir dazu wie gewohnt über den Run > Run 'app' Menüeintrag, den wir über die obere Menüleiste erreichen.

Unsere Android App wird auf dem virtuellen Gerät wie folgt angezeigt:

screenshot_swipe_vorbereitung_3a

Das Button-Element wurde aus der Layout der MainActivity entfernt

In der oberen Abbildung ist der Startbildschirm, die Start-Activity, unserer App dargestellt. Er besteht nun nur noch aus der App Bar, die als Toolbar implementiert ist und dem ListView. Das Button-Element wurde korrekt aus dem Layout der Activity entfernt.

Im nächsten Abschnitt werden wir das SwipeRefreshLayout implementieren. Es wird die Hole Daten-Funktion des eben entfernten Button-Elements übernehmen.

3. Das SwipeRefreshLayout in unsere Android App einfügen

Wir werden nun ein SwipeRefreshLayout in unsere Anwendung einfügen. Wie wir bereits im theoretischen Teil dieser Lektion erfahren haben, verläuft dieser Vorgang zweistufig. Zuerst wird ein SwipeRefreshLayout-Element in das Layout der MainActivity eingebunden. Danach wird die Funktionalität des SwipeRefreshLayout in der MainActivity-Klasse implementiert, so dass vom eingefügten Layout-Element auf die Swipe-Geste reagiert werden kann.

Somit besteht das Einfügen des SwipeRefreshLayouts aus folgenden Schritten:

  1. Einfügen des SwipeRefreshLayout-Elements in das Layout der MainActivity – Das SwipeRefreshLayouts-Element wird als Elternelement des ListView-Elements in das Layout der MainActivity eingefügt. Es ist dabei darauf zu achten, dass es nur ein einziges Kindelement, den ListView besitzt.

  2. Implementieren der SwipeRefreshLayout-Funktionalität in der MainActivity-Klasse – Implementieren eines OnRefreshListeners in der MainActivity-Klasse, mit dessen Hilfe das SwipeRefreshLayout auf die Swipe-Geste reagieren kann. Der Listener erfasst die Geste und löst den Aktualisierungsvorgang der ListView-Daten aus. Dazu muss die Listener-Instanz zusätzlich für das SwipeRefreshLayout registriert werden.

3.1 Einfügen des SwipeRefreshLayout-Elements in das Layout der MainActivity

Das Laden neuer Daten, genauer gesagt neuer Zitate, für den ListView soll zukünftig vom SwipeRefreshLayoutWidget übernommen werden. Dazu muss das Widget als Elternelement des ListViews in das Layout der MainActivity eingefügt werden.

Dies werden wir nun durchführen, daher öffnen wir die Layout-Datei activity_main.xml im Editor von Android Studio, indem wir doppelt auf ihren Dateinamen im Project Tool Window klicken. Die XML-Datei befindet sich im Ressourcen-Ordner res/layout/ unseres Projekts.

In den bisherigen Quellcode der activity_main.xml XML-Datei fügen wir die markierten Zeilen ein:

activity_main.xml

<?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"
    android:background="@color/colorDivider">

    <androidx.appcompat.widget.Toolbar
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/toolbar_main"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:elevation="@dimen/toolbar_elevation"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh_layout_activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/listview_activity_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginEnd="@dimen/default_margin"
            android:layout_marginStart="@dimen/default_margin"
            android:divider="@color/colorDivider"
            android:dividerHeight="@dimen/default_margin" />

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</LinearLayout>

In dem oberen XML-Code der activity_main.xml ist das SwipeRefreshLayout-Element bereits eingefügt worden. Das SwipeRefreshLayout-Element umschließt dabei das ListView-Element. Dadurch wird der ListView zum Kindelement des SwipeRefreshLayout. Weitere Kindelemente dürfen wir dem SwipeRefreshLayout nicht hinzufügen, da dies negative Auswirkungen auf die Gestenerkennung und somit Benutzererfahrung haben würde.

In den Zeilen 18 bis 21 definieren wir mit dem öffnenden Tag <androidx.swiperefreshlayout.widget.SwipeRefreshLayout> das SwipeRefreshLayout-Element. Es erhält als Bezeichner seiner Ressourcen-ID den Wert swipe_refresh_layout_activity_main zugewiesen, über den wir im Quellcode auf das Element zugreifen können.

Weiterhin wird für die Breite und Höhe des SwipeRefreshLayouts der Wert match_parent vorgegeben. Somit wird das SwipeRefreshLayouts-Element den gesamten, vom Elternelement (dem LinearLayout), zur Verfügung gestellten Bildschirmplatz einnehmen. In der Praxis wird dies der gesamte Bereich unterhalb der Toolbar sein.

In Zeile 32 wird mit dem schließenden Tag </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> die Definition des SwipeRefreshLayout-Elements abgeschlossen. Hier muss unbedingt beachtet werden, dass das ListView-Element zwischen dem öffnenden und schließenden Tag des SwipeRefreshLayout-Elements eingeschlossen ist. Das ListView-Element muss unbedingt das Kindelement des SwipeRefreshLayout-Elements sein.

In Android Studio sollte die activity_main.xml Layout-Datei nun wie folgt aussehen:

swipe_layout_added

Das SwipeRefreshLayout wurde als Elternelement des ListViews dem Layout der MainActivity hinzugefügt

In der oberen Abbildung ist die überarbeitete Layout-Datei activity_main.xml dargestellt. Das SwipeRefreshLayout-Element wurde bereits in das Layout der MainActivity eingefügt. Es enthält im inneren das ListView-Element unserer Anwendung. Dies muss auch unbedingt genau so angeordnet sein, damit das SwipeRefreshLayout ordnungsgemäß funktioniert.

Durch das Einfügen des SwipeRefreshLayout-Elements werden bereits jetzt schon Gesten des Benutzers von unserer Android App erfasst, jedoch wird noch nicht korrekt darauf reagiert. Dies werden wir nun ändern und die Funktionalität des hinzugefügten SwipeRefreshLayouts in der MainActivity-Klasse implementierten.

3.2 Implementieren der SwipeRefreshLayout-Funktionalität in der MainActivity-Klasse

Wir werden nun die Funktionalität des eben eingefügten SwipeRefreshLayouts implementieren. Dazu werden wir am Quellcode der MainActivity-Klasse folgende Änderungen vornehmen:

  • Importieren der SwipeRefreshLayout-Klasse.
  • Anlegen der Membervariable mSwipeRefreshLayout.
  • Definieren der initSwipeRefreshLayout() Methode.
  • Einfügen des Methodenaufrufs initSwipeRefreshLayout() in der onCreate() Methode.
  • Überarbeiten der refreshListView() Methode, so dass die Fortschrittsanzeige ausgeblendet wird.
  • Überarbeiten der onOptionsItemSelected() Methode, so dass Refresh-Vorgang auch über die App Bar ausführbar ist.

Um die notwendigen Änderungen durchzuführen, öffnen wir die Klassendatei MainActivity.java im Editor von Android Studio. Dazu klicken wir doppelt auf ihren Dateinamen im Project Tool Window. Die Klassendatei befindet sich im Package-Ordner de.codeyourapp.zitate unseres Projekts.

In dem unten aufgeführten Quellcode der MainActivity.java Datei sind die Änderungen bereits durchgeführt worden:

android_programmieren_lernen_blurry_sourcecode

Der obere Quellcode ist Teil des geschützten Bereichs. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte. Klicke auf die Info-Box, um mehr zu erfahren.

An oben aufgeführtem Quellcode der MainActivity-Klasse wurden an insgesamt sechs verschiedenen Stellen Änderungen durchgeführt. Wir werden diese nun der Reihenfolge nach durchgehen und dabei die jeweiligen Codezeilen erläutern.

Hinweis: Manchmal tritt an dieser Stelle ein Fehler in Android Studio auf, bei dem die Ressourcen-IDs R.id.toolbar_main, R.id.listview_activity_main und R.id.swipe_refresh_layout_activity_main nicht korrekt aufgelöst werden können. Dieser Fehler kann einfach behoben werden, indem das Android Projekt über die obere Menüleiste mittels File > Close Project geschlossen und anschließend wieder vom Android Studio Willkommensbildschirm geöffnet wird.

In Zeile 4 wird die SwipeRefreshLayout-Klasse mittels Import-Anweisung innerhalb der MainActivity-Klasse sichtbar gemacht, so dass wir sie ohne den Namen ihres Packages verwenden können.

In Zeile 31 wird die Membervariable mSwipeRefreshLayout angelegt. In ihr werden wir die Referenz auf die SwipeRefreshLayout-Instanz speichern. Wir haben sie als Membervariable der MainActivity-Klasse angelegt, damit wir innerhalb der gesamten Klasse auf sie zugreifen können.

In Zeile 39 rufen wir die initSwipeRefreshLayout() Methode innerhalb der onCreate() Methode auf, in der wir das SwipeRefreshLayout initialisieren. Die Definition der initSwipeRefreshLayout() Methode erfolgt weiter unten im Quellcode.

In den Zeilen 58 und 59 haben wir eine Änderung an der onOptionsItemSelected() Methode vorgenommen. Und zwar lassen wir vom SwipeRefreshLayout die Fortschrittsanzeige einblenden, wenn Action-Button (Hole Daten) der App Bar angeklickt wird. Dadurch ist es möglich auch über den Action-Button der App Bar den Aktualisierungsvorgang zu starten. Dies ist besonders für Nutzer wichtig, die aufgrund von Geräteeinschränkungen die Swipe-Geste nicht ausführen können.

In den Zeilen 82 bis 95 wird die initSwipeRefreshLayout() Methode definiert. Auf sie werden wir weiter unten genauer eingehen.

In den Zeilen 166 und 167 haben wir eine Änderung an der refreshListView() Methode vorgenommen. Die Änderung ist erforderlich, da das SwipeRefreshLayout über das Beenden des Aktualisierungsvorgangs informiert werden muss. Dies wird durch Aufrufen von setRefreshing(false) auf dem SwipeRefreshLayout-Objekt durchgeführt, woraufhin das SwipeRefreshLayout die Fortschrittsanzeige ausblendet. Weiterhin wird eine Toast-Meldung ausgegeben, die den Nutzer über die erfolgte Aktualisierung informiert.

Die initSwipeRefreshLayout() Methode

Wie bereits weiter oben erwähnt, wird in den Zeilen 82 bis 95 der MainActivity-Klasse die initSwipeRefreshLayout() Methode definiert. Ihre Aufgabe ist es, das SwipeRefreshLayout zu initialisieren. Wie dies erfolgt, werden wir nun ausführlich beschreiben. Die Methode ist daher zur Übersichtlichkeit an dieser Stelle nochmals aufgeführt:

private void initSwipeRefreshLayout() {
    mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout_activity_main);

    SwipeRefreshLayout.OnRefreshListener onRefreshListener;

    onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            refreshListView();
        }
    };

    mSwipeRefreshLayout.setOnRefreshListener(onRefreshListener);
}

Aufgabe der initSwipeRefreshLayout() Methode ist, das SwipeRefreshLayout für die MainActivity zu initialisieren. Dazu muss das SwipeRefreshLayout.OnRefreshListener-Interface implementiert und eine Instanz davon für das SwipeRefreshLayout registriert werden.

In Zeile 2 wird eine Referenz auf das SwipeRefreshLayout-Objekt angefordert und in der Membervariable mSwipeRefreshLayout gespeichert. Dazu wird mit der findViewById() Methode und der entsprechenden Ressourcen-ID nach dem SwipeRefreshLayout-Objekt gesucht.

Anschließend wird in Zeile 4 die Variable onRefreshListener vom Typ SwipeRefreshLayout.OnRefreshListener deklariert.

In den Zeilen 6 bis 11 wird das SwipeRefreshLayout.OnRefreshListener-Interface mit Hilfe einer anonymen Klasse implementiert und direkt instanziiert. Für die Implementierung wird die onRefresh() Methode des Interfaces ausprogrammiert, in der die refreshListView() Methode der MainActivity-Klasse aufgerufen wird. Die onRefresh() Methode wird vom Android Framework automatisch aufgerufen, sobald eine Swipe-Down Geste erkannt wurde.

Abschließend wird in Zeile 13 das erzeugte OnRefreshListener-Objekt für das SwipeRefreshLayout registriert. Dies geschieht mit Hilfe der setOnRefreshListener() Methode, die auf der SwipeRefreshLayout-Instanz aufgerufen wird und als Argument das OnRefreshListener-Objekt erhält.

Auf diese Weise haben wir nun das SwipeRefreshLayout in unsere Android App eingefügt und implementiert. Somit sind alle Änderungen an der MainActivity-Klasse und ihrer XML-Layout Ressourcen-Datei abgeschlossen.

In Android Studio sollte die MainActivity.java Klassendatei nun wie folgt aussehen:

android_programmieren_lernen_blurry_sourcecode

Der obere Quellcode ist Teil des geschützten Bereichs. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte. Klicke auf die Info-Box, um mehr zu erfahren.

In der oberen Abbildung ist die überarbeitete MainActivity.java Klassendatei dargestellt. Der Quellcode wurde an 6 Stellen erweitert. Welche Bedeutung der jeweilige Code-Block besitzt, ist in der unteren Liste angegeben:

  1. A – Importieren der Klasse SwipeRefreshLayout, um sie in der MainActivity-Klasse sichtbar zu machen.

  2. B – Anlegen der Membervariable mSwipeRefreshLayout, in der das SwipeRefreshLayout-Objekt gespeichert wird.

  3. C – Aufrufen der initSwipeRefreshLayout() Methode innerhalb der onCreate() Methode.

  4. D – Anpassen der onOptionsItemSelected() Methode, so dass die Fortschrittsanzeige auch angezeigt wird, wenn die Aktualisierung mittels Action-Button über die App Bar gestartet wird.

  5. E – Definieren der initSwipeRefreshLayout() Methode, in der das OnRefreshListener-Interface implementiert und eine Instanz davon erzeugt wird. Die OnRefreshListener-Instanz wird anschließend für das SwipeRefreshLayout registriert. Somit ist das SwipeRefreshLayout vollständig aktiviert und neue ListView-Daten können mittels der Swipe-Geste angefordert werden.

  6. F – Anpassen der refreshListView() Methode, so dass nach Abschließen des Aktualisierungsvorgangs der ListView-Daten das SwipeRefreshLayout-Objekt darüber mit Hilfe des Methodenaufrufs setRefreshing(false) informiert wird. Woraufhin die Fortschrittsanzeige vom SwipeRefreshLayout ausgeblendet wird. Weiterhin wird eine Toast-Meldung ausgegeben, die den Nutzer über die erfolgte Aktualisierung informiert.

Wir haben nun das SwipeRefreshLayout unserer Android App hinzugefügt und einen OnRefreshListener registriert. Unsere App ist dadurch in der Lage Swipe-Gesten zu erfassen und entsprechend darauf zu reagieren.

Führt der Benutzer die Swipe-Down Geste durch, wird dies vom Android Framework erkannt und die Callback-Methode onRefresh() (über die onAnimationEnd() Methode der SwipeRefreshLayout-Klasse) aufgerufen. In der onRefresh() Methode behandeln wir die Swipe-Geste, indem wir die refreshListView() Methode aufrufen lassen.

Wie dies zur Laufzeit auf einem Android Gerät aussieht, werden wir im nächsten Abschnitt erfahren.

4. Ausführen und Testen unserer Android App

Wir werden nun unserer Android App auf einem Android Virtual Device im Emulator ausführen lassen. Auf diese Weise können wir direkt überprüfen, wie unsere App reagiert, wenn die Swipe-Down Geste vom Benutzer durchgeführt wird.

Unsere App starten wir dazu wie gewohnt über den Run > Run 'app' Menüeintrag, den wir über die obere Menüleiste erreichen.

In der unteren Abbildung ist unsere Android App auf dem virtuellen Gerät zu sehen:

screenshot_swipe_added

Die Swipe-Down Geste wurde ausgeführt, woraufhin das SwipeRefreshLayout das Lade-Symbol einblendet

Es wurde gerade die Swipe-Geste durchgeführt, was an dem markierten Lade-Symbol des SwipeRefreshLayouts zu erkennen ist. Dazu wurde der ListView ganz nach oben gescrollt und anschließend mit einem Finger nochmals nach oben gescrollt. Diese Benutzereingabe erkennt das Android Framework als Swipe-to-Refresh Geste und blendet daraufhin das Lade-Symbol, die Fortschrittsanzeige des SwipeRefreshLayouts, ein.

Das Lade-Symbol wird im oberen Bereich des SwipeRefreshLayouts, direkt unter der App Bar, mittig eingeblendet und bewegt sich entsprechend unserer Fingerbewegung.

Der Aktualisierungsvorgang startet, wenn der Pfeil des Lade-Symbols schwarz ist und wir den Finger vom Display unseres Android Geräts lösen. Tun wir dies, wird die onRefresh() Methode aufgerufen, von der aus das Aktualisieren der ListView-Daten angeordnet wird. Sobald die Daten neu geladen worden sind, wird eine kurze Mitteilung in Form eines Toasts auf dem Display ausgegeben.

Zusammenfassung

Wie in Android auf Klicks des Benutzers reagiert wird, hatten wir bereits in den vorherigen drei Lektionen kennengelernt und dabei auch das Ereignisbehandlungssystem von Android ausführlich betrachtet. In dieser Lektion haben wir dann eine weitere Art der Benutzereingabe kennengelernt, die Steuerung einer Android Anwendung mit Hilfe von Gesten.

Im theoretischen Teil dieser Lektion haben wir erfahren, wie mit Hilfe eines SwipeRefreshLayout auf Gesten des Benutzers reagiert wird. Danach haben wir einige Vorbereitungen am Quellcode unseres Android Projekts vorgenommen, die für das Einfügen des SwipeRefreshLayouts notwendig waren. Dazu wurden an den Dateien activity_main.xml und MainActivity.java kleine Änderungen vorgenommen.

Anschließend haben wird das SwipeRefreshLayout in das Layout unserer Anwendung eingefügt und in der MainActivity-Klasse dessen Funktionalität implementiert, so dass unsere App nun in der Lage ist Benutzergesten zu erfassen und entsprechend darauf zu reagieren.

Abschließend haben wir unsere Android App im Emulator auf einem Android Virtual Device ausgeführt und die korrekte Funktionsweise des SwipeRefreshLayouts überprüft.

Weiterführende Literatur




Comments 13

  1. Hallo,

    ich habe das gleich Problem wie Steffi, Sepp und Hans-Jürgen. Ich arbeite in onCreateView() und die XML ist augenscheinlich richtig.

    Gibt es eine Lösung?

    Mit freundlichen Grüßen

    Daniel

    1. Post
      Author

      Hallo Daniel,

      ich habe die Lektion 14 gerade noch einmal selbst nachprogrammiert. Auch bei mir wurde die Zeile:

      mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout_aktienliste);
      

      rot von Android Studio markiert und das, obwohl im gesamten Projektquellcode kein Fehler vorlag. Ich ließ die App von Android Studio trotz der Warnung kompilieren und zusammenbauen. Bei diesem Prozess, konnte Android Studio das Problem selbständig beheben und die ID korrekt auflösen.

      Vielleicht hilft dir diese Vorgehensweise weiter. Hoffentlich 🙂

      Viele Grüße, Chris

  2. Hallo,

    ich hatte auch das Problem, daß mir “swipe_refresh_layout_aktienliste” rot angezeigt wurde.
    Der Fehler lag bei mir in der Datei „fragment_aktienliste.xml“, in der ein „>“ zuviel war.

    Fehlerhaft:
    tools:context=“.AktienlisteFragment“>
    android:id=“@+id/swipe_refresh_layout_aktienliste“>

    Richtig:
    tools:context=“.AktienlisteFragment“
    android:id=“@+id/swipe_refresh_layout_aktienliste“>

  3. Hi,
    ich hatte das Problem auch. Das lag daran, dass ich den Code in „onCreate“ anstatt in „onCreateView“ eingefügt hatte.

    Ich hoffe, ich konnte helfen

    1. Post
      Author
  4. Hallo Chris,

    habe genau dasselbe Problem wie Sepp (von 2016/12/12 at 21:49). Gibt es dazu eine Lösung ?

    Schöne Grüße
    Steffi

    1. Post
      Author

      Hallo Steffi,

      bisher habe ich von Sepp kein Feedback erhalten. Ob er es lösen konnte weiß ich daher leider nicht.

      Wenn du möchtest, kannst Du mir deine Projektdateien (den ganzen Android Studio Projektordner, aber OHNE die build-Unterordner) als ZIP per E-Mail zusenden. Die E-Mail Adresse kannst Du im Impressum finden. Ich schaue dann mal, ob ich etwas über das Swipe-Problem herausfinden kann.

      Viele Grüße, Chris

  5. Hi Chris,

    Ich hänge gerade an der swipe-refresh-Funktion.
    in der Zeile „rootView.findViewById(R.id.swipe_refresh_layout_aktienliste);“ der AktienlisteFragment.java wird mir „swipe_refresh_layout_aktienliste“ rot angezeigt mit den Worten Cannot resolve symbol ’swipe_…‘

    ich vermute das soll nicht so sein…? Ich finde jedenfalls den Fehler nicht. Habe für das File deinen oben angegeben gesamten Quellcode kopiert und bei mir eingefügt und die anderen Schritte überprüft. Kann mir vielleicht im SDK-Manager ein File konkret für die Swipe-Funktion fehlen?

    Hoffe du kannst mir helfen.
    adventliche Grüße…

    1. Post
      Author

      Hallo Sepp,

      besteht das Problem noch oder konntest du es mittlerweile lösen?

      Viele Grüße, Chris

  6. Hallo,

    zunächst einmal vielen Dank für dieses Ausführliche und Lehrreiche Tutorial. Auch ohne Java-Kenntnisse werden diese hier ebenfalls weitergegeben 🙂

    Nach Absolvierung des 14.Kapitels bemerkte ich beim testen der SwipeRefresh-Methodik einen Crash der App.
    Zunächst wusste ich nicht warum, aus dem Fehlercode von Android Studio wurde ich auch nicht schlau.

    Nach ein wenig gedebugge jedoch denke ich den Fehler gefunden zu haben:

    Die „zusammen gesetzte Anzeigestring“-Variable

    [„https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20csv%20where%20url=’http://download.finance.yahoo.com/d/quotes.csv?s=BMW.DE,DAI.DE,VOW3.DE,SAP.DE,Sie.de,%255EGDAXI,ALV.DE%26f=snc4xl1d1t1c1p2ohgv%26e=.csv‘
    %20and%20columns=’symbol,name,currency,exchange,price,date,time,change,percent,open,high,low,volume‘
    &diagnostics=true“]

    endet von yahoo aus manchmal mit dem http-status-code=“400″ welcher auf einen Bad Request hinweist.

    Kannst du das, Chris, bestätigen oder befindet sich der Fehler woanders?

    1. Post
      Author

      Hallo Razgriz 🙂

      danke für deinen sehr nützlichen Hinweis.

      Ja es stimmt. Yahoo sendet ab und zu einen „leeren“ String, also ohne sinnvolle Finanzdaten, zurück. Dann stürzt die App ab, da wir keine Ausnahmenbehandlung implementiert haben. Bei einer realen App, wäre dies fatal. Hier wurde aber aus Platzgründen, um den Quellcode so kurz wie möglich zu halten, darauf verzichtet. Dadurch aber in Kauf genommen, dass es schnell zu Abstürzen der App bei den Lesern kommt. Sorry!

      Viele Grüße, Chris

  7. Hallo,

    an erster Stelle einmal vielen Dank für dieses außerordentlich detailreiche Tutorial.

    Nach diesem Kapitel (14), klappt auch alles wie immer einwandfrei was die App betrifft.
    Lediglich tritt sporadisch ein Fehler auf, welches die App zum Absturz zwingt.
    Der Fehler an sich tauchte erst nach den Code-Änderungen von Kapitel 14 auf. Wie gesagt sporadisch, sodass es manchmal klappt, manchmal nicht.
    Besagter Fehler tritt beim Aktualisieren auf, wobei es egal ist ob über den Aktalisieren-Button oder der Swipe-Geste.

    Folgend einmal der Fehler aus logcat:

    10-10 10:03:11.260 13652-15204/de.programmierenlernenhq.aktiehq.app E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #2
    Process: de.programmierenlernenhq.aktiehq.app, PID: 13652
    java.lang.RuntimeException: An error occurred while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:309)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
    at java.util.concurrent.FutureTask.run(FutureTask.java:242)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
    Caused by: java.lang.NullPointerException: Attempt to invoke interface method ‚org.w3c.dom.NodeList org.w3c.dom.Node.getChildNodes()‘ on a null object reference
    at de.programmierenlernenhq.aktiehq.app.AktienlisteFragment$HoleDatenTask.leseXmlAktiendatenAus
    (AktienlisteFragment.java:205)
    at de.programmierenlernenhq.aktiehq.app.AktienlisteFragment$HoleDatenTask.doInBackground
    (AktienlisteFragment.java:319)
    at de.programmierenlernenhq.aktiehq.app.AktienlisteFragment$HoleDatenTask.doInBackground
    (AktienlisteFragment.java:177)
    at android.os.AsyncTask$2.call(AsyncTask.java:295)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)

  8. Pingback: Android Tutorial: Der ShareActionProvider in Android

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.