Konfigurationsänderungen verarbeiten

Responsive Benutzeroberfläche und Navigation

Um den Nutzern die bestmögliche Navigation zu bieten, solltest du eine Navigations-UI bereitstellen, die auf die Breite, Höhe und kleinste Breite des jeweiligen Geräts zugeschnitten ist. Sie können z. B. eine untere App-Leiste, eine immer verfügbare oder minimierbare Navigationsleiste, eine Leiste oder eine völlig neue Art der Navigationsleiste verwenden – je nach verfügbarem Bildschirmbereich und dem einzigartigen Stil Ihrer App.

Beispiele für eine Leiste, Navigationsleisten und eine untere App-Leiste
Abbildung 1: Beispiele für Schienen, Navigationsleisten und eine untere App-Leiste.

Der Leitfaden zur Produktarchitektur für Material Design enthält zusätzlichen Kontext und weitere Überlegungen zum Erstellen einer responsiven UI, also einer UI, die sich dynamisch an Umgebungsänderungen anpasst. Einige Beispiele für umgebungsbezogene Änderungen sind Anpassungen der Breite, Höhe, Ausrichtung und Spracheinstellungen des Nutzers. Diese Umgebungseigenschaften werden zusammen als die Konfiguration des Geräts bezeichnet.

Wenn sich eines oder mehrere dieser Eigenschaften während der Laufzeit ändern, reagiert das Android-Betriebssystem, indem die Aktivitäten und Fragmente der App gelöscht und anschließend neu erstellt werden. Zur Unterstützung einer responsiven Benutzeroberfläche unter Android sollten Sie daher am besten darauf achten, gegebenenfalls Ressourcenkonfigurationsqualifizierer zu verwenden und fest codierte Layoutgrößen zu vermeiden.

Globale Navigation in einer responsiven UI implementieren

Die Implementierung der globalen Navigation als Teil einer responsiven UI beginnt mit der Aktivität, die das Navigationsdiagramm hostet. Ein praktisches Beispiel finden Sie im Navigationscodelab. Im Codelab wird ein NavigationView verwendet, um das Navigationsmenü aufzurufen (siehe Abbildung 2). Beim Ausführen auf einem Gerät, das mit einer Breite von mindestens 960 dp gerendert wird, ist NavigationView immer auf dem Bildschirm zu sehen.

Im Navigationscodelab wird eine Navigationsansicht verwendet, die immer sichtbar ist, wenn die Gerätebreite mindestens 960 dp beträgt
Abbildung 2: Im Navigationscodelab wird ein NavigationView verwendet, um das Navigationsmenü aufzurufen.

Andere Gerätegrößen und ‐ausrichtungen wechseln bei Bedarf dynamisch zwischen DrawerLayout oder BottomNavigationView.

eine Navigationsansicht am unteren Rand und ein Schubladenlayout, die bei Bedarf für das Navigationsmenü in kleineren Gerätelayouts verwendet werden
Abbildung 3: Im Navigations-Codelab werden BottomNavigationView und DrawerLayout verwendet, um das Navigationsmenü auf kleineren Geräten anzuzeigen.

Sie können dieses Verhalten implementieren, indem Sie drei verschiedene Layouts erstellen, wobei jedes Layout die gewünschten Navigationselemente und Ansichtshierarchie basierend auf der aktuellen Gerätekonfiguration definiert.

Die Konfiguration, für die die einzelnen Layouts gelten, wird durch die Verzeichnisstruktur bestimmt, in der sich die Layoutdatei befindet. Die Layoutdatei NavigationView befindet sich beispielsweise im Verzeichnis res/layout-w960dp.

<!-- res/layout-w960dp/navigation_activity.xml -->
<RelativeLayout
   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"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_alignParentStart="true"
       app:elevation="0dp"
       app:headerLayout="@layout/nav_view_header"
       app:menu="@menu/nav_drawer_menu" />

   <View
       android:layout_width="1dp"
       android:layout_height="match_parent"
       android:layout_toEndOf="@id/nav_view"
       android:background="?android:attr/listDivider" />

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentTop="true"
       android:layout_toEndOf="@id/nav_view"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_below="@id/toolbar"
       android:layout_toEndOf="@id/nav_view"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />
</RelativeLayout>

Die untere Navigationsansicht befindet sich im Verzeichnis res/layout-h470dp:

<!-- res/layout-h470dp/navigation_activity.xml -->
<LinearLayout
   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:orientation="vertical"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="1"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />

   <com.google.android.material.bottomnavigation.BottomNavigationView
       android:id="@+id/bottom_nav_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:menu="@menu/bottom_nav_menu" />
</LinearLayout>

Das Leistenlayout befindet sich im Verzeichnis res/layout. Verwenden Sie dieses Verzeichnis für Standardlayouts ohne konfigurationsspezifische Qualifizierer:

