具有多个顶级目的地和返回键行为的导航图

Navigation graph with multiple top level destinations and back-key behaviors

当我的导航图中有多个顶级目的地时,当我单击后退键完成我的应用程序时,startDestination 以外的那些目的地不会完成但会弹出 startDestination 片段.

我试过app:popUpTo="@id/nav_graph"and/orapp:popUpToInclusive="true"但是没用。

Activity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);

        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);

        AppBarConfiguration appBarConfiguration =
                new AppBarConfiguration.Builder(R.id.fragmentA, R.id.fragmentB, R.id.fragmentC)
                        .setOpenableLayout(drawerLayout)
                        .build();

        NavigationView navView = findViewById(R.id.nav_view);
        NavigationUI.setupWithNavController(navView, navController);

        Toolbar toolbar = findViewById(R.id.toolbar);
        NavigationUI.setupWithNavController(
                toolbar,
                navController,
                appBarConfiguration
        );
    }
}

layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <!-- Layout to contain contents of navigation_view body of screen (drawer will slide over this) -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            android:minHeight="?attr/actionBarSize"
            android:theme="@style/AppTheme.AppBarOverlay"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

        <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/toolbar"
            app:navGraph="@navigation/nav_graph" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <!-- Container for contents of drawer - use NavigationView to make configuration easier -->
    <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"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/navigation_header"
        app:menu="@menu/navigation_view" />

</androidx.drawerlayout.widget.DrawerLayout>

nav_graph:

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/fragmentA">

    <fragment
        android:id="@+id/fragmentA"
        android:name="com.Whosebug.FragmentA"
        android:label="@string/fragmentA">
        <argument
            android:name="message"
            android:defaultValue="(no message)"
            app:argType="string" />
    </fragment>

    <action
        android:id="@+id/toFragmentAWithMessage1"
        app:destination="@id/fragmentA">
        <argument
            android:name="message"
            android:defaultValue="message 1"
            app:argType="string" />
    </action>

    <action
        android:id="@+id/toFragmentAWithMessage2"
        app:destination="@id/fragmentA">
        <argument
            android:name="message"
            android:defaultValue="message 2"
            app:argType="string" />
    </action>

    <fragment
        android:id="@+id/fragmentB"
        android:name="com.Whosebug.navigationui.FragmentB"
        android:label="@string/fragmentB">
        <argument
            android:name="message"
            android:defaultValue="(no message)"
            app:argType="string" />
    </fragment>

    <action
        android:id="@+id/toFragmentBWithMessage1"
        app:destination="@id/fragmentB">
        <argument
            android:name="message"
            android:defaultValue="message 1"
            app:argType="string" />
    </action>

    <action
        android:id="@+id/toFragmentBWithMessage2"
        app:destination="@id/fragmentB">
        <argument
            android:name="message"
            android:defaultValue="message 2"
            app:argType="string" />
    </action>

    <fragment
        android:id="@+id/fragmentC"
        android:name="com.Whosebug.FragmentC"
        android:label="@string/fragmentC">
        <argument
            android:name="message"
            android:defaultValue="(no message)"
            app:argType="string" />
    </fragment>

    <action
        android:id="@+id/toFragmentCWithMessage1"
        app:destination="@id/fragmentC">
        <argument
            android:name="message"
            android:defaultValue="message 1"
            app:argType="string" />
    </action>

    <action
        android:id="@+id/toFragmentCWithMessage2"
        app:destination="@id/fragmentC">
        <argument
            android:name="message"
            android:defaultValue="message 2"
            app:argType="string" />
    </action>

</navigation>

我知道覆盖 OnBackPressedCallback 是一种解决方法。

public class FragmentB extends Fragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        requireActivity().getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {
                remove();
                requireActivity().finish();
            }
        });
    }
}

有没有更根本的解决办法?

只需删除 false 或将 layout.xml 中 NavHostFragment 的 <fragment>app:defaultNavHost 属性设置为 layout.xml:

<fragment
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:defaultNavHost="false"
    />

Interact programmatically with the Navigation component

Note that setPrimaryNavigationFragment(finalHost) lets your NavHost intercept system Back button presses. You can also implement this behavior in your NavHost XML by adding app:defaultNavHost="true".

setPrimaryNavigationFragment

Set a currently active fragment in this FragmentManager as the primary navigation fragment.

The primary navigation fragment's child FragmentManager will be called first to process delegated navigation actions such as FragmentManager.popBackStack() if no ID or transaction name is provided to pop to. Navigation operations outside of the fragment system may choose to delegate those actions to the primary navigation fragment as returned by FragmentManager.getPrimaryNavigationFragment().

由于您将 app:defaultNavHost 设置为 true,您的 NavHost 会拦截后退键并导致弹出 startDestination FragmentA。