So schön auch so ein einfacher Bildschirm mit einem Button auch sein mag, kommen wir relativ schnell an die Grenzen des Anzeigbaren. Es wird höchste Zeit zwischen Bildschirmen navigieren zu können.

Da sie zur Zeit einigermaßen Hip ist, werden wir dazu eine BottomNavigation einbauen.

Die BottomNavigationBar, die wir verwenden, werden ist Teil der Design Library, die wir im vorherhigen Kapitel bereits eingebunden haben. Sie ist kompatibel mit der Navigation Library, die uns im Folgenden das Leben erheblich vereinfachen wird.
Zunächst binden wir daher die Navigation Library in das build.gradle-File des App Moduls ein:

def nav_version = "2.1.0"

implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

Bisher haben wir nur von Activities gehört, die einen einzelnen Bildschirm anzeigen können. Zu Beginn der Androidentwicklung war das auch so gut wie die einzige Art weitere Ansichten zu bauen.
Doch Activities haben schnell einen großen Nachteil bekommen: Sie sind ressourcenhungrige, in sich abgeschlossene Komponenten mit eigenem Kontext und eigener Ressourcenverwaltung und damit für den Zweck einfache Bildschirmabfolgen anzuzeigen zu ungeeignet. Und dann kamen irgendwann Tablets, die gleich mehrere Ansichten nebeneinander darstellen konnten. Etwas neues musste her: Fragments.

Das Fragment bietet, genau so wie die Activity, einen abgespeckten Lifecycle und basiert auf einem Activity Kontext und wird per FragmentManager innerhalb einer Activity verwaltet. So kann ein Fragment ressourcenarm bleiben und eine Activity kann gleich mehrere Fragmente gleichzeitig anzeigen oder aber übereinander ablegen. So können Kompositionen erstellt werden, wie 2 Fragmente nebeneinander bei Tablets und übereinander bei Smartphones. Da dieses System gut funktioniert, kann eine App komplett aus einer einzigen Activity bestehen und alle anderen Anzeigen werden per Fragmente realisiert. Google empfiehlt Anwendungen nach diesem "Single Activity"-Prinzip zu bauen.

Ebenso wie Activities können Fragmente vom System zerstört und wiederhergestellt werden. Der Entwicklungsaufwand um deren Stati und das Verwalten im Manager richtig anzugehen, ist dabei allerdings schon immer ein wenig hoch gewesen - der Entwickler muss schon genau wissen, was er wann macht. Daher nutzen wir hier die Navigation Library. Sie kann uns die Verwaltung komplett abnehmen. Dazu müssen wir nur einen Navigationsgraphen und die Layouts der Fragmente anlegen und verknüpfen. Mit ein paar weiteren Kniffen können dann gewisse Komponenten mit dem Graphen verbunden werden und die Navigation steht auch schon.

Die Navigation wird über einen sogenannten Navigations Graphen abgebildet.

Um dem Projekt einen Nav-Graphen hinzuzufügen, gehen wir wie folgt vor:

  1. Klicke im Projektfenster mit der rechten Maustaste auf den res-Ordner und wähle New > Android Resource File. Das Dialogfeld "Neue Ressourcendatei" wird angezeigt.
  2. Gebe einen Namen in das Feld Dateiname ein: "nav_graph".
  3. Wähle "Navigation" aus der Dropdown-Liste des Ressourcentyps, und klicke dann auf OK.
    Beim Hinzufügen des ersten Navigationsgraphen, erstellt Android Studio ein Verzeichnis für Navigationsressourcen im Verzeichnis res/navigation. Dort zu finden ist nav_graph.xml.

Der Navigation Editor öffnet sich.
Mit einem Klick auf "Click to add a destination" öffnet sich ein Dialog bei dem man mit "Create new destination" ein neues Fragment anlegen kann.

Hier entscheiden wir uns dafür, ein HomeFragment anzulegen.

Das wiederholen wir noch 2 mal mit einem

Unser Graph hat jetzt 3 Destinationen, aber ist trotzdem noch nicht komplett - dazu fehlt ein Host, der dem Graphen zugeordnet ist.
Daher gehen wir nun zurück in das "activity_main"-File und fügen innerhalb des ConstraintLayout

  <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="?android:attr/actionBarSize"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />


    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/actionBarSize"
        android:background="#ffffff"
        app:layout_anchorGravity="bottom"
        app:layout_constraintBottom_toBottomOf="@+id/root"
        app:menu="@menu/bottom_navigation" />

ein.

Das baut auch gleich die BottomNavigationView mit ein.

Jetzt fehlt noch ein Menu File, das die Menüpunkte der Bottom Bar vorgibt.

Dazu wird wieder ein Resource File angelegt, diesmal mit dem Typen "menu" mit dem Namen "bottom_navigation" und dem Inhalt:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/homeFragment"
        android:icon="@android:drawable/ic_menu_compass"
        android:title="Home" /> <!-- Export me to strings.xml !-->

    <item
        android:id="@+id/listFragment"
        android:icon="@android:drawable/ic_menu_sort_by_size"
        android:title="List" />  <!-- Export me to strings.xml !-->

    <item
        android:id="@+id/profileFragment"
        android:icon="@android:drawable/ic_menu_view"
        android:title="Profile" />  <!-- Export me to strings.xml !-->

