Dialog队列能避免页面中同时显示复数的窗口,App的首页是个重灾区,例如广告弹框,公告弹框,更新弹框等。
一个Dialog队列应该具备以下特性:
- 防重复弹出,因为很多弹框依赖接口返回,这些接口通常也需要在onResume的时候刷新,这种情况的一般解决方案是使用一个成员变量来控制。
- 优先级,例如我希望公告弹框优先于广告弹框
- 懒加载,在显示时才构建Dialog
- 不占用dialog原本的dismissListener,因为我可能要在弹框消失时做某些操作
- 生命周期感知,在onResume时才让队列工作, 并且在onDestroy中自动隐藏dialog防止窗体泄漏
实现分析
- 防重复:用一个 id 或 tag 来标识 dialog 的唯一性
- 优先级:使用优先队列
- 懒加载:优先队列中的元素是dialog构建函数,用来构建Dialog
- 不占用listener:提供一个next函数给外部主动调用,意味着构建外部构建dialog时需要手动设置dismissListener调用next函数。
- 生命周期感知:传入lifecycleOwner对象。
实现代码
class DialogQueue(private val lifecycleOwner: LifecycleOwner) {
private var activeDialog: Dialog? = null
private val pq = PriorityQueue<Task>(11, Comparator { o1, o2 ->
return@Comparator o1.priority - o2.priority
})
init {
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
tryPoll()
}
override fun onDestroy(owner: LifecycleOwner) {
activeDialog?.dismiss()
activeDialog = null
}
})
}
private fun tryPoll() {
if (activeDialog == null && lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
val task = pq.poll() ?: return
val nextFunc = fun() {
activeDialog = null
tryPoll()
}
activeDialog = task.dialogBuilder(nextFunc)
activeDialog?.show()
}
}
fun offer(tag: String, priority: Int, dialogBuilder: (next: () -> Unit) -> Dialog) {
val task = Task(tag, priority, dialogBuilder)
if (pq.contains(task)) {
return
}
pq.offer(task)
tryPoll()
}
private class Task(
val tag: String,
val priority: Int,
val dialogBuilder: (next: () -> Unit) -> Dialog
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Task
if (tag != other.tag) return false
return true
}
override fun hashCode(): Int {
return tag.hashCode()
}
}
}
使用方式
class SampleActivity : AppcompatActivity {
// 传入一个lifecycleOwner对象来初始化一个Dialog队列
private val queue = DialogQueue(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 将显示“bar” “foo”, 因为bar优先级较高
showDialogs()
findViewById<Button>(R.id.show_btn).setOnClickListener {
// 将显示“foo” “bar”,因为此时队列是空,所以 “foo” 入列的一瞬间就显示了
showDialogs()
}
}
private fun showDialogs() {
queue.offer("foo", 1) { next ->
MaterialAlertDialogBuilder(this)
.setMessage("Hello, foo!")
.setOnDismissListener { next() } // 在dialog消失时调用next函数
.create()
}
queue.offer("bar", 2) { next ->
MaterialAlertDialogBuilder(this)
.setMessage("Hello, bar!")
.setOnDismissListener { next() } // 在dialog消失时调用next函数
.create()
}
}
}