Dialog队列能避免页面中同时显示复数的窗口,App的首页是个重灾区,例如广告弹框,公告弹框,更新弹框等。

一个Dialog队列应该具备以下特性:

  1. 防重复弹出,因为很多弹框依赖接口返回,这些接口通常也需要在onResume的时候刷新,这种情况的一般解决方案是使用一个成员变量来控制。
  2. 优先级,例如我希望公告弹框优先于广告弹框
  3. 懒加载,在显示时才构建Dialog
  4. 不占用dialog原本的dismissListener,因为我可能要在弹框消失时做某些操作
  5. 生命周期感知,在onResume时才让队列工作, 并且在onDestroy中自动隐藏dialog防止窗体泄漏

实现分析

  1. 防重复:用一个 id 或 tag 来标识 dialog 的唯一性
  2. 优先级:使用优先队列
  3. 懒加载:优先队列中的元素是dialog构建函数,用来构建Dialog
  4. 不占用listener:提供一个next函数给外部主动调用,意味着构建外部构建dialog时需要手动设置dismissListener调用next函数。
  5. 生命周期感知:传入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()
        }
    }
}

Github 完整代码

https://github.com/aitsuki/DialogQueue