导航体系
Narrator Android · Jetpack Navigation · 导航图 / 层级 / 切换方式 / 回退栈策略
Narrator 全部页面跳转由 NavController 统一调度,导航配置集中在
res/navigation/nav_graph.xml。页面入口分为三类:
底部导航
侧边抽屉
子页面(navigate 入栈)。
1. 导航图定义
文件:res/navigation/nav_graph.xml 入口:scriptFragment(剧本列表)
<navigation android:id="@+id/nav_graph"
app:startDestination="@id/scriptFragment">
<!-- 16 个 <fragment> 节点,含 <argument> 与 <action> -->
</navigation>
2. 导航层级
各 Fragment 的父子层级与跳转关系:
scriptFragment (剧本列表 — 底部导航 / 启动入口)
│
├── scriptDetailFragment (剧本详情)
│ ├── outlineFragment (大纲编辑)
│ ├── deductionFragment (演绎 / 三栏)
│ ├── scriptCharactersFragment (角色管理)
│ │ └── scriptCharacterEditFragment (剧本角色编辑)
│ ├── scriptSettingsFragment (剧本设置)
│ └── newChapterFragment (添加章节)
│
├── characterFragment (角色管理 — 底部导航)
│ ├── characterEditFragment (角色详情编辑)
│ └── scriptCharactersFragment (剧本角色管理)
│
├── aiFragment (AI 配置 — 底部导航)
│ └── aiProviderDetailFragment (AI 服务商详情)
│
├── uiFragment (UI 主题 — 底部导航)
├── settingsFragment (设置 — 底部导航)
├── dialogueFragment (对话 — 抽屉)
├── manualFragment (手册 — 抽屉)
└── trashFragment (回收站 — 抽屉)
3. 应用壳入口
3.1 MainActivity(主壳)
| 项 | 说明 |
|---|---|
| 文件 | MainActivity.java |
| 布局 | res/layout/activity_main.xml |
| 对应 Web | web/src/App.vue |
| 组成 | 标题栏 + NavHostFragment(内容区)+ BottomNavigationView + NavigationView(右侧抽屉) |
3.2 底部导航栏(BottomNavigationView)
| Tab | Fragment | 菜单 ID | 图标 |
|---|---|---|---|
| 剧本 | ScriptFragment | nav_script | ic_script |
| 角色 | CharacterFragment | nav_characters | ic_characters |
| AI | AiFragment | nav_ai | ic_ai |
| UI | UiFragment | nav_ui | ic_ui |
| 设置 | SettingsFragment | nav_settings | ic_settings |
3.3 侧边抽屉(NavigationView · 右侧)
| 菜单项 | Fragment | 菜单 ID |
|---|---|---|
| 对话 | DialogueFragment | nav_dialogue |
| 手册 | ManualFragment | nav_manual |
| 回收站 | TrashFragment | nav_trash |
4. 导航方式
| 方式 | 触发 | 实现 |
|---|---|---|
| 底部导航切换 | 点击底部 Tab | BottomNavigationView.setOnItemSelectedListener → NavigationUI.onNavDestinationSelected(item, controller) |
| 抽屉导航切换 | 点击抽屉菜单项 | NavigationView.setNavigationItemSelectedListener → 关闭抽屉 + NavigationUI.onNavDestinationSelected |
| 按钮点击导航 | 页面内按钮 | BaseFragment.navigateTo(destId) / navigateToWithArgs(destId, args) |
| 标题栏返回按钮 | 点击 ← | navController.popBackStack()(由 header_back_btn 统一处理) |
| 系统返回键 | 系统返回 | OnBackPressedCallback:抽屉打开先收起,否则 popBackStack() |
| 标签默认 | — | app:defaultNavHost="true" 自动处理返回栈 |
🎯 标题栏按钮规则(
MainActivity.isSubPageDest()):
顶层页面显示 ≡ 菜单按钮(点击打开抽屉);
子页面显示 ← 返回按钮(点击 popBackStack())。
演绎页(deductionFragment)特殊:同时显示返回按钮与 ⋮ 三圆点菜单。
5. 回退栈管理策略
所有顶层页面切换(底部 Tab / 抽屉菜单)统一使用以下 NavOptions:
new NavOptions.Builder()
.setLaunchSingleTop(true)
.setPopUpTo(R.id.scriptFragment, false)
.build()
| 选项 | 含义 |
|---|---|
launchSingleTop(true) | 目标已在栈顶时不重复创建 |
popUpTo(scriptFragment, false) | 跳转前弹出 scriptFragment 之上的所有页面,但保留 scriptFragment 本身 |
💡 效果:反复切换底部 Tab 不会导致回退栈无限膨胀,按返回键始终回到首页(剧本列表)。
v3 修复了顶部 Tab 重复切换导致栈无限增长的问题。
5.1 NavController 初始化
为避免 Navigation.findNavController() 的时序问题,MainActivity 通过 FragmentManager 直接获取 NavHostFragment:
getSupportFragmentManager().executePendingTransactions();
NavHostFragment navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
navController = navHostFragment.getNavController();
6. 动态主题配色
导航栏(顶部标题栏 / 底部 / 抽屉)在导航变化时由 MainActivity.applyDynamicThemeColors() 应用当前主题色:
- 顶部标题栏背景(
headerBg())与标题文字色(textPrimary()) - 菜单 / 返回按钮图标
setColorFilter(textPrimary()) - 底部导航栏背景
surface(),选中文字/图标色primary(),未选中textSecondary() - 底部选中指示条颜色
primary(),并按选中项宽度做 200ms 滑动动画 - 抽屉背景
sidebarBg(),选中项onPrimary(),未选中textSecondary()
7. 页面标题与可见性
- 标题栏文字自动取
NavDestination.getLabel()。 - 除演绎/抽屉页面外,标题栏始终可见(v4 起首页不再隐藏标题栏)。
- 演绎页与 AI 商详情页、抽屉页面隐藏底部导航及指示条。
- 底部导航 / 抽屉选中态高亮跟随当前 destination(
navDrawer.setCheckedItem(...))。