Android 自定义弹窗框架

前言

在项目开发中,我们经常需要创建各种各样的自定义弹窗,为了简约代码和统一管理,我初步搭建了这么一个简单的框架,待日后在新项目中使用后再不断维护。

------ 更新 2019/11/27 ------

  • 增加单选弹窗
  • 修复魅族手机出现编辑框清空崩溃的问题
  • 增加弹窗初始化布局宽高方向代码

样式图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这只是初始的想法搭建的框架,本文只说明思维方式,并不提供其他类型弹窗代码,有需要的朋友根据以下代码,进行自定义开发

另外本文也是提供一种在每个弹窗上都带有进度条的代码方法,供大家参考

写一个Base类 BaseDialog

/**
 * 基础弹窗
 *
 * @author D10NG
 * @date on 2019-10-25 14:49
 */
open class BaseDialog<T> constructor(
    private val context: Context
) {
    /** 弹窗创建器 */
    protected val builder = AlertDialog.Builder(context)
    /** 最终的弹窗实例 */
    protected var alert: AlertDialog? = null

    /** 基础弹窗布局 */
    val binding: DialogDefLoadingViewBinding = DataBindingUtil.inflate(
        LayoutInflater.from(context),
        R.layout.dialog_def_loading_view, null, false)

    /**
     * 创建
     */
    open fun create() : T {
        binding.loadIndeterminate = true
        binding.loadVisible = false
        builder.setView(binding.root)
        builder.setCancelable(false)
        alert = builder.create()
        return this as T
    }

    /**
     * 移除所有button
     */
    open fun removeAllButtons() {
        binding.buttonLayout.removeAllViews()
    }

    /**
     * 移除content内容
     */
    open fun removeContent() {
        binding.contentLayout.removeAllViews()
    }

    /**
     * 设置标题
     */
    open fun setTittle(tittle: String) : T {
        binding.tittle = tittle
        return this as T
    }

    /**
     * 设置二级文本
     */
    open fun setMsg(msg: String) : T {
        binding.message = msg
        return this as T
    }

    /**
     * 设置图标
     */
    open fun setIcon(resId: Int) : T {
        binding.image.setImageResource(resId)
        return this as T
    }

    /**
     * 设置图标
     */
    open fun setIcon(bitmap: Bitmap) : T {
        binding.image.setImageBitmap(bitmap)
        return this as T
    }

    /**
     * 开始加载中
     */
    open fun startLoad(indeterminate: Boolean, progress: Int, max: Int) {
        binding.loadIndeterminate = indeterminate
        binding.loadProgress = progress
        binding.loadMax = max
        binding.loadVisible = true
    }

    /**
     * 停止加载中
     */
    open fun stopLoad() {
        binding.loadVisible = false
    }

    /**
     * 添加button事件
     */
    open fun addAction(text: String, style: Int, onBtnClick: OnBtnClick?) : T {
        binding.buttonLayout.addView(createButton(text, style, onBtnClick))
        return this as T
    }

    /**
     * 显示
     */
    open fun show() {
        alert?.show()
    }

    /**
     * 关闭
     */
    open fun dismiss() {
        alert?.dismiss()
    }

    protected fun createButton(text: String, style: Int, onBtnClick: OnBtnClick?) : Button {
        val button = Button(context)
        button.background = ContextCompat.getDrawable(context, R.drawable.button_top_line_bg)
        button.text = text
        when(style) {
            ButtonStyle.THEME -> button.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary))
            ButtonStyle.NORMAL -> button.setTextColor(ContextCompat.getColor(context, R.color.text_hint_color))
            ButtonStyle.ERROR -> button.setTextColor(ContextCompat.getColor(context, R.color.text_wrong_color))
        }
        button.setOnClickListener {
            if (null == onBtnClick) {
                dismiss()
            } else {
                onBtnClick.click(this, text)
            }
        }
        return button
    }
}

建立基本弹窗布局 dialog_def_loading_view.xml

