Android Dialog 层级处理方案之ViewDialog

问题梳理

在之前公司做直播项目的时候,在首页会弹出很多dialog,当时有2种方案。 一种是一次只弹出一个,每次回到首页再次弹出一个。 第二种层叠堆放。 因为众所周知原因,政策弹窗永远要在最上层,其次是升级弹窗等。 第一种我们暂不讨论,各家有各家解决方案。 第二种是大家常用方案,一般解决方案就是我延迟几秒弹出,则最上层的必然是XX弹窗。但是当涉及网络接口的时候,不可抗拒力就变大了,毕竟存在网络延迟和重试问题。 我们针对第二种情况封装了基于View的dialog,在内部进行层级处理,基于FrameLayout的堆叠特性。实现了ViewDialog。经过线上的考验。 使用方式简单粗暴。

解决方案

依赖开源库

arduino 复制代码
 implementation 'io.github.nuonuoOkami:ViewDialog:1.0.0'

使用方法

无需业务处理
kotlin 复制代码
//直接传入布局
    ViewDialog(MainActivity@ this, layout = R.layout.activity_main).show()
    // 支持传入View
    ViewDialog(
        MainActivity@ this,
        rootView = LayoutDialogBinding.inflate(layoutInflater).root
    ).show()
内部进行业务处理 继承ViewDialog
kotlin 复制代码
 //自定义Demo
 class DemoDialog2(content: Activity) :
ViewDialog(content, rootView = LayoutDialogBinding.inflate(content.layoutInflater).root) {


//控件处理
override fun bindUI(rootView: View) {
    super.bindUI(rootView)
}

//level 越大 显示的时候就在上面
override fun level() = 3

//默认warp
override fun params(): LayoutParams {
    val layoutParams = LayoutParams(
        LayoutParams.WRAP_CONTENT,
        LayoutParams.WRAP_CONTENT
    )
    layoutParams.gravity = gravity()
    return layoutParams
}

//同类型只能存在一个 避免多次弹窗
override fun single()=true
//是否弹窗变黑
override fun isDark()=true
//支持响应事件
override  fun   canceledAble()=true

//是否可以点击外部取消
override  fun canceledOnTouchOutside() = true
//业务处理
    override fun business() {
        super.business()
    }

//直接调用show()和dismiss()就行,无需其他操作
     val dialog2=   DemoDialog2(MainActivity@this)
        dialog2 .show()
        dialog2.dismiss()

原理解析

找到当前Activity的rootView,添加进一个容器 XViewDialogContainer,设置tag。 当ViewDialog 调用show()的时候,会检查当前页面有没有对应容器,没有就进行容器添加,有则直接调用容器将自己添加进去。 在 容器内部,根据所谓的level 值 进行添加,其实这里就是z值,容器的Z值被设置的特别高,为65535,一般没有正常View会超过这个值。 在内部添加ViewDialog的时候,会对传入的ViewDialog 进行排序。如果需要响应返回事件和点击外部消失,也做了对应的处理。 因为实现了DialogInterface ,所以使用方法和普通dialog一致。 现将核心代码奉上,大家可以参考,欢迎提出建议!

源码

kotlin 复制代码
open class ViewDialog(context: Context, rootView: View? = null, layout: Int? = null) : IViewDialog,ViewDialogBusiness,
    FrameLayout(context) {
    override fun cancel() {
        dismiss()
    }

    override fun dismiss() {
        if (parent != null) {
            (parent as ViewGroup).removeView(this)
        }
    }
    open fun level(): Int = 0
    open fun params(): LayoutParams {
        val layoutParams = LayoutParams(
            LayoutParams.WRAP_CONTENT,
            LayoutParams.WRAP_CONTENT
        )
        layoutParams.gravity = gravity()
        return layoutParams
    }

    open fun gravity() = Gravity.CENTER
    init {
        if (rootView == null && layout == null) {
            throw RuntimeException("必须选择传入布局或者view")
        }


        if (rootView != null) {
            addView(rootView, params())
            bindUI(rootView)

        } else {
            val root = LayoutInflater.from(context)
                .inflate(layout!!, null)
            addView(
                root, params()
            )

            bindUI(root)


        }

        this.z = this.level().toFloat()

        if (this.isDark()) {
            this.setBackgroundColor("#4d000000".toColor())
        }
        safeClickListener {
            if (canceledOnTouchOutside()) {
                dismiss()
            }
        }


    }


    open fun bindUI(rootView: View) {

    }


    open fun show() {
        val activity = context.findActivity()
        if (activity != null) {
            val root = activity.window.decorView.rootView
            var viewDialogContainer =
                root.findViewWithTag<XViewDialogContainer>(XViewDialogContainer::class.java.name)
            //还没容器就添加容器
            if (viewDialogContainer == null) {
                viewDialogContainer = XViewDialogContainer(context)
                (root as ViewGroup).addView(
                    viewDialogContainer,
                    ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                    )
                )

            }
            //显示添加
            viewDialogContainer.showDialog(this)

        }
    }

    override fun business() {

    }


}
kotlin 复制代码
class XViewDialogContainer : FrameLayout {


    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    fun showDialog(child: ViewDialog) {


        val params =
            LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        if (child.single()) {
            children
            val has = children.firstOrNull {
                it::class.java.name.equals(child::class.java.name)
            }
            if (has != null) {
                return
            } else {
                addView(child, params)
            }
        } else {
            addView(child, params)
        }


    }

