首页 / Android 入门教程 / 菜单与对话框

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 更简洁。下一部分进入数据存储的学习。