• 简单封装一个易拓展的Dialog


    Dialog,每个项目中多多少少都会用到,肯定也会有自己的一套封装逻辑,无论如何封装,都是奔着简单复用的思想,有的是深层次的封装,也就是把相关的UI效果直接封装好,暴露可以修改的属性和方法,让调用者根据实际业务,调用修改即可,当然也有简单的封装,只封装基本的功能,其UI和实际的动作,交给调用者,两种封装方式,各有利弊,前者调用者不用自己创建UI和实现相关动作,只需要简单的调用即可,但是不易于扩展,效果比较局限,想要拓展其他的效果,就不得不自己动手实现;后者扩展性强,因为只提供基本的调用方式,也就是说,你想要什么效果都行,毕竟是所有的UI和动作都是你自己来实现,优点是它,其缺点也是它。

    前者的封装司空见惯,大多数的公司也都是采取的这样的封装,毕竟调用者实现起来也是很方便,这里就不详细说了,具体我们谈一下后者的封装,后者的封装虽然调用者需要自己来实现,但是扩展性是很强的。

    今天的内容大致如下:

    1、效果及代码具体调用。

    2、如何封装一个Dialog。

    3、开源地址。

    4、总结及注意事项。

    一、效果及代码具体调用

    通过Kotlin的扩展函数,参数以类做为扩展,封装之后,调用非常的便捷,只需要传递你要的视图即可,我们先看下具体的案例,代码如下:

    1. showVipDialog {
    2. addLayout(R.layout.layout_dialog_custom)//传递dialog视图
    3. set {
    4. //Dialog操作,获取View及绑定数据
    5. }
    6. }

    通过以上的代码,我们就实现了一个Dialog的弹出,addLayout方法传递视图,set扩展函数进行获取View和绑定数据,这样的一个简单的封装,我们就实现了Dialog的扩展操作,针对不同的Dialog样式,传递不同的xml视图即可。

    1、快速使用

    为了方便大家使用,目前已经上传到了远程maven,大家可以进行依赖使用,或者下载源码依赖也可以。

    根项目build.gradle

    1. allprojects {
    2. repositories {
    3. ……
    4. maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }
    5. }
    6. }

    在需要的Module下引入依赖

    1. dependencies {
    2. ……
    3. implementation "com.vip:dialog:1.0.0"
    4. }

    2、代码案例

    源码下载之后,运行项目,就可以看到给大家提供的相关Demo,当然了,由于做到了可扩展,大家想实现什么样的效果都是可以的,毕竟视图都是自己传递的。

    由于所有的案例都是调用开头的代码,就不一一列举了,简单的列举几个。

    普通的提示框

    普通的提示框,可以按照下面的代码逻辑进行调用。

    1. showVipDialog {
    2. addLayout(R.layout.layout_dialog_custom)//添加弹出的视图
    3. set {//逻辑处理,获取view,绑定数据
    4. setDialogCancelable(false)//点击空白不消失
    5. val btnConfirm = findView(R.id.dialog_button_confirm)//获取View
    6. btnConfirm.setOnClickListener {
    7. toast("确定")
    8. dismiss()
    9. }
    10. }
    11. }

    方法一览

    方法名

    参数类型

    概述

    addLayout

    int

    xml视图

    set

    无参

    逻辑处理

    style

    无参

    dialog设置样式

    setDialogCancelable

    Boolean

    点击空白是否消失,默认true消失,false为不消失

    findView

    int

    控件id,泛型为控件

    dismiss

    无参

    隐藏dialog

    getDialogView

    无参

    获取当前View视图

    DataBinding形式的提示框

    DataBinding形式和普通的区别在于,不用再获取View视图,由普通的set扩展函数改为bind扩展函数,泛型为Binding,记得把xml视图进行convert to data binding layout。

    1. showVipDialog {
    2. addLayout(R.layout.layout_dialog_custom)//添加弹出的视图
    3. bind {//逻辑处理,获取view,绑定数据
    4. it.dialogButtonConfirm.setOnClickListener {
    5. toast("确定")
    6. dismiss()
    7. }
    8. }
    9. }

    方法一览

    除了普通的方法调用之外,还可以调用下面的方法。

    方法名参数概述

    bind

    无参

    和set一样进行逻辑处理,泛型为ViewDataBinding

    getDataBinding

    无参

    获取当前的DataBinding,用于更新视图

    setPendingBindings

    int

    传递的BR,用于xml和Data数据进行绑定

    具体的案例大家直接可以看源码,源码中提供了很多常见的效果,都是可以自定义实现的,具体的就不罗列了,本身没有多少难度。

    确认框

    输入框

    底部列表

    菊花加载

    二、如何封装一个Dialog

    这样的一个简单的Dialog如何进行封装呢?在封装之前,我们首先要明确封装思路,1、视图由调用者传递,2、逻辑操作由调用者处理,3、样式也由调用者进行设置,也就是说,我们只封装基本的dialog使用,也就是一个壳,具体的内容,统统交给调用者进行处理,有了这三个思路我们就可以进行着手封装了。

    1、封装BaseDialog

    封装Base的原因,在于统一管理子类,在于简化子类的代码逻辑,便于提供公共的方法让子类实现或调用,BaseDialog这里继承的是DialogFragment,最大的原因就是,容易通过生命周期回调来管理弹窗,还有对于复杂样式的弹窗,使用DialogFragment会更加方便和高效。

    和之前封装Activity一样,做为一个抽象父类,子类要实现的无非就是,视图的传递和逻辑的处理,我们就可以在父类中进行定义抽象方法,Dialog一般有自己定义的样式,我们也可以定义一个初始化样式的方法。

    1. /**
    2. * AUTHOR:AbnerMing
    3. * INTRODUCE:初始化数据
    4. */
    5. abstract fun initData()
    6. /**
    7. * AUTHOR:AbnerMing
    8. * INTRODUCE:初始化样式
    9. */
    10. abstract fun initStyle()
    11. /**
    12. * AUTHOR:AbnerMing
    13. * INTRODUCE:传递的视图
    14. */
    15. abstract fun getLayoutId(): Int

    除了必要实现的方法之外,我们还可以把一些公用的方法,定义到Base里,如获取View的方法,获取控件的方法等,这么做的目的,便于子类自定义实现一些效果以及减少findViewById的调用次数。

    1. /**
    2. * AUTHOR:AbnerMing
    3. * INTRODUCE:获取View视图
    4. */
    5. fun findView(id: Int): View {
    6. var view = mViewSparseArray[id]
    7. if (view == null) {
    8. view = mView?.findViewById(id)
    9. mViewSparseArray.put(id, view)
    10. }
    11. return view
    12. }
    13. /**
    14. * AUTHOR:AbnerMing
    15. * INTRODUCE:获取当前View视图
    16. */
    17. fun getDialogView(): View {
    18. return mView!!
    19. }

    以上只是列举了几个实现的方法,完整的代码,大家可以看源码中的BaseDialog类。

    2、拓展ViewDataBinding形式Dialog

    正常的普通Dialog就可以继承BaseDialog,基本就可以满足需要的,若是要和ViewDataBinding进行结合,那么就需要拓展需求了,具体的拓展也很简单,一是绑定View,二是绑定数据,完整的代码,大家可以看源码中BaseBindingDialog类。

    绑定View

    通过DataBindingUtil的bind方法,得到ViewDataBinding。

    mBinding = DataBindingUtil.bind(getDialogView())

    绑定数据

    完成xml视图和数据的绑定。

    1. mBinding.setVariable(variableId, t)
    2. mBinding.executePendingBindings()

    3、封装工具类,拓展相关功能

    为了更加方便的让调用者使用,封装拓展函数是很有必要的,要不然,调用者每次都得要继承上边的两个父类,这样的代码就会增加很多,还会创建很多的类,我们需要单独的创建一个工具类,来实例化我们需要简化的功能逻辑。

    提供添加xml视图的方法

    很简单的一个普通方法,没什么好说的,把传递的xml,赋值给重写的getLayoutId方法即可。

    1. /**
    2. * AUTHOR:AbnerMing
    3. * INTRODUCE:设置layout
    4. * @param mLayoutId xml布局
    5. */
    6. fun addLayout(mLayoutId: Int): VipDialog {
    7. this.mLayoutId = mLayoutId
    8. return this
    9. }

    提供普通使用和DataBinding形式使用方法

    普通和DataBinding方法,这里用到了接口回调,接口的实现则在initVMData方法里,两个方法本身功能是一样的,无非就是一个是普通,一个是返回ViewDataBinding。

    1. /**
    2. * AUTHOR:AbnerMing
    3. * INTRODUCE:初始化数据
    4. */
    5. fun bind(block: (bind: VB) -> Unit): VipDialog {
    6. setDataCallBackListener(object : OnDialogDataCallbackListener {
    7. override fun dataCallback() {
    8. block.invoke(getDataBinding())
    9. }
    10. })
    11. return this
    12. }
    13. /**
    14. * AUTHOR:AbnerMing
    15. * INTRODUCE:初始化数据
    16. */
    17. fun set(block: () -> Unit): VipDialog {
    18. setDataCallBackListener(object : OnDialogDataCallbackListener {
    19. override fun dataCallback() {
    20. block.invoke()
    21. }
    22. })
    23. return this
    24. }

    提供设置样式的方法

    样式的设置也就是使用了接口回调。

    1. /**
    2. * AUTHOR:AbnerMing
    3. * INTRODUCE:设置样式
    4. */
    5. fun style(style: () -> Unit): VipDialog {
    6. setStyleCallBackListener(object : OnStyleCallBackListener {
    7. override fun styleCallback() {
    8. style.invoke()
    9. }
    10. })
    11. return this
    12. }

    提供获取ViewDataBinding的方法

    这个方法的提供是便于拿到ViewDataBinding,有效的更新视图数据。

    1. /**
    2. * AUTHOR:AbnerMing
    3. * INTRODUCE:获取ViewDataBinding
    4. */
    5. fun getDataBinding(): VB {
    6. return mBinding as VB
    7. }

    我们看下整体的代码,如下:

    1. /**
    2. *AUTHOR:AbnerMing
    3. *DATE:2022/11/22
    4. *INTRODUCE:实例化功能
    5. */
    6. class VipDialog : BaseBindingDialog<ViewDataBinding>() {
    7. companion object {
    8. fun init(): VipDialog {
    9. return VipDialog()
    10. }
    11. }
    12. private var mLayoutId = 0
    13. override fun initVMData() {
    14. mOnDialogDataCallbackListener?.dataCallback()
    15. }
    16. override fun initStyle() {
    17. mOnStyleCallBackListener?.styleCallback()
    18. }
    19. override fun getLayoutId(): Int {
    20. return mLayoutId
    21. }
    22. /**
    23. * AUTHOR:AbnerMing
    24. * INTRODUCE:获取ViewDataBinding
    25. */
    26. fun getDataBinding(): VB {
    27. return mBinding as VB
    28. }
    29. /**
    30. * AUTHOR:AbnerMing
    31. * INTRODUCE:设置layout
    32. * @param mLayoutId xml布局
    33. */
    34. fun addLayout(mLayoutId: Int): VipDialog {
    35. this.mLayoutId = mLayoutId
    36. return this
    37. }
    38. /**
    39. * AUTHOR:AbnerMing
    40. * INTRODUCE:初始化数据
    41. */
    42. fun bind(block: (bind: VB) -> Unit): VipDialog {
    43. setDataCallBackListener(object : OnDialogDataCallbackListener {
    44. override fun dataCallback() {
    45. block.invoke(getDataBinding())
    46. }
    47. })
    48. return this
    49. }
    50. /**
    51. * AUTHOR:AbnerMing
    52. * INTRODUCE:初始化数据
    53. */
    54. fun set(block: () -> Unit): VipDialog {
    55. setDataCallBackListener(object : OnDialogDataCallbackListener {
    56. override fun dataCallback() {
    57. block.invoke()
    58. }
    59. })
    60. return this
    61. }
    62. /**
    63. * AUTHOR:AbnerMing
    64. * INTRODUCE:设置样式
    65. */
    66. fun style(style: () -> Unit): VipDialog {
    67. setStyleCallBackListener(object : OnStyleCallBackListener {
    68. override fun styleCallback() {
    69. style.invoke()
    70. }
    71. })
    72. return this
    73. }
    74. private var mOnDialogDataCallbackListener: OnDialogDataCallbackListener? = null
    75. private fun setDataCallBackListener(mOnDialogDataCallbackListener: OnDialogDataCallbackListener) {
    76. this.mOnDialogDataCallbackListener = mOnDialogDataCallbackListener
    77. }
    78. private var mOnStyleCallBackListener: OnStyleCallBackListener? = null
    79. private fun setStyleCallBackListener(mOnStyleCallBackListener: OnStyleCallBackListener) {
    80. this.mOnStyleCallBackListener = mOnStyleCallBackListener
    81. }
    82. }

    4、封装拓展函数,简化调用

    dialog的弹出可能有很多场景,比如Activity里,比如Fragment里,比如一个工具类中,我们可以根据已知的场景,来定义我们的调用方式,目前,我定义了两种,在Activity或者Fragment里可以直接进行调用,也就是开头的调用方式,当然了,大家也可以自己拓展。

    1. /**
    2. * AUTHOR:AbnerMing
    3. * INTRODUCE:Activity显示Dialog
    4. */
    5. fun AppCompatActivity.showVipDialog(vipDialog: VipDialog.() -> Unit): VipDialog {
    6. val dialog = VipDialog.init()
    7. dialog.apply(vipDialog)
    8. setActivityDialog(this.supportFragmentManager, dialog)
    9. return dialog
    10. }
    11. /**
    12. * AUTHOR:AbnerMing
    13. * INTRODUCE:Fragment显示Dialog
    14. */
    15. fun Fragment.showVipDialog(vipDialog: VipDialog.() -> Unit): VipDialog {
    16. val dialog = VipDialog.init()
    17. dialog.apply(vipDialog)
    18. setActivityDialog(this.childFragmentManager, dialog)
    19. return dialog
    20. }

    通过以上几步,我们就可以实现开头的简单调用,具体的大家可以查看相关源码。

    三、开源地址

    项目地址:https://github.com/AbnerMing888/VipDialog

    四、总结及注意事项

    在开头已经阐述,这种方式易于拓展,但是代码量相对比较多,毕竟所有的UI和逻辑都必须独自来处理,在项目中的解决方式为,如果很多的弹框效果一样,建议再封装一层,抽取公共的工具类。

    还有一个需要注意的,本身扩展函数showVipDialog返回的就是调用的类,也就是一个Dialog,大家可以直接获取变量,在其他的地方做更新Dialog或者销毁的操作。

    1. val dialog=showVipDialog {
    2. ……
    3. }

  • 相关阅读:
    77. 组合
    继承(二) —— 基类和派生类对象的赋值转换
    2023年系统规划与设计管理师-第二章信息技术知识
    小红书信息流广告投放怎么收费?投信息流广告效果怎么样
    自定义mvc增删改查
    北京互联网公司、外企、国企大盘点
    【面试总结大纲】
    移动平台 GPU 架构扫盲
    Python期末复习题:流程控制
    ERP系统中有采购管理模块,为什么企业要选择供应商管理SRM系统?
  • 原文地址:https://blog.csdn.net/ming_147/article/details/128126321