Gestione delle modifiche alla configurazione

Interfaccia utente adattabile e navigazione

Per offrire agli utenti la migliore esperienza di navigazione possibile, devi fornire un'interfaccia utente di navigazione personalizzata in base alla larghezza, all'altezza e alla larghezza minima del dispositivo dell'utente. Ti consigliamo di utilizzare una barra dell'app in basso, un riquadro di navigazione a scomparsa sempre presente o comprimibile, una barra o qualcosa di completamente nuovo in base allo spazio disponibile sullo schermo e allo stile unico della tua app.

esempi di binario, riquadro di navigazione a scomparsa e barra delle app in basso
Figura 1. Esempi di binari, riquadri di navigazione a scomparsa e di una barra delle app in basso.

La guida all'architettura dei prodotti di material design fornisce un contesto aggiuntivo e considerazioni per la creazione di un'interfaccia utente reattiva, ovvero un'interfaccia utente che si adatta dinamicamente ai cambiamenti ambientali. Alcuni esempi di cambiamenti ambientali includono la modifica di larghezza, altezza, orientamento e preferenza della lingua dell'utente. Queste proprietà ambientali sono definite collettivamente la configurazione del dispositivo.

Quando una o più di queste proprietà cambiano in fase di runtime, il sistema operativo Android risponde eliminando e poi ricreando le attività e i frammenti dell'app. Pertanto, la cosa migliore che puoi fare per supportare un'interfaccia utente reattiva su Android è assicurarti di utilizzare i qualificatori di configurazione delle risorse, ove opportuno, ed evita di utilizzare dimensioni di layout hardcoded.

Implementazione della navigazione globale in una UI adattabile

L'implementazione della navigazione globale come parte di una UI adattabile inizia con l'attività che ospita il grafico di navigazione. Per un esempio pratico, consulta il Codelab sulla navigazione. Il codelab utilizza una NavigationView per visualizzare il menu di navigazione, come mostrato nella Figura 2. Quando è in esecuzione su un dispositivo che esegue il rendering a una larghezza di almeno 960 dp, questo NavigationView è sempre sullo schermo.

Il codelab di navigazione utilizza una visualizzazione di navigazione sempre visibile quando la larghezza del dispositivo è di almeno 960 dp
Figura 2. Il codelab di navigazione utilizza un elemento NavigationView per visualizzare il menu di navigazione.

Altri orientamenti e dimensioni del dispositivo cambiano in modo dinamico, a seconda delle esigenze, da DrawerLayout a BottomNavigationView.

una visualizzazione di navigazione in basso e un layout a scomparsa, utilizzati per il menu di navigazione
            quando necessario nei layout dei dispositivi più piccoli
Figura 3. Il codelab di navigazione utilizza BottomNavigationView e DrawerLayout per visualizzare il menu di navigazione sui dispositivi più piccoli.

Puoi implementare questo comportamento creando tre layout diversi, in cui ciascuno definisce gli elementi di navigazione desiderati e la gerarchia delle visualizzazioni in base alla configurazione attuale del dispositivo.

La configurazione a cui si applica ogni layout è determinata dalla struttura di directory in cui è posizionato il file di layout. Ad esempio, il file di layout NavigationView si trova nella directory 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 visualizzazione di navigazione in basso si trova nella directory 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>

Il layout del riquadro a scomparsa si trova nella directory res/layout. Utilizza questa directory per i layout predefiniti senza qualificatori specifici per la configurazione:

<!-- 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 segue un ordine di precedenza nel determinare quali risorse applicare. In modo specifico per questo esempio, -w960dp (o larghezza disponibile >= 960 dp) ha la precedenza su -h470dp (o altezza disponibile >= 470). Se la configurazione del dispositivo non corrisponde a nessuna di queste condizioni, viene utilizzata la risorsa di layout predefinita (res/layout/navigation_activity.xml).

Per gestire gli eventi di navigazione, devi collegare solo gli eventi che corrispondono ai widget attualmente presenti, come mostrato nell'esempio che segue.

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

   }
}

Se la configurazione del dispositivo cambia, a meno che non venga esplicitamente configurata in modo diverso, Android elimina l'attività dalla configurazione precedente e dalle viste associate. Quindi ricrea l'attività con le risorse progettate per la nuova configurazione. L'attività, che viene distrutta e ricreata, collega automaticamente gli elementi di navigazione globali appropriati in onCreate().

Considera le alternative ai layout in visualizzazione divisa

Un tempo, i layout con visualizzazione divisa, o layout principale/dettaglio, erano un modo molto popolare e consigliato per progettare contenuti per tablet e altri dispositivi con schermi di grandi dimensioni.

Dall'introduzione dei tablet Android, l'ecosistema dei dispositivi è cresciuto rapidamente. Un fattore che ha influenzato notevolmente lo spazio di progettazione dei dispositivi con schermi di grandi dimensioni è stata l'introduzione di modalità multi-finestra, in particolare le finestre in formato libero completamente ridimensionabili, come quelle sui dispositivi ChromeOS. In questo modo, viene data una maggiore enfasi al fatto che tutti gli schermi dell'app sono reattivi, anziché modificare la struttura di navigazione in base alle dimensioni dello schermo.

Sebbene sia possibile implementare un'interfaccia di layout con visualizzazione divisa utilizzando la libreria di navigazione, dovresti valutare altre alternative.

Nomi destinazione

Se indichi i nomi delle destinazioni nel grafico utilizzando l'attributo android:label, assicurati di utilizzare sempre i valori delle risorse in modo che i contenuti possano essere localizzati.

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

Con i valori delle risorse, alle destinazioni vengono applicate automaticamente le risorse più appropriate ogni volta che la configurazione viene modificata.