Zagnieżdżone wykresy

Procesy logowania, kreatory i inne procesy podrzędne w aplikacji są zwykle najlepiej zobrazowane jako zagnieżdżone wykresy nawigacyjne. Dzięki zagnieżdżeniu w ten sposób samodzielnych podnawigacji łatwiej jest zrozumieć główny przepływ interfejsu użytkownika aplikacji i nim zarządzać.

Ponadto zagnieżdżonych wykresów można używać wielokrotnie. Zapewniają też poziom hermetyzacji – miejsca docelowe poza zagnieżdżonym wykresem nie mają bezpośredniego dostępu do żadnego z miejsc docelowych w zagnieżdżonym wykresie. Zamiast tego należy zastosować metodę navigate() w samym zagnieżdżonym wykresie, gdzie logika wewnętrzna może się zmieniać bez wpływu na pozostałą część wykresu.

Przykład

Wykres nawigacyjny najwyższego poziomu aplikacji powinien zaczynać się od miejsca docelowego, które użytkownik widzi po uruchomieniu aplikacji. Powinien zawierać miejsca docelowe, które widzą podczas poruszania się po aplikacji.

Rysunek 1. Wykres nawigacyjny najwyższego poziomu.

Korzystając z przykładowego wykresu nawigacyjnego najwyższego poziomu z rys. 1, załóżmy, że chcesz, aby użytkownik widział ekrany title_screen i enroll tylko przy pierwszym uruchomieniu aplikacji. Następnie informacje o użytkowniku są przechowywane i przy kolejnych uruchomieniach aplikacji należy kierować je bezpośrednio na ekran dopasowania.

Sprawdzoną metodą jest ustawienie ekranu dopasowania jako miejsca docelowego wykresu nawigacyjnego najwyższego poziomu, a następnie przenoszenie ekranów tytułu i rejestracji do zagnieżdżonego wykresu, tak jak to widać na ilustracji 1:

Rysunek 2. Wykres nawigacyjny najwyższego poziomu zawiera teraz zagnieżdżony wykres.

Po uruchomieniu ekranu dopasowania sprawdź, czy jest zarejestrowany użytkownik. Jeśli użytkownik nie jest zarejestrowany, przejdź do ekranu rejestracji.

Więcej informacji o scenariuszach nawigacji warunkowej znajdziesz w artykule Nawigacja warunkowa.

Utwórz

Aby utworzyć zagnieżdżony wykres nawigacyjny za pomocą opcji Utwórz, użyj funkcji NavGraphBuilder.navigation(). Korzystasz z navigation() tak samo jak NavGraphBuilder.composable() i NavGraphBuilder.dialog() przy dodawaniu miejsc docelowych do wykresu.

Główna różnica polega na tym, że navigation tworzy zagnieżdżony wykres, a nie nowe miejsce docelowe. Następnie wywołujesz metody composable i dialog w funkcji lambda funkcji navigation, aby dodać miejsca docelowe do zagnieżdżonego wykresu.

Zobacz, jak ten fragment implementuje wykres z Rys. 2 za pomocą funkcji Utwórz:

