问题梳理
在之前公司做直播项目的时候,在首页会弹出很多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 弹出层级更高,所以在使用的时候,需要结合自身业务需求进行处理。 代码比较简单,侵入性极低。基本不存在泄漏风险。大家可以参考自己业务线需求处理。