Il componente Navigazione è una libreria in grado di gestire navigazione complessa, animazione delle transizioni, link diretti e un argomento con modalità di compilazione verificata che passa tra le schermate dell'app.
Questo documento funge da guida generale per la migrazione di un'app esistente a utilizzare il componente Navigazione.
A livello generale, la migrazione prevede i seguenti passaggi:
Rimuovi la logica dell'interfaccia utente specifica per la schermata dalle attività. Sposta l'UI dell'app. uscire dalle attività, assicurandosi che ciascuna attività possieda solo la logica di componenti della UI di navigazione globale, ad esempio
Toolbar
, mentre delega implementazione di ogni schermata su un frammento o una destinazione personalizzata.Integra il componente Navigazione: per ogni attività, crea un grafico di navigazione che contiene uno o più frammenti gestiti da tale attività. Sostituisci le transazioni dei frammenti con le operazioni del componente Navigazione.
Aggiungi destinazioni attività: sostituisci le chiamate
startActivity()
con azioni tramite le destinazioni delle attività.Combina attività: combina i grafici di navigazione nei casi in cui più attività condividono lo stesso layout.
Prerequisiti
Questa guida presuppone che tu abbia già eseguito la migrazione della tua app per utilizzarla librerie AndroidX. Se non lo hai già fatto, eseguire la migrazione del progetto per utilizzare AndroidX prima continua.
Sposta logica UI specifica per la schermata fuori dalle attività
Le attività sono componenti a livello di sistema che facilitano un'interazione grafica tra la tua app e Android. Le attività sono registrate nel file manifest dell'app per far sapere ad Android quali attività sono disponibili per l'avvio. L'attività consente alla tua app di reagire anche ai cambiamenti di Android, ad esempio quando l'UI dell'app entra o esce dal primo piano, ruota e così via. La attività può servire anche da luogo di condividere lo stato tra le schermate.
Nel contesto della tua app, le attività devono fungere da host per la navigazione. e dovrebbe contenere la logica e la conoscenza di come passare da uno schermo all'altro, trasferire dati e così via. Tuttavia, è preferibile gestire i dettagli della UI. in una parte più piccola e riutilizzabile dell'interfaccia utente. L'implementazione consigliata in questo caso è fragments. Consulta Attività singola: perché, quando e come per saperne di più sui vantaggi dell'utilizzo dei frammenti. La navigazione supporta i frammenti tramite la dipendenza navigation-fragment. La navigazione supporta anche tipi di destinazioni personalizzate.
Se la tua app non utilizza frammenti, la prima cosa da fare è eseguire la migrazione ogni schermata dell'app utilizzando un frammento. Non stai rimuovendo l'attività in a questo punto. ma piuttosto un frammento che rappresenta lo schermo e si rompe a parte la logica dell'UI in base alla responsabilità.
Introduzione ai frammenti
Per illustrare il processo di introduzione dei frammenti, iniziamo con un esempio di un'applicazione composta da due schermate: una per l'elenco dei prodotti e una schermata con i dettagli del prodotto. Facendo clic su un prodotto nella schermata dell'elenco, a una schermata dei dettagli per scoprire di più sul prodotto.
In questo esempio, le schermate con elenco e dettagli sono attualmente attività separate.
Crea un nuovo layout per ospitare l'UI
Per introdurre un frammento, inizia creando un nuovo file di layout per l'attività ospitare il frammento. Sostituisce l'attuale layout di visualizzazione dei contenuti dell'attività.
Per una visualizzazione semplice, puoi utilizzare un FrameLayout
, come mostrato di seguito
esempio product_list_host
:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent" />
L'attributo id
si riferisce alla sezione dei contenuti, a cui in seguito aggiungeremo il parametro
.
Successivamente, nella funzione onCreate()
dell'attività, modifica il riferimento del file di layout
nella funzione onCreate dell'attività per puntare a questo nuovo file di layout:
Kotlin
class ProductListActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... // Replace setContentView(R.layout.product_list) with the line below setContentView(R.layout.product_list_host) ... } }
Java
public class ProductListActivity extends AppCompatActivity { ... @Override public void onCreate(@Nullable Bundle savedInstanceState) { ... // Replace setContentView(R.layout.product_list); with the line below setContentView(R.layout.product_list_host); ... } }
Il layout esistente (in questo esempio product_list
) viene utilizzato come vista principale
per il frammento che stai per creare.
Crea un frammento
Crea un nuovo frammento per gestire l'UI per il tuo schermo. È buona prassi
coerente con il nome host dell'attività. Lo snippet riportato di seguito utilizza
ProductListFragment
, ad esempio:
Kotlin
class ProductListFragment : Fragment() { // Leave empty for now. }
Java
public class ProductListFragment extends Fragment { // Leave empty for now. }
Sposta la logica di attività in un frammento
Con la definizione del frammento, il passaggio successivo è spostare la logica dell'interfaccia utente
schermata dall'attività al nuovo frammento. Se provieni da
basata sull'attività, probabilmente c'è molta logica per la creazione delle viste
che si verificano nella funzione onCreate()
della tua attività.
Ecco un esempio di schermata basata sull'attività con logica UI che dobbiamo spostare:
Kotlin
class ProductListActivity : AppCompatActivity() { // Views and/or ViewDataBinding references, Adapters... private lateinit var productAdapter: ProductAdapter private lateinit var binding: ProductListActivityBinding ... // ViewModels, System Services, other Dependencies... private val viewModel: ProductListViewModel by viewModels() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity) // Post view initialization logic // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } ... }
Java
public class ProductListActivity extends AppCompatActivity { // Views and/or ViewDataBinding references, adapters... private ProductAdapter productAdapter; private ProductListActivityBinding binding; ... // ViewModels, system services, other dependencies... private ProductListViewModel viewModel; ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity); // Post view initialization logic // Connect adapters productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(v -> { ... }); // Subscribe to state viewModel.getProducts().observe(this, myProducts -> ... ); // ...and so on }
La tua attività potrebbe anche controllare quando e come l'utente accede alla schermata successiva, come mostrato nell'esempio seguente:
Kotlin
// Provided to ProductAdapter in ProductListActivity snippet. private val productClickCallback = ProductClickCallback { product -> show(product) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) }
Java
// Provided to ProductAdapter in ProductListActivity snippet. private ProductClickCallback productClickCallback = this::show; private void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); }
All'interno del frammento, distribuisci quest'opera tra
onCreateView()
e
onViewCreated()
,
mentre nell'attività rimane solo la logica di navigazione:
Kotlin
class ProductListFragment : Fragment() { private lateinit var binding: ProductListFragmentBinding private val viewModel: ProductListViewModel by viewModels() // View initialization logic override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = DataBindingUtil.inflate( inflater, R.layout.product_list, container, false ) return binding.root } // Post view initialization logic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } // Provided to ProductAdapter private val productClickCallback = ProductClickCallback { product -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { (requireActivity() as ProductListActivity).show(product) } } ... }
Java
public class ProductListFragment extends Fragment { private ProductAdapter productAdapter; private ProductListFragmentBinding binding; // View initialization logic @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate( inflater, R.layout.product_list_fragment, container, false); return binding.getRoot(); } // Post view initialization logic @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // Connect adapters binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this) .get(ProductListViewModel.class); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(...) // Subscribe to state viewModel.getProducts().observe(this, myProducts -> { ... }); // ...and so on // Provided to ProductAdapter private ProductClickCallback productClickCallback = new ProductClickCallback() { @Override public void onClick(Product product) { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { ((ProductListActivity) requireActivity()).show(product); } } }; ... }
In ProductListFragment
, noterai che non è possibile effettuare chiamate a
setContentView()
per gonfiare e collegare il layout. In un frammento, onCreateView()
inizializza la classe
vista root. onCreateView()
prende un'istanza di un
LayoutInflater
che può essere utilizzato per
gonfiare la vista principale in base a un file di risorse di layout. Questo esempio riutilizza il metodo
layout product_list
esistente che è stato utilizzato dall'attività perché non è presente nulla
deve modificare il layout stesso.
Se disponi di logiche UI all'interno di onStart()
, onResume()
dell'attività,
Le funzioni onPause()
o onStop()
non correlate alla navigazione, puoi
spostarle nelle funzioni corrispondenti con lo stesso nome sul frammento.
Inizializzare il frammento nell'attività host
Dopo aver spostato tutta la logica dell'interfaccia utente verso il basso nel frammento, verrà utilizzata solo la navigazione dovrebbe rimanere nell'attività.
Kotlin
class ProductListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) } }
Java
public class ProductListActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); } public void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); } }
L'ultimo passaggio consiste nel creare un'istanza del frammento in onCreate()
,
Dopo aver impostato la visualizzazione dei contenuti:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); if (savedInstanceState == null) { ProductListFragment fragment = new ProductListFragment(); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } }
Come mostrato in questo esempio, FragmentManager
salva e ripristina automaticamente
per modificare la configurazione, quindi devi aggiungere il frammento solo se
savedInstanceState
è nullo.
Passa gli extra per intent al frammento
Se la tua attività riceve Extras
tramite un intent, puoi trasmetterli all'
direttamente come argomenti.
In questo esempio, ProductDetailsFragment
riceve direttamente i propri argomenti
dagli extra relativi all'intenzione dell'attività:
Kotlin
... if (savedInstanceState == null) { val fragment = ProductDetailsFragment() // Intent extras and Fragment Args are both of type android.os.Bundle. fragment.arguments = intent.extras supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } ...
Java
... if (savedInstanceState == null) { ProductDetailsFragment fragment = new ProductDetailsFragment(); // Intent extras and fragment Args are both of type android.os.Bundle. fragment.setArguments(getIntent().getExtras()); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } ...
A questo punto, dovresti riuscire a testare l'esecuzione dell'app con la prima schermata aggiornato per utilizzare un frammento. Continua con la migrazione del resto dei dati basati sulle attività schermate, il che richiede tempo per la verifica dopo ogni iterazione.
Integrare il componente Navigazione
Dopo aver utilizzato un'architettura basata su frammenti, puoi iniziare a integrare il componente Navigazione.
Innanzitutto, aggiungi le dipendenze di navigazione più recenti al progetto, seguendo le istruzioni nel Note di rilascio della libreria di navigazione.
Crea un grafico di navigazione
Il componente Navigazione rappresenta la configurazione di navigazione dell'app in un un file di risorse sotto forma di grafico, proprio come sono rappresentate le visualizzazioni dell'app. Ciò consente di mantenere la navigazione dell'app organizzata al di fuori del codebase e fornire un modo per modificare visivamente la navigazione dell'app.
Per creare un grafico di navigazione, crea innanzitutto una nuova cartella di risorse
navigation
. Per aggiungere il grafico, fai clic con il tasto destro del mouse su questa directory e scegli
Nuovo > File di risorse di navigazione.
Il componente Navigazione utilizza un'attività come
host per la navigazione
e scambia i singoli frammenti all'interno dell'host mentre gli utenti navigano
la tua app. Per poter iniziare a organizzare visivamente la navigazione dell'app, devi
devi configurare un NavHost
all'interno dell'attività che lo ospiterà
grafico. Poiché utilizziamo i frammenti, possiamo usare il comando
implementazione predefinita di NavHost
,
NavHostFragment
Un NavHostFragment
viene configurato tramite un FragmentContainerView
posizionati all'interno di un'attività host, come mostrato nell'esempio seguente:
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
L'attributo app:NavGraph
rimanda al grafico di navigazione associato a questo
host di navigazione. L'impostazione di questa proprietà comporta l'gonfiamento del grafico di navigazione e l'impostazione del grafico
in NavHostFragment
. L'attributo app:defaultNavHost
garantisce
che NavHostFragment
intercetta il pulsante Indietro del sistema.
Se utilizzi la navigazione di primo livello, come DrawerLayout
o
BottomNavigationView
, questa FragmentContainerView
sostituisce l'elemento principale della visualizzazione dei contenuti. Consulta
Aggiornare i componenti dell'UI con NavigationUI
per consultare alcuni esempi.
Per un layout semplice, puoi includere questo elemento: FragmentContainerView
come elemento secondario della radice ViewGroup
:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_content"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Se fai clic sulla scheda Design in basso, dovresti vedere un grafico simile
a quello mostrato di seguito. Nella parte superiore sinistra del grafico, sotto
Destinazioni, puoi vedere un riferimento all'attività NavHost
nel modulo.
di layout_name (resource_id)
.
Fai clic sul pulsante Più in alto per aggiungere i frammenti al grafico.
Il componente Navigazione fa riferimento alle singole schermate come destinazioni. Le destinazioni possono essere frammenti, attività o destinazioni personalizzate. Puoi aggiungere qualsiasi tipo di destinazione nel grafico, ma tieni presente che le destinazioni delle attività sono considerate destinazioni dei terminal perché, dopo aver raggiunto un'attività destinazione, opera all'interno di un host di navigazione e di un grafico separati.
Il componente Navigazione fa riferimento al modo in cui gli utenti ottengono da una a un'altra come azioni. Le azioni possono anche descrivere la transizione animazioni e comportamento pop.
Rimuovi transazioni dei frammenti
Ora che stai utilizzando il componente Navigazione, se ti sposti tra schermate con frammenti nella stessa attività, puoi rimuovere
FragmentManager
e interazioni.
Se la tua app utilizza più frammenti nella stessa attività o di primo livello
come il layout a scomparsa o la navigazione in basso, probabilmente
usando un FragmentManager
FragmentTransactions
per aggiungere o sostituire frammenti nella sezione del contenuto principale della UI. Ora è possibile
essere sostituita e semplificata nel componente Navigazione fornendo azioni
per collegare le destinazioni nel grafico e la navigazione utilizzando
NavController
.
Ecco alcuni scenari che potresti incontrare e il tuo approccio per ogni scenario.
Una singola attività che gestisce più frammenti
Se hai una singola attività che gestisce più frammenti, la tua attività potrebbe avere il seguente aspetto:
Kotlin
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Logic to load the starting destination // when the Activity is first created if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit() } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). fun navigateToProductDetail(productId: String) { val fragment = new ProductDetailsFragment() val args = Bundle().apply { putInt(KEY_PRODUCT_ID, productId) } fragment.arguments = args supportFragmentManager.beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit() } }
Java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Logic to load the starting destination when the activity is first created. if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit(); } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). public void navigateToProductDetail(String productId) { Fragment fragment = new ProductDetailsFragment(); Bundle args = new Bundle(); args.putInt(KEY_PRODUCT_ID, productId); fragment.setArguments(args); getSupportFragmentManager().beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit(); } }
All'interno della destinazione di origine, potresti richiamare una funzione di navigazione risposta ad alcuni eventi, come mostrato di seguito:
Kotlin
class ProductListFragment : Fragment() { ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked // in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the activity to make the transition. private val productClickCallback = ProductClickCallback { product -> (requireActivity() as MainActivity).navigateToProductDetail(product.id) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> ( ((MainActivity) requireActivity()).navigateToProductDetail(product.getId()) ); }
Per sostituire questo valore, aggiorna il grafico di navigazione la destinazione iniziale e le azioni per collegare le destinazioni e definire argomenti dove richiesto:
<navigation 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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_detail" />
</fragment>
<fragment
android:id="@+id/product_detail"
android:name="com.example.android.persistence.ui.ProductDetailFragment"
android:label="Product Detail"
tools:layout="@layout/product_detail">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Dopodiché puoi aggiornare la tua attività:
Kotlin
class MainActivity : AppCompatActivity() { // No need to load the start destination, handled automatically by the Navigation component override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Java
public class MainActivity extends AppCompatActivity { // No need to load the start destination, handled automatically by the Navigation component @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
L'attività non richiede più un metodo navigateToProductDetail()
. Nei prossimi
aggiorniamo ProductListFragment
in modo che utilizzi NavController
per la navigazione
alla schermata successiva dei dettagli del prodotto.
Passa argomenti in sicurezza
Il componente Navigazione ha un plug-in Gradle chiamato Arg sicuri che genera semplici classi di oggetti e builder per l'accesso sicuro per tipo per le destinazioni e le azioni.
Una volta applicato il plug-in, tutti gli argomenti definiti su una destinazione nel
grafico di navigazione fa sì che la struttura del componente Navigazione generi un
Classe Arguments
che fornisce argomenti sicuri per il tipo alla destinazione di destinazione.
La definizione di un'azione fa sì che il plug-in generi una configurazione Directions
che può essere utilizzata per indicare a NavController
come indirizzare l'utente
la destinazione target. Quando un'azione rimanda a una destinazione che richiede
argomenti, la classe Directions
generata include metodi costruttore che
richiedono questi parametri.
All'interno del frammento, usa NavController
e la classe Directions
generata per
fornire argomenti sicuri per il tipo alla destinazione di destinazione, come mostrato di seguito
esempio:
Kotlin
class ProductListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the NavController to make the transition. private val productClickCallback = ProductClickCallback { product -> val directions = ProductListDirections.navigateToProductDetail(product.id) findNavController().navigate(directions) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> { ProductListDirections.ViewProductDetails directions = ProductListDirections.navigateToProductDetail(product.getId()); NavHostFragment.findNavController(this).navigate(directions); }; }
Navigazione di primo livello
Se la tua app utilizza un DrawerLayout
, potresti avere molta logica di configurazione
nell'attività che gestisce l'apertura e la chiusura del riquadro a scomparsa e la navigazione
per altre destinazioni.
L'attività risultante potrebbe avere un aspetto simile a questo:
Kotlin
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar: Toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) val navView: NavigationView = findViewById(R.id.nav_view) val toggle = ActionBarDrawerToggle( this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) drawerLayout.addDrawerListener(toggle) toggle.syncState() navView.setNavigationItemSelectedListener(this) } override fun onBackPressed() { val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) if (drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.closeDrawer(GravityCompat.START) } else { super.onBackPressed() } } override fun onNavigationItemSelected(item: MenuItem): Boolean { // Handle navigation view item clicks here. when (item.itemId) { R.id.home -> { val homeFragment = HomeFragment() show(homeFragment) } R.id.gallery -> { val galleryFragment = GalleryFragment() show(galleryFragment) } R.id.slide_show -> { val slideShowFragment = SlideShowFragment() show(slideShowFragment) } R.id.tools -> { val toolsFragment = ToolsFragment() show(toolsFragment) } } val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) drawerLayout.closeDrawer(GravityCompat.START) return true } } private fun show(fragment: Fragment) { val drawerLayout = drawer_layout as DrawerLayout val fragmentManager = supportFragmentManager fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit() drawerLayout.closeDrawer(GravityCompat.START) }
Java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); navigationView.setNavigationItemSelectedListener(this); } @Override public void onBackPressed() { DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onNavigationItemSelected(MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.home) { Fragment homeFragment = new HomeFragment(); show(homeFragment); } else if (id == R.id.gallery) { Fragment galleryFragment = new GalleryFragment(); show(galleryFragment); } else if (id == R.id.slide_show) { Fragment slideShowFragment = new SlideShowFragment(); show(slideShowFragment); } else if (id == R.id.tools) { Fragment toolsFragment = new ToolsFragment(); show(toolsFragment); } DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } private void show(Fragment fragment) { DrawerLayout drawerLayout = findViewById(R.id.drawer_layout); FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit(); drawerLayout.closeDrawer(GravityCompat.START); } }
Dopo aver aggiunto il componente Navigazione al progetto e aver creato un
grafico di navigazione, aggiungi ogni destinazione dei contenuti dal grafico (ad esempio
Home, Galleria, SlideShow e Strumenti nell'esempio precedente). Assicurati che
che i valori della voce di menu id
corrispondano ai valori id
di destinazione associati;
come mostrato di seguito:
<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/home"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_home" />
<item
android:id="@+id/gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery" />
<item
android:id="@+id/slide_show"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow" />
<item
android:id="@+id/tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools" />
</group>
</menu>
<!-- activity_main_graph.xml -->
<navigation 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/main_graph"
app:startDestination="@id/home">
<fragment
android:id="@+id/home"
android:name="com.example.HomeFragment"
android:label="Home"
tools:layout="@layout/home" />
<fragment
android:id="@+id/gallery"
android:name="com.example.GalleryFragment"
android:label="Gallery"
tools:layout="@layout/gallery" />
<fragment
android:id="@+id/slide_show"
android:name="com.example.SlideShowFragment"
android:label="Slide Show"
tools:layout="@layout/slide_show" />
<fragment
android:id="@+id/tools"
android:name="com.example.ToolsFragment"
android:label="Tools"
tools:layout="@layout/tools" />
</navigation>
Se corrisponde ai valori id
del menu e del grafico, puoi collegare il
NavController
per questa attività per gestire automaticamente la navigazione in base a
alla voce di menu. NavController
gestisce anche l'apertura e la chiusura
DrawerLayout
e come gestire il comportamento dei pulsanti Su e Indietro in modo appropriato.
Puoi quindi aggiornare MainActivity
per collegare NavController
al
Toolbar
e NavigationView
.
Vedi lo snippet che segue per avere un esempio:
Kotlin
class MainActivity : AppCompatActivity() { val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) } val navController by lazy { (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController } val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) // Show and Manage the Drawer and Back Icon setupActionBarWithNavController(navController, drawerLayout) // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. navigationView.setupWithNavController(navController) } override fun onSupportNavigateUp(): Boolean { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation return navController.navigateUp(drawerLayout) } }
Java
public class MainActivity extends AppCompatActivity { private DrawerLayout drawerLayout; private NavController navController; private NavigationView navigationView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = findViewById(R.id.drawer_layout); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); navController = navHostFragment.getNavController(); navigationView = findViewById(R.id.nav_view); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Show and Manage the Drawer and Back Icon NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout); // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. NavigationUI.setupWithNavController(navigationView, navController); } @Override public boolean onSupportNavigateUp() { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation. return NavigationUI.navigateUp(navController, drawerLayout); } }
Puoi utilizzare questa stessa tecnica con la navigazione basata su BottomNavigatorView e navigazione basata su menu. Consulta Aggiornare i componenti dell'UI con NavigationUI per altri esempi.
Aggiungi destinazioni per le attività
Dopo aver collegato ogni schermata dell'app per utilizzare il componente Navigazione, e
non stai più utilizzando FragmentTransactions
per la transizione tra
destinazioni basate su frammenti, il passaggio successivo è eliminare startActivity
chiamate.
Per prima cosa, identifica i punti dell'app in cui sono disponibili due grafici di navigazione separati
e utilizzano startActivity
per la transizione da un ambiente all'altro.
Questo esempio contiene due grafici (A e B) e una chiamata startActivity()
a
transizione da A a B.
Kotlin
fun navigateToProductDetails(productId: String) { val intent = Intent(this, ProductDetailsActivity::class.java) intent.putExtra(KEY_PRODUCT_ID, productId) startActivity(intent) }
Java
private void navigateToProductDetails(String productId) { Intent intent = new Intent(this, ProductDetailsActivity.class); intent.putExtra(KEY_PRODUCT_ID, productId); startActivity(intent);
Poi, sostituisci questi elementi con una destinazione attività nel grafico A che rappresenti vai all'attività che ospita il grafico B. Se hai argomenti da passare destinazione iniziale del grafico B, puoi designarla nella destinazione dell'attività definizione di Kubernetes.
Nel seguente esempio, il grafico A definisce una destinazione di attività che prende una
product_id
insieme a un'azione. Il grafico B non contiene modifiche.
La rappresentazione XML dei grafici A e B potrebbe essere simile alla seguente:
<!-- Graph A -->
<navigation 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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list_fragment">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details"
tools:layout="@layout/product_details_host">
<argument
android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<navigation 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"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details_fragment">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Puoi accedere all'attività host del Grafico B utilizzando gli stessi meccanismi utilizzati per accedere alle destinazioni dei frammenti:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
Passa gli argomenti della destinazione dell'attività a un frammento della destinazione di inizio
Se l'attività relativa alla destinazione riceve extra, come nell'esempio precedente,
passarli direttamente alla destinazione iniziale come argomenti, ma devi
impostare manualmente il grafico di navigazione del padrone di casa all'interno dei campi
onCreate()
in modo da poter passare gli extra per intent come argomenti
come mostrato di seguito:
Kotlin
class ProductDetailsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_details_host) val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment val navController = navHostFramgent.navController navController .setGraph(R.navigation.product_detail_graph, intent.extras) } }
Java
public class ProductDetailsActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_details_host); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); NavController navController = navHostFragment.getNavController(); navController .setGraph(R.navigation.product_detail_graph, getIntent().getExtras()); } }
I dati possono essere estratti dagli argomenti dei frammenti Bundle
utilizzando il metodo
generata args, come mostrato nell'esempio seguente:
Kotlin
class ProductDetailsFragment : Fragment() { val args by navArgs<ProductDetailsArgs>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val productId = args.productId ... } ...
Java
public class ProductDetailsFragment extends Fragment { ProductDetailsArgs args; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); args = ProductDetailsArgs.fromBundle(requireArguments()); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { int productId = args.getProductId(); ... } ...
Combina le attività
È possibile combinare i grafici di navigazione nei casi in cui più attività condividano
lo stesso layout, ad esempio un elemento FrameLayout
semplice contenente un singolo frammento. Nella
nella maggior parte dei casi, basta combinare tutti gli elementi di
grafico di navigazione e aggiornamento di eventuali elementi della destinazione dell'attività in frammentazione
destinazioni.
L'esempio seguente combina i grafici A e B della sezione precedente:
Prima della combinazione:
<!-- Graph A -->
<navigation 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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details Host"
tools:layout="@layout/product_details_host">
<argument android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<navigation 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/product_detail_graph"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Dopo la combinazione:
<!-- Combined Graph A and B -->
<navigation 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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details" />
</fragment>
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Mantenere gli stessi nomi delle azioni durante l'unione può semplificare l'operazione
senza dover modificare il codebase esistente. Ad esempio:
Il valore navigateToProductDetail
rimane invariato qui. L'unica differenza è che
questa azione ora rappresenta la navigazione verso una destinazione di frammento all'interno della stessa
NavHost
anziché una destinazione per l'attività:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
Risorse aggiuntive
Per ulteriori informazioni relative alla navigazione, consulta i seguenti argomenti:
- Aggiornamento dei componenti UI con NavigationUI - Scopri come gestire la navigazione con la barra delle app in alto, il riquadro di navigazione a scomparsa e navigazione inferiore
- Navigazione di prova - Scopri come testare i flussi di lavoro di navigazione per la tua app