此处用了 DataBinding 使用方法参考我的另一篇文章 :
Android Kotlin学习 Jitpack 组件之DataBinding

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="android.view.View"/>
        <variable
            name="loadIndeterminate"
            type="boolean" />
        <variable
            name="loadProgress"
            type="int" />
        <variable
            name="loadMax"
            type="int" />
        <variable
            name="loadVisible"
            type="boolean" />
        <variable
            name="tittle"
            type="String" />
        <variable
            name="message"
            type="String" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white">

        <TextView
            android:id="@+id/txt_tittle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:gravity="center"
            android:singleLine="true"
            android:text="@{tittle}"
            android:textColor="#ff333333"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:adjustViewBounds="true"
            android:scaleType="centerInside"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/txt_tittle" />

        <TextView
            android:id="@+id/txt_message"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:ellipsize="end"
            android:lineSpacingExtra="6sp"
            android:maxLines="10"
            android:text="@{message}"
            android:textAlignment="center"
            android:textColor="#ff666666"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/image" />

        <LinearLayout
            android:id="@+id/content_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/txt_message">

        </LinearLayout>

        <ProgressBar
            android:id="@+id/pb_load"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:indeterminate="@{loadIndeterminate}"
            android:max="@{loadMax}"
            android:progress="@{loadProgress}"
            android:visibility="@{loadVisible? View.VISIBLE : View.INVISIBLE}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/content_layout" />

        <LinearLayout
            android:id="@+id/button_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/pb_load">

        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

写一个按钮样式选择 ButtonStyle

object ButtonStyle {
    const val THEME = 1
    const val ERROR = 2
    const val NORMAL = 3
}

写一个按钮点击监听 OnBtnClick

interface OnBtnClick {
    fun click(d0: BaseDialog<*>, text: String)
}

自定义弹窗 ButtonDialog

class ButtonDialog constructor(
    context: Context
) : BaseDialog<ButtonDialog>(context)

自定义弹窗 EditDialog

布局

dialog_edit_view.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/ti_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:errorEnabled="true">

            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/edt_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="hint" />
        </com.google.android.material.textfield.TextInputLayout>
    </LinearLayout>
</layout>

代码

/**
 * 带编辑框的弹窗
 *
 * @author D10NG
 * @date on 2019-11-23 10:05
 */
class EditDialog constructor(
    private val context: Context
) : BaseDialog<EditDialog>(context) {

    init {
        // 改变宽度
        val params = binding.contentLayout.layoutParams
        params.width = LinearLayout.LayoutParams.MATCH_PARENT
        binding.contentLayout.layoutParams = params
    }

    /** 编辑框列表 */
    private val edtMap: MutableMap<String, DialogEditViewBinding> = mutableMapOf()

    /**
     * 添加一个编辑框
     * @param tag 标签
     * @param text 初始文本
     * @param hint 提示文本
     */
    fun addEdit(tag: String, text: String, hint: String) : EditDialog {
        val viewBinding: DialogEditViewBinding = DataBindingUtil.inflate(
            LayoutInflater.from(context),
            R.layout.dialog_edit_view, null, false
        )
        viewBinding.edtText.setText(text)
        viewBinding.tiLayout.hint = hint
        binding.contentLayout.addView(viewBinding.root)
        edtMap[tag] = viewBinding
        return this
    }

    /**
     * 获取输入文本
     * @param tag 标签
     */
    fun getInputText(tag: String) : String {
        return edtMap[tag]?.edtText?.text.toString().trim()
    }

    /**
     * 显示错误信息
     * @param tag 标签
     * @param value 信息
     */
    fun setError(tag: String, value: String) : EditDialog {
        edtMap[tag]?.tiLayout?.error = value
        return this
    }
}

自定义弹窗 SingleChooseDialog

布局

dialog_recycle_view.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="startText"
            type="String" />
        <variable
            name="endText"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/txt_start"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@{startText}" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rcv"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1">

        </androidx.recyclerview.widget.RecyclerView>

        <TextView
            android:id="@+id/txt_end"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@{endText}" />

    </LinearLayout>
</layout>

dialog_normal_item_view.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="itemText"
            type="String" />
        <variable
            name="isSelected"
            type="boolean" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="35dp"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:ellipsize="middle"
            android:gravity="center"
            android:minWidth="100dp"
            android:text="@{itemText}"
            android:textColor="@{isSelected? @color/colorPrimary : @color/text_hint_color}"
            android:textSize="@{isSelected? 80 : 50}" />
    </LinearLayout>
</layout>

代码

/**
 * 单选弹窗
 *
 * @author D10NG
 * @date on 2019-11-25 16:36
 */
