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.
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.
Altri orientamenti e dimensioni del dispositivo cambiano in modo dinamico, a seconda delle esigenze, da
DrawerLayout
a
BottomNavigationView
.
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.