Android 入门教程
菜单与对话框
本教程共 100 篇 · 第 63 篇 · 更新于 2026-07-28 · 约 7 分钟阅读
AndroidAndroid 入门教程OptionsMenuAlertDialogPopupMenu对话框菜单
63. 菜单与对话框
本节目标:掌握传统 View 的三种菜单(选项菜单、上下文菜单、弹出菜单)和 AlertDialog 对话框的用法,能做自定义对话框。
三种菜单
Android 传统菜单有三种形式:
| 菜单类型 | 触发方式 | 场景 |
|---|---|---|
| OptionsMenu(选项菜单) | 按菜单键 / 工具栏按钮 | 全局操作(搜索、设置) |
| ContextMenu(上下文菜单) | 长按元素 | 对特定项操作(删除、编辑) |
| PopupMenu(弹出菜单) | 点击触发 | 绑定到某个按钮的临时菜单 |
OptionsMenu 选项菜单
创建菜单资源
res/menu/main_menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_search"
android:title="搜索"
android:icon="@drawable/ic_search"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:title="设置"
app:showAsAction="never" />
</menu>
showAsAction 显示方式
| 值 | 效果 |
|---|---|
always | 总是显示在工具栏 |
ifRoom | 有空间就显示,没空间收进溢出菜单 |
never | 永远收在溢出菜单里(三点菜单) |
withText | 显示文字+图标 |
在 Activity 中使用
class MainActivity : AppCompatActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_search -> {
// 搜索操作
true
}
R.id.action_settings -> {
// 设置操作
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
Note
onCreateOptionsMenu创建菜单,onOptionsItemSelected处理点击。返回true表示已处理,返回false会继续传给父类。在 Toolbar 时代,OptionsMenu 通常显示为工具栏右侧的图标和溢出菜单。
ContextMenu 上下文菜单
长按某个 View 弹出的菜单:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 注册长按菜单
registerForContextMenu(binding.listView)
}
override fun onCreateContextMenu(
menu: ContextMenu,
v: View,
menuInfo: ContextMenu.ContextMenuInfo?
) {
super.onCreateContextMenu(menu, v, menuInfo)
menuInflater.inflate(R.menu.context_menu, menu)
menu.setHeaderTitle("操作")
}
override fun onContextItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_edit -> {
// 编辑
true
}
R.id.action_delete -> {
// 删除
true
}
else -> super.onContextItemSelected(item)
}
}
}
Tip
registerForContextMenu给 View 注册长按菜单。长按该 View 就会弹出菜单。适合列表项的操作菜单(编辑、删除、分享)。
PopupMenu 弹出菜单
绑定到某个按钮的点击事件,在按钮下方弹出菜单:
binding.moreButton.setOnClickListener { view ->
val popup = PopupMenu(this, view)
popup.menuInflater.inflate(R.menu.popup_menu, popup.menu)
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_share -> {
// 分享
true
}
R.id.action_report -> {
// 举报
true
}
else -> false
}
}
popup.show()
}
Note
- PopupMenu 比 ContextMenu 更主动:ContextMenu 要长按才出来,PopupMenu 是你代码里调
show()就出来。适合做「更多」按钮的下拉菜单。
AlertDialog 对话框
AlertDialog.Builder(this)
.setTitle("确认删除")
.setMessage("删除后无法恢复,确定要删除吗?")
.setPositiveButton("删除") { dialog, _ ->
// 确认删除
}
.setNegativeButton("取消") { dialog, _ ->
dialog.dismiss()
}
.setCancelable(true) // 点外面能否取消
.show()
列表选择对话框
val options = arrayOf("红色", "绿色", "蓝色")
AlertDialog.Builder(this)
.setTitle("选择颜色")
.setItems(options) { dialog, which ->
val selected = options[which]
Toast.makeText(this, "选了 $selected", Toast.LENGTH_SHORT).show()
}
.show()
单选对话框
var checkedItem = 0
AlertDialog.Builder(this)
.setTitle("选择排序方式")
.setSingleChoiceItems(options, checkedItem) { dialog, which ->
checkedItem = which
}
.setPositiveButton("确定") { dialog, _ ->
// 用 checkedItem
}
.show()
多选对话框
val checkedItems = booleanArrayOf(true, false, true)
AlertDialog.Builder(this)
.setTitle("选择标签")
.setMultiChoiceItems(options, checkedItems) { dialog, which, isChecked ->
checkedItems[which] = isChecked
}
.setPositiveButton("确定") { dialog, _ ->
// 用 checkedItems
}
.show()
自定义对话框
方式一:AlertDialog + 自定义布局
val dialogBinding = DialogLoginBinding.inflate(layoutInflater)
AlertDialog.Builder(this)
.setView(dialogBinding.root)
.setPositiveButton("登录") { dialog, _ ->
val username = dialogBinding.etUsername.text.toString()
val password = dialogBinding.etPassword.text.toString()
// 处理登录
}
.setNegativeButton("取消", null)
.show()
方式二:DialogFragment(推荐)
class LoginDialog : DialogFragment() {
private var _binding: DialogLoginBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogLoginBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
// 通过 Fragment Result 传递结果
parentFragmentManager.setFragmentResult(
"login",
bundleOf("username" to username)
)
dismiss()
}
}
}
// 显示
LoginDialog().show(supportFragmentManager, "login")
Warning
- 推荐用
DialogFragment而不是直接用AlertDialog。因为 DialogFragment 能正确处理生命周期和配置变更(旋转屏幕不会丢失对话框状态),而裸 AlertDialog 旋转屏幕会消失。
DatePickerDialog 和 TimePickerDialog
// 日期选择
DatePickerDialog(this, { _, year, month, day ->
val date = "$year-${month + 1}-$day" // month 从 0 开始
binding.tvDate.text = date
}, 2026, 6, 28).show() // 默认显示日期
// 时间选择
TimePickerDialog(this, { _, hour, minute ->
val time = "%02d:%02d".format(hour, minute)
binding.tvTime.text = time
}, 12, 0, true).show() // true = 24 小时制
Tip
month参数从 0 开始(0 = 一月),显示时要 +1。这是个经典坑。
小结
OptionsMenu 做工具栏菜单,ContextMenu 做长按菜单,PopupMenu 做点击弹出菜单。AlertDialog 做各种确认/选择对话框,自定义对话框推荐用 DialogFragment(生命周期安全)。DatePickerDialog 注意 month 从 0 开始。在 Compose 项目里,对话框用 AlertDialog 可组合函数或 ModalBottomSheet 更简洁。下一部分进入数据存储的学习。