NavHost(navController, startDestination = "title_screen") {
    composable("title_screen") {
        TitleScreen(
            onPlayClicked = { navController.navigate("register") },
            onLeaderboardsClicked = { /* Navigate to leaderboards */ }
        )
    }
    composable("register") {
        RegisterScreen(
            onSignUpComplete = { navController.navigate("gameInProgress") }
        )
    }
    navigation(startDestination = "match", route = "gameInProgress") {
        composable("match") {
            MatchScreen(
                onStartGame = { navController.navigate("in_game") }
            )
        }
        composable("in_game") {
            InGameScreen(
                onGameWin = { navController.navigate("results_winner") },
                onGameLose = { navController.navigate("game_over") }
            )
        }
        composable("results_winner") {
            ResultsWinnerScreen(
                onNextMatchClicked = {
                    navController.navigate("match") {
                        popUpTo("match") { inclusive = true }
                    }
                },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("game_over") {
            GameOverScreen(
                onTryAgainClicked = {
                    navController.navigate("match") {
                        popUpTo("match") { inclusive = true }
                    }
                }
            )
        }
    }
}

Aby nawigować bezpośrednio do zagnieżdżonego miejsca docelowego, użyj właściwości route tak samo jak w przypadku każdego innego miejsca docelowego. Trasy to pojęcie globalne, do którego może dotrzeć każdy ekran:

navController.navigate("match")

Funkcje rozszerzeń

Aby dodać do wykresu miejsca docelowe, możesz użyć funkcji rozszerzenia w NavGraphBuilder. Tych funkcji rozszerzenia możesz używać razem z gotowymi metodami rozszerzenia navigation, composable i dialog.

Możesz na przykład użyć funkcji rozszerzenia, aby dodać zagnieżdżony wykres przedstawiony w poprzedniej sekcji:

fun NavGraphBuilder.addNestedGraph(navController: NavController) {
    navigation(startDestination = "match", route = "gameInProgress") {
        composable("match") {
            MatchScreen(
                onStartGame = { navController.navigate("in_game") }
            )
        }
        composable("in_game") {
            InGameScreen(
                onGameWin = { navController.navigate("results_winner") },
                onGameLose = { navController.navigate("game_over") }
            )
        }
        composable("results_winner") {
            ResultsWinnerScreen(
                onNextMatchClicked = { navController.navigate("match") },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("game_over") {
            GameOverScreen(
                onTryAgainClicked = { navController.navigate("match") }
            )
        }
    }
}

Możesz następnie wywołać tę funkcję w elemencie lambda przekazywanym do interfejsu NavHost, zamiast wywoływać nawigację w tekście. Oto przykład:

@Composable
fun MyApp() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "title_screen") {
        composable("title_screen") {
            TitleScreen(
                onPlayClicked = { navController.navigate("register") },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("register") {
            RegisterScreen(
                onSignUpComplete = { navController.navigate("gameInProgress") }
            )
        }

        // Add the nested graph using the extension function
        addNestedGraph(navController)
    }
}

Plik XML

Jeśli używasz kodu XML, możesz użyć edytora nawigacji do utworzenia zagnieżdżonego wykresu. W tym celu wykonaj następujące czynności:

  1. W edytorze nawigacji naciśnij i przytrzymaj klawisz Shift, a następnie kliknij miejsca docelowe, które chcesz uwzględnić na zagnieżdżonym wykresie.
  2. Kliknij prawym przyciskiem myszy, aby otworzyć menu kontekstowe, i wybierz Przenieś do wykresu zagnieżdżonego > Nowy wykres. Miejsca docelowe są przedstawione na zagnieżdżonym wykresie. Rysunek 2 przedstawia zagnieżdżony wykres w Edytorze nawigacji:

    Rysunek 2. Wykres zagnieżdżony w edytorze nawigacji
  3. Kliknij zagnieżdżony wykres. W panelu Atrybuty są widoczne te atrybuty:

    • Typ zawierający „Wykres zagnieżdżony”.
    • Identyfikator: zawiera przypisany przez system identyfikator zagnieżdżonego wykresu. Ten identyfikator służy do odwoływania się w kodzie do zagnieżdżonego wykresu.
  4. Kliknij dwukrotnie zagnieżdżony wykres, aby wyświetlić jego miejsca docelowe.

  5. Kliknij kartę Tekst, by przełączyć się na widok XML. Do wykresu dodaliśmy zagnieżdżony wykres nawigacyjny. Ten wykres nawigacyjny ma własne elementy navigation, własny identyfikator i atrybut startDestination, który wskazuje pierwsze miejsce docelowe na zagnieżdżonym wykresie:

    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:android="http://schemas.android.com/apk/res/android"
        app:startDestination="@id/mainFragment">
        <fragment
            android:id="@+id/mainFragment"
            android:name="com.example.cashdog.cashdog.MainFragment"
            android:label="fragment_main"
            tools:layout="@layout/fragment_main" >
            <action
                android:id="@+id/action_mainFragment_to_sendMoneyGraph"
                app:destination="@id/sendMoneyGraph" />
            <action
                android:id="@+id/action_mainFragment_to_viewBalanceFragment"
                app:destination="@id/viewBalanceFragment" />
        </fragment>
        <fragment
            android:id="@+id/viewBalanceFragment"
            android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
            android:label="fragment_view_balance"
            tools:layout="@layout/fragment_view_balance" />
        <navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
            <fragment
                android:id="@+id/chooseRecipient"
                android:name="com.example.cashdog.cashdog.ChooseRecipient"
                android:label="fragment_choose_recipient"
                tools:layout="@layout/fragment_choose_recipient">
                <action
                    android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
                    app:destination="@id/chooseAmountFragment" />
            </fragment>
            <fragment
                android:id="@+id/chooseAmountFragment"
                android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
                android:label="fragment_choose_amount"
                tools:layout="@layout/fragment_choose_amount" />
        </navigation>
    </navigation>
    
  6. Przekaż w kodzie identyfikator zasobu działania łączącego wykres główny z zagnieżdżonym wykresem:

    Kotlin

    view.findNavController().navigate(R.id.action_mainFragment_to_sendMoneyGraph)
    

    kawa

    Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph);
    
  7. Po powrocie na kartę Projekt wróć do wykresu głównego, klikając Root.

Nawiąż do innych wykresów nawigacyjnych, używając opcji „Uwzględnij”

Innym sposobem modularyzacji struktury wykresu jest uwzględnienie jednego wykresu w innym przy użyciu elementu <include> w nadrzędnym wykresie nawigacyjnym. Dzięki temu uwzględniony wykres można zdefiniować zbiorczo w osobnym module lub projekcie, co maksymalizuje możliwość wielokrotnego wykorzystania.

Ten fragment kodu pokazuje, jak używać <include>:

<!-- (root) nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<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/nav_graph"
    app:startDestination="@id/fragment">

    <strong><include app:graph="@navigation/included_graph" /></strong>

    <fragment
        android:id="@+id/fragment"
        android:name="com.example.myapplication.BlankFragment"
        android:label="Fragment in Root Graph"
        tools:layout="@layout/fragment_blank">
        <strong><action
            android:id="@+id/action_fragment_to_second_graph"
            app:destination="@id/second_graph" /></strong>
    </fragment>

    ...
</navigation>
<!-- included_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<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"
    <strong>android:id="@+id/second_graph"</strong>
    app:startDestination="@id/includedStart">

    <fragment
        android:id="@+id/includedStart"
        android:name="com.example.myapplication.IncludedStart"
        android:label="fragment_included_start"
        tools:layout="@layout/fragment_included_start" />
</navigation>

Dodatkowe materiały

Więcej informacji o nawigacji znajdziesz w tych dodatkowych materiałach.

Próbki

Ćwiczenia z programowania

Filmy