</menu>

Jetzt muss nur noch die Navigationlibrary auch im Activity Code mit dem ganzen Konstrukt verbunden werden, indem am Ende der Methode onCreate():

 NavigationUI.setupWithNavController(
    bottom_navigation,
    findNavController(nav_host_fragment)
)

// mit den imports
import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.ui.NavigationUI

hinzugefügt wird.

Das wäre es eigentlich...

Das kommt davon, dass in den einzelnen Fragment Files, die wir automatisiert anlegen haben lassen, jede Menge Beispielcode enthalten ist, der aufzeigt, wie in einem Bundle Arguments an ein Fragment übergeben werden können und wie man eine Interaktion über einen "OnFragmentInteractionListener" vom Fragment an die Activity weiter geben kann. Das ist gut zu wissen, brauchen wir aber in unserer App erstmal nicht. Also modifizieren wir erstmal jedes der drei Fragmente so, dass es so simpel aufgebaut ist, wie dieses hier:

Jetzt kannst du die Anwendung erfolgreich bauen und die Navigation nutzen! Achte darauf wie ein selektierter Zustand sich ändert bei einem Klick auf die "Back"-Taste. Genau so sollte eine Navigation funktionieren!

Bevor wir weiter machen, wollen wir sicher gehen, dass die Ansichten sich unterscheiden und jede Ansicht auch eine Daseinsberechtigung hat.

Da Bilder mehr sagen als tausend Worte, fangen wir einfach damit an ein Bild aus dem Internet zu laden und anzuzeigen. Am einfachsten geht das mit einer Bilderlibrary, die automatisiert das Laden aus dem Internet für uns übernehmen kann und Caching-Technologien benutzt.

Wir brauchen daher eine Dependency im build.gradle-File im App Modul. Mit

implementation 'com.github.bumptech.glide:glide:4.10.0'

holen wir uns die Libary "Glide".

Jetzt befassen wir uns mit einem Layout. Gehe ins fragment_home.xml und lösche alles heraus und ersetze es mit:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#efefef"
    tools:context=".HomeFragment">

    <!-- export strings and dimensions like 12 dp to strings.xml and dimens.xml in production app -->

    <ImageView
        android:id="@+id/home_image"
        android:layout_width="wrap_content"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:layout_margin="24dp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="ARTSHSSLLLUUKHKGKFKFK:"
        android:layout_above="@+id/home_image"
        android:textStyle="bold" />

</RelativeLayout>

Also einem RelativeLayout mit einer ImageView. Das RelativeLayout hier steht für eine View, die mit relativen Positionsregeln, andere (Child-)Views anordnen kann. Daher wird die ImageView, mit android:layout_centerInParent="true" im Layout zentriert. ImageViews sind für gewöhnlich Views, die Bilder darstellen.

Ist das eingebunden, brauchen wir nur noch in den Code des ersten Fragments (HomeFragment.kt) zu springen und eine Methode hinzuzufügen:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        Glide.with(requireContext())
                  .load("https://jamitlabs.com/assets/covers/android_table-5c71e8ad3ec35a60ae27dd43c8651c7a4aa2f49b05121d9117f46e961b892c6f.jpg")
                  .apply(RequestOptions.bitmapTransform(RoundedCorners(100)))
                  .transition(DrawableTransitionOptions.withCrossFade())
                  .into(home_image)
    }

Aber halt: Das reicht leider nicht! Das ist das erste Mal, dass unsere App ins Internet will. Das sollten wir nicht ohne die Berechtigung des Users machen. Zwar muss man den User nicht explizit um Erlaubnis fragen, aber spätestens im Play Store sollte man einsehen können, dass die App ins Internet möchte.

Suche im Projekt nach dem Android Manifest File. Hier ist alles eingetragen, was dem Betriebssystem aussagt, welche Komponenten die App so hat. Trage weit oben noch vor dem Application Tag:

<uses-permission android:name="android.permission.INTERNET" />

ein.

Jetzt kannst du die Anwendung bauen - Falls du eine Verbindung zum Internet hast, sollte das Bild auf dem entsprechenden Bildschirm geladen werden.

In diesem Schritt wollen wir den Grundstein für das nächste Kapitel legen, und zwar das Layout um eine gewöhnliche Liste anzeigen zu können.

Wechsle dazu ins fragment_list.xml Layout und tausche den Inhalt mit:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true"
    android:background="#efefef"
    tools:context=".ListFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

Unser Profil wird ein sehr Einfaches für den Anfang. Wechsle dazu den Inhalt des fragment_profile.xml aus mit:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#efefef"
    tools:context=".ProfileFragment">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/profile_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_launcher_foreground"
            android:tint="@color/colorPrimary" />

        <TextView
            android:textStyle="bold"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@string/app_name" />

        <TextView
            android:text="App"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

Wie findest du das Ergebnis?

Du kannst in dieser Arbeit auch gerne eigene Texte und Bilder einfügen.

Referenzieren kannst du das im XML mit "@string/" für String-Resourcen in dem strings.xml File und mit "@drawable/" für Bilder die in dem "res/drawables" Ordner liegen.

Jetzt können wir mehrere Ansichten nutzen.

Als Nächstes kümmern wir uns um wohl das wichtigste Anzeigeelement in Apps: Listen!

Mache nun weiter mit den Listen.