<!-- res/layout/navigation_activity.xml -->
<androidx.drawerlayout.widget.DrawerLayout
   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:id="@+id/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">

       <androidx.appcompat.widget.Toolbar
           android:id="@+id/toolbar"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:background="@color/colorPrimary"
           android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

       <androidx.fragment.app.FragmentContainerView
           android:id="@+id/my_nav_host_fragment"
           android:name="androidx.navigation.fragment.NavHostFragment"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           app:defaultNavHost="true"
           app:navGraph="@navigation/mobile_navigation" />
   </LinearLayout>

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       app:menu="@menu/nav_drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>

Android folgt bei der Bestimmung, welche Ressourcen angewendet werden sollen, einer Rangfolge. Speziell in diesem Beispiel hat -w960dp (oder verfügbare Breite >= 960 dp) Vorrang vor -h470dp (oder verfügbare Höhe >= 470). Wenn die Gerätekonfiguration keine dieser Bedingungen erfüllt, wird die Standardlayoutressource (res/layout/navigation_activity.xml) verwendet.

Bei der Verarbeitung von Navigationsereignissen müssen Sie nur die Ereignisse verbinden, die den aktuell vorhandenen Widgets entsprechen, wie im folgenden Beispiel gezeigt.

Kotlin

class MainActivity : AppCompatActivity() {

   private lateinit var appBarConfiguration : AppBarConfiguration

   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.navigation_activity)
      val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
      appBarConfiguration = AppBarConfiguration(
                  setOf(R.id.home_dest, R.id.deeplink_dest),
                  drawerLayout)

      ...

      // Initialize the app bar with the navigation drawer if present.
      // If the drawerLayout is not null here, a Navigation button will be added
      // to the app bar whenever the user is on a top-level destination.
      setupActionBarWithNavController(navController, appBarConfig)

      // Initialize the NavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val sideNavView = findViewById<NavigationView>(R.id.nav_view)
      sideNavView?.setupWithNavController(navController)

      // Initialize the BottomNavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
      bottomNav?.setupWithNavController(navController)

      ...
    }

    ...
}

Java

public class MainActivity extends AppCompatActivity {

   private AppBarConfiguration appBarConfiguration;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.navigation_activity);
       NavHostFragment host = (NavHostFragment) getSupportFragmentManager()
               .findFragmentById(R.id.my_nav_host_fragment);
       NavController navController = host.getNavController();

       DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
       appBarConfiguration = new AppBarConfiguration.Builder(
               R.id.home_dest, R.id.deeplink_dest)
               .setDrawerLayout(drawerLayout)
               .build();

       // Initialize the app bar with the navigation drawer if present.
       // If the drawerLayout is not null here, a Navigation button will be added to
       // the app bar whenever the user is on a top-level destination.
       NavigationUI.setupActionBarWithNavController(
               this, navController, appBarConfiguration);


       // Initialize the NavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       NavigationView sideNavView = findViewById(R.id.nav_view);
       if(sideNavView != null) {
           NavigationUI.setupWithNavController(sideNavView, navController);
       }

       // Initialize the BottomNavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
       if(bottomNav != null) {
           NavigationUI.setupWithNavController(bottomNav, navController);
       }

   }
}

Wenn sich die Gerätekonfiguration ändert, löscht Android die Aktivität aus der vorherigen Konfiguration zusammen mit den zugehörigen Ansichten. Diese Konfiguration ist nicht explizit konfiguriert. Anschließend wird die Aktivität mit Ressourcen neu erstellt, die für die neue Konfiguration vorgesehen sind. Die Aktivität, die gelöscht und neu erstellt wird, verbindet dann automatisch die richtigen globalen Navigationselemente in onCreate().

Alternativen zu Layouts mit geteilter Ansicht in Betracht ziehen

Split-View-Layouts oder Master-/Detail-Layouts waren früher eine sehr beliebte und empfohlene Methode für die Entwicklung von Designs für Tablets und andere Geräte mit großen Bildschirmen.

Seit der Einführung der Android-Tablets ist die Zahl der Geräte inzwischen rasant gewachsen. Ein Faktor, der den Designbereich für Geräte mit großen Bildschirmen erheblich beeinflusst hat, war die Einführung von Mehrfenstermodi, insbesondere Freiformfenstern, deren Größe vollständig angepasst werden kann, wie z. B. auf ChromeOS-Geräten. Dadurch wird die Reaktion auf jeden Bildschirm Ihrer App erheblich betont, anstatt die Navigationsstruktur an die Bildschirmgröße anzupassen.

Es ist zwar möglich, mithilfe der Navigationsbibliothek eine Layoutoberfläche mit geteilter Ansicht zu implementieren, Sie sollten jedoch andere Alternativen in Betracht ziehen.

Zielnamen

Wenn Sie in Ihrer Grafik Zielnamen mit dem Attribut android:label angeben, müssen Sie immer Ressourcenwerte verwenden, damit Ihre Inhalte weiterhin lokalisiert werden können.

<navigation ...>
    <fragment
        android:id="@+id/my_dest"
        android:name="com.example.MyFragment"
        android:label="@string/my_dest_label"
        tools:layout="@layout/my_fragment" />
    ...

Mit Ressourcenwerten werden auf Ihre Ziele automatisch die am besten geeigneten Ressourcen angewendet, wenn sich Ihre Konfiguration ändert.