具有多个顶级目的地和返回键行为的导航图
Navigation graph with multiple top level destinations and back-key behaviors
当我的导航图中有多个顶级目的地时,当我单击后退键完成我的应用程序时,startDestination
以外的那些目的地不会完成但会弹出 startDestination
片段.
- FragmentA (startDestination) -> 使用返回键立即完成
- FragmentB -> 使用返回键返回 FragmentA
- FragmentC -> 使用返回键返回 FragmentA
我试过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"
.
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。
当我的导航图中有多个顶级目的地时,当我单击后退键完成我的应用程序时,startDestination
以外的那些目的地不会完成但会弹出 startDestination
片段.
- FragmentA (startDestination) -> 使用返回键立即完成
- FragmentB -> 使用返回键返回 FragmentA
- FragmentC -> 使用返回键返回 FragmentA
我试过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 addingapp:defaultNavHost="true"
.
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 byFragmentManager.getPrimaryNavigationFragment()
.
由于您将 app:defaultNavHost
设置为 true,您的 NavHost 会拦截后退键并导致弹出 startDestination FragmentA。