业务小结:AlertDialog弹窗切换间如何不响应任何点击点击事件

550 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

不知道大家是否有看懂这个标题,就是当前界面中弹出一个AlertDialog,dismiss掉然后弹出另外一个AlertDialog,这两个弹窗切换期间不允许任何触摸事件传递到下层的当前界面,进行响应。

可能我这样说大家还是不明白,接下来我们通过一个例子举例说明:

class DialogActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityDialogBinding

    private var mDialog: Dialog? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = ActivityDialogBinding.inflate(layoutInflater)
        setContentView(mBinding.root)

        mBinding.root.setOnClickListener {
            Toast.makeText(this, "你点击了界面", Toast.LENGTH_SHORT).show()
        }

        mBinding.root.postDelayed(10 * 1000) {
            showDialog()
        }
    }

    private fun showDialog() {
        mDialog = AlertDialog.Builder(this)
            .setTitle("dialog1")
            .setMessage("这里是Dialog1")
            .setPositiveButton("confirm") { _, _ ->
                showDialog2()
            }
            .create()
        mDialog?.show()
    }

    private fun showDialog2() {
        mDialog = AlertDialog.Builder(this)
            .setTitle("dialog2")
            .setMessage("这里是Dialog2")
            .setPositiveButton("confirm2") { _, _ ->
                showDialog()
            }
            .create()
        mDialog?.show()
    }
}

先解释下代码逻辑:

  1. 首先给界面根布局设置一个点击事件,并弹出toast提示"你点击了界面";

  2. 接着通过postDelayed()方法延迟10s调用方法showDialog()弹出第一个弹窗,当点击弹窗确认按钮后,调用showDialog2()方法弹出第二个弹窗,同时dimiss掉第一个弹窗;

  3. 第二个弹窗的确认按钮点击后,就会调用showDialog()方法弹出第一个弹窗,也就是说在通过弹窗的确认按钮,在第一个弹窗和第二个弹窗之间不断的切换;

开头所讲的问题就是,在第一个和第二个弹窗切换之间,不允许点击事件传递到下面的界面并触发点击事件弹出toast,上面的代码这样写是会触发的,截图如下 :

7bd8490b6cb034a0b9a62d25f99eb33.jpg

不多比比,下面给出解决方案。

当界面弹出dialog弹窗时,我们可以给界面的根布局添加一个透明的View,在dialog切换之间拦截触摸事件分发到下面界面中响应点击事件。

具体的代码如下:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //...
    mBinding.root.postDelayed(10 * 1000) {
        mBinding.root.addView(View(this).apply {
            layoutParams = LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.MATCH_PARENT
            )
            //设置界面背景透明
            setBackgroundColor(Color.TRANSPARENT)
            //消费点击事件,避免直接传递到界面布局中
            setOnClickListener {
            }
        })
        showDialog()
    }
}

在通过postDelay()延迟弹出弹窗时,先给根布局添加一个透明的View拦截事件向下分发,请注意,这里有个非常非常关键的点:

主动调用了setOnClickListener {}去消费了点击事件.

如果没有这一行代码,那么你这个拦截将是无效的。为什么呢,因为根据View事件分发机制,它是遍历子View集合去分发触摸事件,如果你这个额外添加的透明View没有消费点击事件,那么事件分发会将触摸事件分发到另一个子View中(即弹窗下面的界面布局)消费,这就直接导致了继续响应弹出toast的根布局点击事件。

还有一个需要注意的地方,在合适的位置移除透明View: 当界面需要完全关闭弹窗时,记得将这个额外添加的透明View从根布局中移除.

这个地方也有一个小技巧:

我们可以给这个添加的View指定一个id,当从根布局移除这个透明View时,直接通过这个id去获取这个透明View,如果存在就直接从根布局进行移除。

  1. 首先在xml中定义一个id:
<resources>
    <item name="globe_view" type="id" />
</resources>
  1. 然后向根布局添加透明View指定为这个id:
mBinding.root.postDelayed(10 * 1000) {
    mBinding.root.addView(View(this).apply {
        //...
        //消费点击事件,避免直接传递到界面布局中
        setOnClickListener {
        }
        //指明id
        id = R.id.globe_view
    })
    showDialog()
}
  1. 当需要移除透明View,直接通过id获取并移除:
private fun removeMaskView() {
    mBinding.root.findViewById<View>(R.id.globe_view)?.let {
        mBinding.root.removeView(it)
    }
}

总结

本篇文章主要是讲解了在界面弹窗切换间 ,拦截触摸事件分发到下层界面响应的解决方法 :

主要是通过添加一个透明View实现拦截,当需要移除该透明View时,可以考虑通过View Id的方式获取View并从根布局中进行移除。