注册

底部弹出菜单原来这么简单

底部弹出菜单是什么


底部弹出菜单,即从app界面底部弹出的一个菜单列表,这种UI形式被众多app所采用,是一种主流的布局方式。


截屏2023-09-28 14.36.51.png


截屏2023-09-28 14.37.29.png


思路分析


我们先分析一下,这样一种UI应该由哪些布局组成?首先在原界面上以一小块区域显示界面的这种形式,很明显就是对话框Dialog做的事情吧!最底部是一个取消菜单,上面的功能菜单可以是一个,也可以是两个、三个甚至更多。所以,我们可以使用RecyclerView实现。需要注意一点的是,最上面那个菜单的样式稍微有点不一样,因为它上面是圆滑的,有圆角,这样的界面显示更加和谐。我们主要考虑的就是弹出对话框的动画样式,另外注意一点就是可以多支持几个语种,让框架更加专业,这里只需要翻译“取消”文字。


开始看代码


package dora.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 dora.widget.bean.BottomMenu
import dora.widget.bottomdialog.R

class DoraBottomMenuDialog : View.OnClickListener, OnItemChildClickListener {

private var bottomDialog: Dialog? = null
private var listener: OnMenuClickListener? = null

interface OnMenuClickListener {
fun onMenuClick(position: Int, menu: String)
}

fun setOnMenuClickListener(listener: OnMenuClickListener) : DoraBottomMenuDialog {
this.listener = listener
return this
}

fun show(activity: Activity, menus: Array<String>): DoraBottomMenuDialog {
if (bottomDialog == null && !activity.isFinishing) {
bottomDialog = Dialog(activity, R.style.DoraView_AlertDialog)
val contentView =
LayoutInflater.from(activity).inflate(R.layout.dview_dialog_content, null)
initView(contentView, menus)
bottomDialog!!.setContentView(contentView)
bottomDialog!!.setCanceledOnTouchOutside(true)
bottomDialog!!.setCancelable(true)
bottomDialog!!.window!!.setGravity(Gravity.BOTTOM)
bottomDialog!!.window!!.setWindowAnimations(R.style.DoraView_BottomDialog_Animation)
bottomDialog!!.show()
val window = bottomDialog!!.window
window!!.decorView.setPadding(0, 0, 0, 0)
val lp = window.attributes
lp.width = WindowManager.LayoutParams.MATCH_PARENT
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
window.attributes = lp
} else {
bottomDialog!!.show()
}
return this
}

private fun initView(contentView: View, menus: Array<String>) {
val recyclerView = contentView.findViewById<RecyclerView>(R.id.dview_recycler_view)
val adapter = MenuAdapter()
val list = mutableListOf<BottomMenu>()
menus.forEachIndexed { index, s ->
when (index) {
0 -> {
list.add(BottomMenu(s, BottomMenu.TOP_MENU))
}
else -> {
list.add(BottomMenu(s, BottomMenu.NORMAL_MENU))
}
}
}
adapter.setList(list)
recyclerView.adapter = adapter
val decoration = DividerItemDecoration(contentView.context, DividerItemDecoration.VERTICAL)
recyclerView.addItemDecoration(decoration)
adapter.addChildClickViewIds(R.id.tv_menu)
adapter.setOnItemChildClickListener(this)
val tvCancel = contentView.findViewById<TextView>(R.id.tv_cancel)
tvCancel.setOnClickListener(this)
}

private fun dismiss() {
bottomDialog?.dismiss()
bottomDialog = null
}

override fun onClick(v: View) {
when (v.id) {
R.id.tv_cancel -> dismiss()
}
}

override fun onItemChildClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
listener?.onMenuClick(position, adapter.getItem(position) as String)
dismiss()
}
}

类的结构不仅可以继承,还可以使用聚合和组合的方式,我们这里就不直接继承Dialog了,使用一种更接近代理的一种方式。条条大路通罗马,能抓到老鼠的就是好猫。这里的设计是通过调用show方法,传入一个菜单列表的数组来显示菜单,调用dismiss方法来关闭菜单。最后添加一个菜单点击的事件,把点击item的内容和位置暴露给调用方。


package dora.widget

import com.chad.library.adapter.base.BaseMultiItemQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import dora.widget.bean.BottomMenu
import dora.widget.bottomdialog.R

class MenuAdapter : BaseMultiItemQuickAdapter<BottomMenu, BaseViewHolder>() {

init {
addItemType(BottomMenu.NORMAL_MENU, R.layout.dview_item_menu)
addItemType(BottomMenu.TOP_MENU, R.layout.dview_item_menu_top)
}

override fun convert(holder: BaseViewHolder, item: BottomMenu) {
holder.setText(R.id.tv_menu, item.menu)
}
}

多类型的列表布局我们采用BRVAH,


implementation("io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.10")

来区分有圆角和没圆角的item条目。


<?xml version="1.0" encoding="utf-8"?>

<resources>
<style name="DoraView.AlertDialog" parent="@android:style/Theme.Dialog">
<!-- 是否启用标题栏 -->
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>

<!-- 是否使用背景半透明 -->
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
</style>

<style name="DoraView.BottomDialog.Animation" parent="Animation.AppCompat.Dialog">
<item name="android:windowEnterAnimation">@anim/translate_dialog_in</item>
<item name="android:windowExitAnimation">@anim/translate_dialog_out</item>
</style>
</resources>

以上是对话框的样式。我们再来看一下进入和退出对话框的动画。


translate_dialog_in.xml


<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:duration="300"
android:fromXDelta="0"
android:fromYDelta="100%"
android:toXDelta="0"
android:toYDelta="0">

</translate>

translate_dialog_out.xml


<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:duration="300"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="100%">

</translate>

最后给你们证明一下我是做了语言国际化的。
截屏2023-09-28 15.08.20.png


使用方式


// 打开底部弹窗
val dialog = DoraBottomMenuDialog()
dialog.setOnMenuClickListener(object : DoraBottomMenuDialog.OnMenuClickListener {
override fun onMenuClick(position: Int, menu: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
startActivity(intent)
}
})
dialog.show(this, arrayOf("外部浏览器打开"))

开源项目


github.com/dora4/dview…


作者:dora
来源:juejin.cn/post/7283516197487214611

0 个评论

要回复文章请先登录注册