如何在Sheet内实现导航且无需两个NavController与NavHost
核心思路:将Sheet及其内部界面纳入同一导航图分支
把Sheet根页面、内部所有界面都加入主导航图,通过目的地弹出规则和自定义导航动画区分主界面与Sheet层级,同时保证Sheet内跳转动画的一致性。
步骤1:在导航图中定义Sheet相关目的地
在nav_graph.xml里,给Sheet根目的地设置launchSingleTop="true",并为Sheet内跳转配置专属动画:
<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/main_home"> <!-- 主界面分支 --> <fragment android:id="@+id/main_home" android:name="com.example.MainHomeFragment" android:label="Home"> <action android:id="@+id/action_home_to_sheet_root" app:destination="@id/sheet_root" app:launchSingleTop="true" app:enterAnim="@anim/sheet_slide_up" app:exitAnim="@anim/fade_out" app:popEnterAnim="@anim/fade_in" app:popExitAnim="@anim/sheet_slide_down"/> </fragment> <!-- Sheet分支:根页面 --> <fragment android:id="@+id/sheet_root" android:name="com.example.SheetRootFragment" android:label="SheetRoot"> <action android:id="@+id/action_sheet_root_to_sheet_detail1" app:destination="@id/sheet_detail1" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right"/> <action android:id="@+id/action_sheet_root_to_sheet_detail2" app:destination="@id/sheet_detail2" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right"/> </fragment> <!-- Sheet内部界面 --> <fragment android:id="@+id/sheet_detail1" android:name="com.example.SheetDetail1Fragment" android:label="SheetDetail1"/> <fragment android:id="@+id/sheet_detail2" android:name="com.example.SheetDetail2Fragment" android:label="SheetDetail2"/> </navigation>
步骤2:监听导航变化控制Sheet显示
在主Activity中通过OnDestinationChangedListener监听页面跳转,同步Sheet的显示/隐藏状态:
class MainActivity : AppCompatActivity() { private lateinit var navController: NavController private var sheetRootFragment: SheetRootFragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) navController = findNavController(R.id.nav_host_fragment) navController.addOnDestinationChangedListener { _, destination, _ -> when (destination.id) { R.id.sheet_root, R.id.sheet_detail1, R.id.sheet_detail2 -> { if (sheetRootFragment == null) { sheetRootFragment = SheetRootFragment() supportFragmentManager.beginTransaction() .add(R.id.sheet_container, sheetRootFragment!!) .commitNow() } sheetRootFragment?.showSheet() } else -> { sheetRootFragment?.hideSheet { supportFragmentManager.beginTransaction() .remove(sheetRootFragment!!) .commitNow() sheetRootFragment = null } } } } } override fun onBackPressed() { // 优先处理Sheet内部回退 val sheetNavHost = sheetRootFragment?.childFragmentManager?.findFragmentById(R.id.sheet_nav_host) as? NavHostFragment if (sheetNavHost?.navController?.popBackStack() != true) { // Sheet无回退项时,关闭Sheet navController.popBackStack() } else { super.onBackPressed() } } }
步骤3:Sheet内部嵌入子NavHost(简化管理)
若Sheet内界面较多,可在SheetRootFragment中嵌入子NavHost,让Sheet内跳转逻辑独立但仍属于主导航分支:
<!-- sheet_root_fragment.xml --> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.fragment.app.FragmentContainerView android:id="@+id/sheet_nav_host" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:navGraph="@navigation/sheet_nav_graph"/> </androidx.constraintlayout.widget.ConstraintLayout>
关键注意事项
- 动画一致性:Sheet弹出/隐藏用上下滑动动画,内部跳转统一用横向滑动,通过导航图的
action配置实现。 - 回退栈优先级:重写
onBackPressed,先处理Sheet内部回退,再处理主界面回退。 - 状态同步:Sheet的显示状态与导航目的地绑定,避免页面重建时状态错乱。
内容的提问来源于stack exchange,提问作者Hazem
相关产品推荐
相关产品推荐

