10、Navigation进阶:BottomNavigationView集成、条件导航、嵌套导航、动画、实战:多页面导航架构。
各位同学,欢迎来到Navigation的进阶篇。说实话,基础用法大家看看官方文档都能上手,但真正到了项目里,各种奇葩需求就来了——底部导航栏怎么和Fragment联动?登录了才能进个人中心怎么办?页面跳转动画卡顿怎么调?
这一章,我把这些年踩过的坑、总结的经验全掏出来。咱们从BottomNavigationView集成开始,一步步把条件导航、嵌套导航、动画这些硬骨头啃下来。最后我会带你们搭一个真实的多页面导航架构,这玩意儿在电商、社交App里天天用。
10.1 BottomNavigationView + Navigation 深度集成
先说说底部导航栏。很多新手喜欢自己写Fragment切换逻辑,结果各种状态丢失、回退栈混乱。我建议直接用Navigation组件来管理,省心又规范。
核心思路:每个Tab对应一个NavGraph,通过NavController统一管理。
具体怎么做?看代码:
// 在布局中定义BottomNavigationView和NavHostFragment
<androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph_main"
app:defaultNavHost="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
然后在Activity里绑定:
val navController = findNavController(R.id.nav_host_fragment)
bottom_nav.setupWithNavController(navController)
嗯,这里要注意。setupWithNavController这个方法会自动处理选中状态和页面切换,但有个坑——它默认会重新创建Fragment。如果你每个Tab的数据量很大,建议在NavGraph里设置launchSingleTop="true"。
我的经验:曾经有个项目,用户频繁切换Tab导致内存暴涨。后来我在每个Tab的NavGraph里加了launchSingleTop,问题就解决了。说白了,就是避免重复创建Fragment实例。
10.2 条件导航:登录拦截与权限控制
条件导航,说白了就是根据状态决定跳不跳转。最常见的场景:用户点“个人中心”,没登录就跳登录页。
Navigation官方提供了NavDeepLink和NavArgument来实现,但我个人习惯用全局拦截器的方式。为什么?因为更灵活,而且代码好维护。
来看一个实战例子:
// 在NavHostFragment的宿主Activity中监听导航事件
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.personalCenterFragment && !isLogin) {
// 拦截,跳转到登录页
navController.navigate(R.id.loginFragment)
}
}
这种方式简单粗暴,但有个问题——用户登录成功后怎么回到原来的目标?我建议用savedStateHandle来传递结果:
// 登录成功后
val previousBackStackEntry = navController.previousBackStackEntry
previousBackStackEntry?.savedStateHandle?.set("loginResult", true)
navController.popBackStack()
避坑指南:我曾经在条件导航里直接用了navigate()方法,结果导致回退栈混乱。后来发现,应该先popBackStack()再navigate(),或者用navigate()的NavOptions设置popUpTo。你想想看,回退栈就像一摞盘子,放错了顺序后面全乱套。
10.3 嵌套导航:模块化与子图管理
当项目大了,一个NavGraph里塞几十个目的地,那画面太美我不敢看。这时候就需要嵌套导航——说白了,就是把子模块的导航图独立出来。
举个例子,购物车模块可能有:购物车列表、结算页、支付成功页。我们可以单独建一个nav_graph_cart.xml:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph_cart"
app:startDestination="@id/cartListFragment">
<fragment android:id="@+id/cartListFragment" ... />
<fragment android:id="@+id/checkoutFragment" ... />
<fragment android:id="@+id/paymentSuccessFragment" ... />
</navigation>
然后在主图里引用:
<include app:graph="@navigation/nav_graph_cart" />
这样做的好处很明显:每个模块的导航逻辑独立,团队协作时互不干扰。而且,子图可以有自己的startDestination,方便单独测试。
核心要点:嵌套导航的层级不要超过3层,否则回退栈逻辑会变得非常复杂。我一般控制在2层——主图+子图。
10.4 导航动画:让页面切换更丝滑
动画这事儿,做得好用户觉得流畅,做不好就感觉卡顿。Navigation组件支持三种动画:进入、退出、弹出。
在NavGraph里配置:
<action android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
动画文件示例(slide_in_right.xml):
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%"
android:toXDelta="0%"
android:duration="300" />
</set>
我个人习惯用Shared Element Transition做更高级的动画,比如图片从列表页飞到详情页。但要注意,这种动画对内存要求高,低端机容易掉帧。我的建议是:只在高端机上启用,或者用Transition的excludeTarget排除掉复杂视图。
小技巧:如果你发现动画卡顿,试试把duration从300ms降到200ms。用户其实感知不到这100ms的差异,但流畅度会明显提升。
10.5 实战:多页面导航架构
好了,理论说完了,咱们来搭一个真实的多页面导航架构。这个架构我在一个电商App里用过,支撑了20多个页面、5个Tab,运行得很稳。
先画个架构图:
架构的核心思想:
- 一个Activity:MainActivity作为唯一宿主,持有NavHostFragment
- 五个子图:每个Tab对应一个独立的NavGraph,互不干扰
- 全局拦截器:在NavController的OnDestinationChangedListener里做条件导航
- 统一动画:在NavGraph的
<action>里配置,保持全局一致性
代码结构示例:
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navController = findNavController(R.id.nav_host_fragment)
bottom_nav.setupWithNavController(navController)
// 全局拦截器
navController.addOnDestinationChangedListener { _, dest, _ ->
when (dest.id) {
R.id.personalCenterFragment -> {
if (!isLogin) {
navController.navigate(R.id.loginFragment)
}
}
R.id.checkoutFragment -> {
if (!isLogin) {
navController.navigate(R.id.loginFragment)
}
}
}
}
}
}
总结一下:这个架构的好处是——每个模块独立开发、独立测试;全局拦截器统一管理权限;动画配置集中维护。我在两个正式项目里用了这套方案,团队反馈都说好。
好了,这一章的内容就到这里。Navigation进阶的东西其实不少,但核心就是这几个点:底部导航集成、条件导航、嵌套导航、动画。把这些吃透了,大部分App的导航需求你都能搞定。
公众号:蓝海资料掘金营,微信deep3321