class SingleChooseDialog constructor(
    private val context: Context
) : BaseDialog<SingleChooseDialog>(context) {

    init {
        // 改变内容排列方向
        binding.contentLayout.orientation = LinearLayout.HORIZONTAL
        // 改变固定高度
        val params = binding.contentLayout.layoutParams
        params.height = 600
        binding.contentLayout.layoutParams = params
    }

    /** 选择项列表 */
    private val recyclerMap: MutableMap<String, DialogRecycleViewBinding> = mutableMapOf()

    /**
     * 添加选择列表
     * @param tag 标签
     * @param selectItem 选择项
     * @param list 全部选项
     * @param start 开始文本
     * @param end 结束文本
     */
    fun addSelectionList(tag: String, selectItem: String, list: List<String>, start: String, end: String) : SingleChooseDialog {
        val viewBinding: DialogRecycleViewBinding = DataBindingUtil.inflate(
            LayoutInflater.from(context),
            R.layout.dialog_recycle_view, null, false
        )
        viewBinding.startText = start
        viewBinding.endText = end
        viewBinding.rcv.layoutManager = LinearLayoutManager(context)
        val adapter = NormalAdapter(selectItem, list)
        viewBinding.rcv.adapter = adapter
        viewBinding.rcv.post {
            viewBinding.rcv.smoothScrollToPosition(list.indexOf(selectItem) + 3)
        }
        binding.contentLayout.addView(viewBinding.root)
        recyclerMap[tag] = viewBinding
        return this
    }

    /**
     * 获取选中项文本内容
     */
    fun getSelectOnTag(tag: String) : String {
        val viewBinding = recyclerMap[tag]?: return ""
        val adapter = viewBinding.rcv.adapter as NormalAdapter
        return adapter.selectStr
    }
}

使用方法

显示普通按键弹窗
在这里插入图片描述

    fun test(v: View) {
        val buttonDialog = ButtonDialog(this)
            .setTittle("标题")
            .setMsg("文本,段落,清晰,强大觉得还u会丢啊就是不对劲啊混合双打u看见我还大手大脚卡号!「大三大四的」")
            .setIcon(R.mipmap.icon_test)
            .addAction("确定", ButtonStyle.THEME, null)
            .addAction("取消", ButtonStyle.NORMAL, null)
            .addAction("乱来", ButtonStyle.ERROR,
                object : OnBtnClick{
                    override fun click(d0: BaseDialog<*>, text: String) {
                        // 进度条
                        d0.startLoad(true, 50, 100)
                    }
                })
            .create()
        buttonDialog.show()
        
        // 取消进度条
        buttonDialog.stopLoad()
    }

显示输入框弹窗
在这里插入图片描述

    fun test(v: View) {
        val editDialog = EditDialog(this)
            .setTittle("标题")
            .setMsg("设置文本")
            .addEdit("tag1", "12345678901234567890", "请输入密码")
            .addAction("确定", ButtonStyle.THEME,
                object : OnBtnClick{
                    override fun click(d0: BaseDialog<*>, text: String) {
                        val dialog = d0 as EditDialog
                        if (dialog.getInputText("tag1") != "666666") {
                            dialog.setError("tag1", "密码错误")
                        } else {
                            dialog.dismiss()
                        }
                    }
                })
            .create()
        editDialog.show()
    }

显示单选弹窗
在这里插入图片描述

                val list = mutableListOf<String>()
                for (i in 0 .. 100) {
                    list.add("$i")
                }
                SingleChooseDialog(this)
                    .setTittle("提示")
                    .setMsg("设定温度")
                    .addSelectionList("temp", "50", list,
                        "", "℃")
                    .addAction(resources.getString(R.string.sure), ButtonStyle.THEME, object : OnBtnClick{
                        override fun click(d0: BaseDialog<*>, text: String) {
                            val d = d0 as SingleChooseDialog
                            // 拿到温度
                            val temp = d.getSelectOnTag("temp").toInt()
                            viewModel.update(temp)
                            d.dismiss()
                        }
                    })
                    .addAction(resources.getString(R.string.cancel), ButtonStyle.NORMAL, null)
                    .create()
                    .show()

完事

发布了103 篇原创文章 · 获赞 31 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/sinat_38184748/article/details/103205741