    init {
        isFocusable = true
        isFocusableInTouchMode = true;
        requestFocus()
        z = 65535f

        setOnKeyListener { _, keyCode, event ->
            if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
                if (childCount > 0) {
                    //遍历第一个
                    val childZ = sortByZ()

                    val first = childZ.firstOrNull {
                        (it as IViewDialog).canceledAble()
                    }
                    if (first != null) {
                        (first as IViewDialog).dismiss()
                    }
                }

            }
            false

        }
    }

    override fun getChildDrawingOrder(childCount: Int, drawingPosition: Int): Int {
        val orderedViews: MutableList<View> = ArrayList()
        for (j in 0 until childCount) {
            val child = getChildAt(j)
            orderedViews.add(child)
        }

        orderedViews.sortWith { view1, view2 ->
            val zIndex1 = view1.z.toInt()
            val zIndex2 = view2.z.toInt()
            zIndex1.compareTo(zIndex2)
        }

        return indexOfChild(orderedViews[drawingPosition])
    }

    /**
     * z轴排序响应返回事件
     * @return MutableList<View>
     */

    private fun sortByZ(): MutableList<View> {
        val list = children.toMutableList()
        list.toMutableList().sortWith { a, b ->
            a.z.toInt().compareTo(b.z.toInt())
        }
        return list

    }

    init {
        tag=this::class.java.name
    }

}

总结

该方案并非百分百解决所有问题,但是基于我们的业务线是没有问题的。 但是该方案因为是基于View,则不可能比默认系统Dialog 弹出层级更高,所以在使用的时候,需要结合自身业务需求进行处理。 代码比较简单,侵入性极低。基本不存在泄漏风险。大家可以参考自己业务线需求处理。

基友网地址

github.com/nuonuoOkami...

相关推荐
谋爱先谋生爱人先爱己5 个月前
Android - 解密 Clean Architecture 框架
android·android things
Zhujiang7 个月前
Jetpack Bluetooth——更优雅地使用蓝牙
android·android jetpack·android things
编码熊8 个月前
Android自定义View 实现一个带音效和震动的SeekBar
android things
时光少年9 个月前
Android alpha动画隐形成本优化
android·前端·android things
时光少年9 个月前
Android StringBuffer 内存碎片优化
android·性能优化·android things
货拉拉技术1 年前
一种基于MVVM的Android换肤方案
android·android things
Zhujiang1 年前
探索 Jetpack Glance 的魔法
android·android jetpack·android things
Zhujiang1 年前
稳定的 Glance 来了,安卓小部件有救了!
android·android jetpack·android things
Pika1 年前
关于方法id生成
android·kotlin·android things