Gérer les modifications de configuration

Interface utilisateur et navigation responsives

Pour offrir la meilleure expérience de navigation possible à vos utilisateurs, vous devez fournir une interface utilisateur de navigation adaptée à la largeur, à la hauteur et à la largeur minimale de l'appareil. Vous pouvez utiliser une barre d'application inférieure, un panneau de navigation toujours présent ou réductible, un rail ou peut-être quelque chose de totalement nouveau en fonction de l'espace disponible à l'écran et du style unique de votre application.

exemples de rail, de panneaux de navigation et de barre d'application inférieure
Figure 1 : Exemples de rail, de panneaux de navigation et de barre d'application inférieure

Le guide concernant l'architecture des produits de Material Design fournit du contexte et des considérations supplémentaires pour créer une interface utilisateur responsive, c'est-à-dire une interface qui s'adapte de manière dynamique aux changements de l'environnement. Voici quelques exemples de changements de l'environnement : ajustements de largeur, de hauteur, d'orientation et de préférence linguistique de l'utilisateur. Ces propriétés d'environnement sont collectivement désignées sous le nom de configuration de l'appareil.

Lorsqu'une ou plusieurs de ces propriétés changent au moment de l'exécution, le système d'exploitation Android répond en détruisant, puis en recréant les activités et les fragments de votre application. Par conséquent, le meilleur moyen de gérer une interface utilisateur responsive sur Android est de vous assurer que vous utilisez des qualificatifs de configuration de ressources, le cas échéant, et que vous évitez d'utiliser des tailles de mise en page codées en dur.

Implémenter la navigation globale dans une interface utilisateur responsive

L'implémentation de la navigation globale dans une interface utilisateur responsive commence par l'activité qui héberge votre graphique de navigation. Pour un exemple pratique, consultez l'atelier de programmation sur la navigation. L'atelier de programmation utilise une NavigationView pour afficher le menu de navigation, comme illustré dans la figure 2. Sur un appareil qui affiche une largeur d'au moins 960 dp, cette NavigationView s'affiche toujours à l'écran.

l'atelier de programmation sur la navigation utilise une vue de navigation toujours visible lorsque la largeur de l'appareil est d'au moins 960 dp
Figure 2 : L'atelier de programmation sur la navigation utilise un élément NavigationView pour afficher le menu de navigation.

Si nécessaire, les autres tailles et orientations d'appareils alternent de manière dynamique entre DrawerLayout et BottomNavigationView.

une bottomnavigationview (vue de navigation inférieure) et une drawerlayout (mise en page avec panneau), utilisées pour le menu de navigation selon les besoins dans les mises en page d'appareils de plus petite taille
Figure 3 : L'atelier de programmation sur la navigation utilise BottomNavigationView et DrawerLayout pour afficher le menu de navigation sur des appareils de plus petite taille.

Vous pouvez implémenter ce comportement en créant trois mises en page différentes, où chaque mise en page définit les éléments de navigation souhaités et la hiérarchie des vues en fonction de la configuration actuelle de l'appareil.

La configuration à laquelle s'applique chaque mise en page est déterminée par la structure du répertoire dans lequel le fichier de mise en page est placé. Par exemple, le fichier de mise en page NavigationView se trouve dans le répertoire 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>

La vue de navigation inférieure se trouve dans le répertoire 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>

La mise en page avec panneau se trouve dans le répertoire res/layout. Utilisez ce répertoire pour les mises en page par défaut sans qualificatifs propres à la configuration :

<!-- 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 suit un ordre de priorité pour déterminer les ressources à appliquer. Pour cet exemple, -w960dp (la largeur disponible est supérieure ou égale à 960 dp) est prioritaire par rapport à -h470dp (la hauteur disponible est supérieure ou égale à 470). Si la configuration de l'appareil ne correspond à aucune de ces conditions, la ressource de mise en page par défaut (res/layout/navigation_activity.xml) est utilisée.

Pour gérer les événements de navigation, vous ne devez connecter que les événements correspondant aux widgets existants, comme illustré dans l'exemple suivant.

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);
       }

   }
}

Si la configuration de l'appareil change, Android détruit l'activité de la configuration précédente ainsi que les vues associées, sauf si vous avez indiqué explicitement dans la configuration que ce ne devait pas être le cas. Android recrée ensuite l'activité avec des ressources conçues pour la nouvelle configuration. L'activité est détruite et recréée, puis associe automatiquement les bons éléments de navigation globale dans onCreate().

Envisager des alternatives aux mises en page avec vue fractionnée

Les mises en page avec vue fractionnée, ou mises en page maître/détail, étaient autrefois un moyen très populaire et recommandé pour concevoir des applications pour des tablettes et d'autres appareils à grand écran.

Depuis le lancement des tablettes Android, l'écosystème d'appareils s'est développé rapidement. L'un des facteurs qui ont considérablement influencé l'espace de conception pour les appareils à grand écran est l'introduction des modes multifenêtre, en particulier les fenêtres au format libre entièrement redimensionnables, comme celles des appareils ChromeOS. Il est alors beaucoup plus important de faire en sorte que chaque écran de votre application soit responsif, plutôt que de modifier votre structure de navigation en fonction de la taille de l'écran.

Bien qu'il soit possible de mettre en œuvre une interface de mise en page avec affichage fractionné à l'aide de la bibliothèque Navigation, nous vous recommandons d'envisager d'autres solutions.

Noms de destination

Si vous fournissez des noms de destination dans votre graphique à l'aide de l'attribut android:label, veillez à toujours utiliser des valeurs de ressource afin que votre contenu puisse toujours être localisé.

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

Avec les valeurs de ressource, les ressources les plus appropriées s'appliquent automatiquement à vos destinations à chaque modification de votre configuration.