底部弹出菜单实现详解
概述
底部弹出菜单(Bottom Menu)是移动端应用中非常常见的一种交互组件,通常用于展示一组操作选项,如分享、删除、编辑等。相比于系统原生的 AlertDialog 或 BottomSheetDialog,自定义的底部弹出菜单可以提供更灵活的布局控制、动画效果以及多类型 Item 的支持。
本文介绍了一种基于 Android Dialog 和 RecyclerView 实现的底部弹出菜单方案。该方案支持动态配置菜单项、顶部特殊样式处理、国际化取消按钮以及平滑的进出场动画。
核心思路分析
要实现一个美观且功能完善的底部弹出菜单,我们需要考虑以下几个关键点:
- 容器选择:使用
Dialog作为外层容器,配合Gravity.BOTTOM设置对齐方式,确保弹窗从底部升起。 - 列表实现:内部使用
RecyclerView承载菜单项,利用其复用机制保证性能,同时支持无限数量的菜单项。 - 样式区分:顶部的第一个菜单项通常需要特殊的圆角背景,而后续项则保持普通样式。这可以通过
BaseMultiItemQuickAdapter来实现不同 ItemViewType 的渲染。 - 动画效果:通过自定义 WindowAnimationStyle 实现从下往上滑入和滑出的过渡效果,提升用户体验。
- 交互逻辑:点击菜单项后触发回调并自动关闭弹窗,点击取消按钮或外部区域也需正确关闭。
关键类实现
主对话框类
主类 DoraBottomMenuDialog 继承自 View.OnClickListener 和 OnItemChildClickListener,负责管理 Dialog 的生命周期和事件分发。
package com.example.bottommenu.widget
import android.app.Activity
import android.app.Dialog
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.listener.OnItemChildClickListener
import com.example.bottommenu.widget.bean.BottomMenu
import com.example.bottommenu.widget.R
class DoraBottomMenuDialog : View.OnClickListener, OnItemChildClickListener {
private bottomDialog: Dialog? =
listener: OnMenuClickListener? =
{
}
: DoraBottomMenuDialog {
.listener = listener
}
: DoraBottomMenuDialog {
(bottomDialog == && !activity.isFinishing) {
bottomDialog = Dialog(activity, R.style.DoraView_AlertDialog)
contentView = LayoutInflater.from(activity).inflate(R.layout.dview_dialog_content, )
initView(contentView, menus)
bottomDialog!!.setContentView(contentView)
bottomDialog!!.setCanceledOnTouchOutside()
bottomDialog!!.setCancelable()
bottomDialog!!.window?.setGravity(Gravity.BOTTOM)
bottomDialog!!.window?.setWindowAnimations(R.style.DoraView_BottomDialog_Animation)
bottomDialog!!.show()
window = bottomDialog!!.window
window?.decorView.setPadding(, , , )
lp = window.attributes
lp.width = WindowManager.LayoutParams.MATCH_PARENT
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
window.attributes = lp
} {
bottomDialog?.show()
}
}
{
recyclerView = contentView.findViewById<RecyclerView>(R.id.dview_recycler_view)
adapter = MenuAdapter()
list = mutableListOf<BottomMenu>()
menus.forEachIndexed { index, s ->
(index) {
-> {
list.add(BottomMenu(s, BottomMenu.TOP_MENU))
}
-> {
list.add(BottomMenu(s, BottomMenu.NORMAL_MENU))
}
}
}
adapter.setList(list)
recyclerView.adapter = adapter
decoration = DividerItemDecoration(contentView.context, DividerItemDecoration.VERTICAL)
recyclerView.addItemDecoration(decoration)
adapter.addChildClickViewIds(R.id.tv_menu)
adapter.setOnItemChildClickListener()
tvCancel = contentView.findViewById<TextView>(R.id.tv_cancel)
tvCancel.setOnClickListener()
}
{
bottomDialog?.dismiss()
bottomDialog =
}
{
(v.id) {
R.id.tv_cancel -> dismiss()
}
}
{
listener?.onMenuClick(position, adapter.getItem(position)?.menu ?: )
dismiss()
}
}


