問(wèn)題梳理
在之前公司做直播項(xiàng)目的時(shí)候床三,在首頁(yè)會(huì)彈出很多dialog,當(dāng)時(shí)有2種方案月趟。
一種是一次只彈出一個(gè)灯蝴,每次回到首頁(yè)再次彈出一個(gè)。
第二種層疊堆放孝宗。
因?yàn)楸娝苤蚯钤辏邚棿坝肋h(yuǎn)要在最上層,其次是升級(jí)彈窗等因妇。
第一種我們暫不討論问潭,各家有各家解決方案。
第二種是大家常用方案沙峻,一般解決方案就是我延遲幾秒彈出睦授,則最上層的必然是XX彈窗。但是當(dāng)涉及網(wǎng)絡(luò)接口的時(shí)候摔寨,不可抗拒力就變大了,畢竟存在網(wǎng)絡(luò)延遲和重試問(wèn)題怖辆。
我們針對(duì)第二種情況封裝了基于View的dialog,在內(nèi)部進(jìn)行層級(jí)處理是复,基于FrameLayout的堆疊特性。實(shí)現(xiàn)了ViewDialog竖螃。經(jīng)過(guò)線上的考驗(yàn)淑廊。
使用方式簡(jiǎn)單粗暴。
解決方案
依賴開(kāi)源庫(kù)
implementation 'io.github.nuonuoOkami:ViewDialog:1.0.0'
使用方法
無(wú)需業(yè)務(wù)處理
//直接傳入布局
ViewDialog(MainActivity@ this, layout = R.layout.activity_main).show()
// 支持傳入View
ViewDialog(
MainActivity@ this,
rootView = LayoutDialogBinding.inflate(layoutInflater).root
).show()
內(nèi)部進(jìn)行業(yè)務(wù)處理 繼承ViewDialog
//自定義Demo
class DemoDialog2(content: Activity) :
ViewDialog(content, rootView = LayoutDialogBinding.inflate(content.layoutInflater).root) {
//控件處理
override fun bindUI(rootView: View) {
super.bindUI(rootView)
}
//level 越大 顯示的時(shí)候就在上面
override fun level() = 3
//默認(rèn)warp
override fun params(): LayoutParams {
val layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
)
layoutParams.gravity = gravity()
return layoutParams
}
//同類型只能存在一個(gè) 避免多次彈窗
override fun single()=true
//是否彈窗變黑
override fun isDark()=true
//支持響應(yīng)事件
override fun canceledAble()=true
//是否可以點(diǎn)擊外部取消
override fun canceledOnTouchOutside() = true
//業(yè)務(wù)處理
override fun business() {
super.business()
}
//直接調(diào)用show()和dismiss()就行特咆,無(wú)需其他操作
val dialog2= DemoDialog2(MainActivity@this)
dialog2 .show()
dialog2.dismiss()
原理解析
找到當(dāng)前Activity的rootView,添加進(jìn)一個(gè)容器 XViewDialogContainer季惩,設(shè)置tag。
當(dāng)ViewDialog 調(diào)用show()的時(shí)候腻格,會(huì)檢查當(dāng)前頁(yè)面有沒(méi)有對(duì)應(yīng)容器画拾,沒(méi)有就進(jìn)行容器添加,有則直接調(diào)用容器將自己添加進(jìn)去菜职。
在 容器內(nèi)部青抛,根據(jù)所謂的level 值 進(jìn)行添加,其實(shí)這里就是z值酬核,容器的Z值被設(shè)置的特別高蜜另,為65535,一般沒(méi)有正常View會(huì)超過(guò)這個(gè)值嫡意。
在內(nèi)部添加ViewDialog的時(shí)候举瑰,會(huì)對(duì)傳入的ViewDialog 進(jìn)行排序。如果需要響應(yīng)返回事件和點(diǎn)擊外部消失蔬螟,也做了對(duì)應(yīng)的處理此迅。
因?yàn)閷?shí)現(xiàn)了DialogInterface ,所以使用方法和普通dialog一致。
現(xiàn)將核心代碼奉上邮屁,大家可以參考整袁,歡迎提出建議!
源碼
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)
//還沒(méi)容器就添加容器
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() {
}
}
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) {
//遍歷第一個(gè)
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軸排序響應(yīng)返回事件
* @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
}
}
總結(jié)
該方案并非百分百解決所有問(wèn)題佑吝,但是基于我們的業(yè)務(wù)線是沒(méi)有問(wèn)題的坐昙。
但是該方案因?yàn)槭腔赩iew,則不可能比默認(rèn)系統(tǒng)Dialog 彈出層級(jí)更高,所以在使用的時(shí)候芋忿,需要結(jié)合自身業(yè)務(wù)需求進(jìn)行處理炸客。
代碼比較簡(jiǎn)單,侵入性極低戈钢”韵桑基本不存在泄漏風(fēng)險(xiǎn)。大家可以參考自己業(yè)務(wù)線需求處理殉了。
基友網(wǎng)地址
https://github.com/nuonuoOkami/XViewDialog/blob/master/README.md