底部弹出菜单是什么

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

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

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

思路分析

我们先分析一下,这样一种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()
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.

类的结构不仅可以继承,还可以使用聚合和组合的方式,我们这里就不直接继承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)
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

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

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

来区分有圆角和没圆角的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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

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

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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

最后给你们证明一下我是做了语言国际化的。

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

使用方式
// 打开底部弹窗
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("外部浏览器打开"))
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
开源项目

github.com/dora4/dview…