AppBar与菜单
AppBar与菜单:保留第四版正文机制,以所有者—状态—结果合同、事件轨迹和章专属故障完成可重放验收。
学习目标
- 能沿“AppBar与菜单”的用户事件解释Android组件、状态所有者、线程与销毁边界。
- 能围绕“掌握 Toolbar 替代 ActionBar——菜单资源、Options Menu、Overflow 和 Navigation Icon 的配置。”改出一个可运行结果,并用前后状态而非组件数量验收。
- 能在旋转、进程重建、拒权、离线或无效输入中选择适用反例,定位首个状态分叉。
- 能用双视口、大字体、焦点顺序、状态语义和对比度截图独立重放结论,并标明第四版机制与现代targetSdk政策的边界。
顶栏不只是标题——它是命令中枢
↡Material Design 顶栏,可显示标题、导航图标和操作菜单,现代 App 多用 Toolbar 实现。(Toolbar)承载标题、返回箭头和「更多」菜单。Fragment 时代默认
↡旧版框架提供的顶栏,现推荐用 Toolbar + setSupportActionBar 替代。不够灵活——把 Toolbar 放进布局,才能和 CoordinatorLayout、Drawer 配合。
启用 Toolbar
res/layout/activity_main.xml:
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/app_name" />setSupportActionBar(findViewById(R.id.toolbar))
supportActionBar?.setDisplayHomeAsUpEnabled(true)主题使用 NoActionBar 变体,避免双顶栏。
Options Menu
res/menu/crime_list_menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_add"
android:icon="@drawable/ic_add"
android:title="@string/add_crime"
app:showAsAction="ifRoom" />
<item
android:id="@+id/menu_settings"
android:title="@string/settings"
app:showAsAction="never" />
</menu>override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.crime_list_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_add -> { viewModel.addCrime(); true }
R.id.menu_settings -> { startActivity(Intent(this, SettingsActivity::class.java)); true }
android.R.id.home -> { onBackPressedDispatcher.onBackPressed(); true }
else -> super.onOptionsItemSelected(item)
}
}showAsAction="ifRoom" 有空间时显示图标;never 进 Overflow(⋮)。
AppBar(Toolbar)从左到右是导航图标、标题、若干 action 图标和溢出入口 ⋮。菜单项统一在 res/menu/*.xml 里声明,由 onCreateOptionsMenu 加载;app:showAsAction(ifRoom/always/never)决定它显示在栏上还是收进 ⋮;点击最终都回到 onOptionsItemSelected 按 itemId 分发处理。① 列表页配置 Toolbar
在列表 Activity 的布局中放置 MaterialToolbar,主题使用 NoActionBar
变体避免双顶栏。通过 setSupportActionBar(toolbar) 将 Toolbar 设为 Activity
的 App Bar——列表页的 Toolbar
通常只显示标题和菜单项(如"添加"),不显示返回箭头(home 图标)。
小结
- NoActionBar 主题 + 布局 Toolbar + setSupportActionBar
- menu XML + inflate + onOptionsItemSelected
- Home/Up 与返回栈一致;Navigation 组件可自动绑定
练习
问题 1:“第14章 The App Bar”覆盖哪些正式节点和项目主线?
问题 2:怎样建立本页最小可执行实验?
问题 3:为什么只在正常点击路径运行不能证明完成?
问题 4:怎样设计能推翻当前实现的反例?
问题 5:从第4版语境迁移到现代目标SDK时如何控制变量?
问题 6:本页达到独立交接标准需要什么?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- App Bar
Material Design 顶栏,可显示标题、导航图标和操作菜单,现代 App 多用 Toolbar 实现。
- ActionBar
旧版框架提供的顶栏,现推荐用 Toolbar + setSupportActionBar 替代。
- Toolbar
可嵌入布局的顶栏控件,功能同 ActionBar 但可定制位置与样式。
“AppBar与菜单”不使用未获授权的纸书正文;InformIT出版信息与授权电子版完整目录只用于确认第四版32章、269个正式目录节点和时代语境,第四版官方勘误用于识别工具链变更。下列中文解释、图示、交互、代码与练习均为独立教学重写,平台行为再以Android Developers的一手文档复核。
为什么“AppBar与菜单”必须回到可观察状态
“AppBar与菜单”的学习结果不是记住类名,而是能预测“掌握 Toolbar 替代 ActionBar——菜单资源、Options Menu、Overflow 和 Navigation Icon 的配置。”在一次输入、一次重建和一次失败中的不同状态,并指出哪条Android合同产生差异。
第四版机制逐项深读
14. The App Bar
在“AppBar与菜单”中,“14. The App Bar”把顶层动作、导航和溢出菜单接入当前界面状态;菜单创建与选择回调要在窄屏、无数据和权限受限时仍有可理解反馈。
AppCompat Default App Bar
在“AppBar与菜单”中,“AppCompat Default App Bar”把顶层动作、导航和溢出菜单接入当前界面状态;菜单创建与选择回调要在窄屏、无数据和权限受限时仍有可理解反馈。
Menus
在“AppBar与菜单”中,“Menus”把顶层动作、导航和溢出菜单接入当前界面状态;菜单创建与选择回调要在窄屏、无数据和权限受限时仍有可理解反馈。
Using the Android Asset Studio
在“AppBar与菜单”中,“Using the Android Asset Studio”通过资源限定符和主题属性把内容与设备配置解耦;最终值由资源匹配和主题继承共同决定。
For the More Curious: App Bar vs Action Bar vs Toolbar
在“AppBar与菜单”中,“For the More Curious: App Bar vs Action Bar vs Toolbar”把顶层动作、导航和溢出菜单接入当前界面状态;菜单创建与选择回调要在窄屏、无数据和权限受限时仍有可理解反馈。
For the More Curious: Accessing the AppCompat App Bar
在“AppBar与菜单”中,“For the More Curious: Accessing the AppCompat App Bar”把顶层动作、导航和溢出菜单接入当前界面状态;菜单创建与选择回调要在窄屏、无数据和权限受限时仍有可理解反馈。
Challenge: An Empty View for the RecyclerView
在“AppBar与菜单”中,验证“Challenge: An Empty View for the RecyclerView”用插入、删除、重排和多类型数据检查item身份、焦点与点击目标,记录首个错误绑定。
“AppBar与菜单”验收回顾
“AppBar与菜单”只有在双视口、大字体、焦点顺序、状态语义和对比度截图能够从相同基线再次得到相同断言时才通过;第四版机制与现代平台政策分别记录,不用新API名称掩盖旧行为。
章专属可重放状态实验
先预测“创建菜单、选择动作、数据变空与配置重建”发生后,MenuHost、当前 Fragment 与列表状态应怎样改变菜单可见性、动作启用、空列表和导航结果;再操作三个实验。第四版示例与当前 Android 政策分别记录,实验不把新 API 名称倒填为原书内容。
实验一:所有者—状态—结果合同
选择任一正式目录节点和正常/边界场景,检查它是否真的进入本章状态合同。目录标题只有同时出现在解释、可视状态和交付证据中才算覆盖。
Owner · state · observable result
AppBar与菜单:状态合同
把 CriminalIntent 的新增、删除、搜索和导航动作接入当前界面状态
验证场景
第四版正式目录节点
app-bar · 正常任务
14. The App Bar:固定 SDK、设备配置和初始状态,触发“创建菜单、选择动作、数据变空与配置重建”
冻结入口:14. The App Bar
记录MenuHost、当前 Fragment 与列表状态的初始菜单可见性、动作启用、空列表和导航结果
观察:menu item、目的地 ID、动作日志、列表快照和导航断言中的“14. The App Bar”轨迹
预期:由MenuHost、当前 Fragment 与列表状态提交菜单可见性、动作启用、空列表和导航结果,并持续满足“菜单由当前状态派生,不能对已销毁目的地继续执行动作”
实验二:事件与生命周期轨迹
沿五次转换逐步执行“创建菜单、选择动作、数据变空与配置重建”。每一步只允许MenuHost、当前 Fragment 与列表状态按职责提交状态,并持续核对“菜单由当前状态派生,不能对已销毁目的地继续执行动作”。
Deterministic event replay
AppBar与菜单:事件轨迹
不变量:菜单由当前状态派生,不能对已销毁目的地继续执行动作
交付证据:menu item、目的地 ID、动作日志、列表快照和导航断言
实验三:章专属反例与同输入恢复
注入“返回列表后旧详情 Fragment 仍处理删除菜单,删错 Crime”,保存第一个偏离点;撤销后以完全相同的 SDK、设备状态和用户事件重放。只有menu item、目的地 ID、动作日志、列表快照和导航断言一起恢复才算修复。
Fault · cancel · restore
AppBar与菜单:反例与恢复
故障:返回列表后旧详情 Fragment 仍处理删除菜单,删错 Crime
第 1 次使用相同 SDK、设备配置、初始状态与用户事件
保持正常输入不变,仅注入“返回列表后旧详情 Fragment 仍处理删除菜单,删错 Crime”
菜单由当前状态派生,不能对已销毁目的地继续执行动作
menu item、目的地 ID、动作日志、列表快照和导航断言