• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

android对话框透传Touch事件

android 搞代码 4年前 (2022-03-01) 29次浏览 已收录 0个评论

当初一个一般activity页心愿对话框弹出来之后, 空白区域依然能进行滑动点击等操作,也就是心愿可能透传给上面的activity, 同时原有的在对话框视图上的各种点击和滑动操作也不应该受到影响. 这个需要听起来多余且辣手, 对话框弹出的目标就是为了强化揭示和屏蔽操作,当初居然要去除那就失去了应用对话框的意义了, 然而iOS居然能够做到!所以不得不硬着头皮看源码实现一下.

家喻户晓安卓中的Dialog是在另外一个Window实例中增加的视图,比Activity所在的Widnow层级要高. 如果仅仅是为了达到成果,如上所说其实就基本不应该用Dialog! 可能想到的控件当然是PopupWindow了,但改成PopupWindow之后它的层级就放在了Activity所在的Window了, 层级一低会毁坏原有程序实现的很多case,而且可能呈现测试笼罩不到的状况减少线上危险, 所以很多实现的麻烦并不是实现自身,而且是波及太多的程序上下文.

只能针对Dialog进行批改了,这篇帖子给咱们一个启发(尽管帖子自身没人答复), 那就是利用Activity.dispatchTouchEvent! 把没有生产的事件整个的交给activity实例去解决,这样即能透传事件也不影响原有对话框视图的各种操作! 那么Touch事件又从何而来? 这就得理解Dialog的实现机制了,先看Dialog创立时的源码:

mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
    if (mCancelable) {
        cancel();
    }
});

Dialog创立了一个Window的实例, 最要害的是w.setCallback(this), window的所有回调都传给了dialog对象,其中就有public boolean dispatchTouchEvent(MotionEvent event);, 同时各种dialog的子类AppCompatDialog, AlertDialog都有可笼罩的办法: public boolean onTouchEvent(@NonNull MotionEvent event), 这就h了, 只有在dialog的子类持有activity实例, 再把没有生产的事件间接传送给activity不就完事了! 一个小的delegate而已, 于是有:

<code class="kotlin">class YourDialog(context: Context) : AlertDialog(context, resolveDialogTheme(context, 0)) {
    private val activity = if (context is Activity) context else null

    companion object {
        // Copy from AlertDialog.resolveDialogTheme
        fun resolveDialogTheme(context: Context, @StyleRes resid: Int): Int {
            // Check to see if this resourceId has a valid package ID.
            return if (resid ushr 24 and 0x000000ff >= 0x00000001) {   // start of real resource IDs.
                resid
            } else {
                val outValue = TypedValue()
                context.theme.resolveAttribute(android.R.attr.alertDialogTheme, outValue, true)
                outValue.resourceId
            }
        }
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        return super.onTouchEvent(ev) || passThrough(ev)
    }

    private fun passThrough(ev: MotionEvent): Boolean {
        return activity?.dispatchTouchEvent(ev) ?: false
    }
}
  • resolveDialogTheme这个办法是为了获取Dialog对应主题,因为申明成package access只能copy过去;
  • 另一个须要留神的问题是activity的实例不能从dialog的context获取, 它的理论类型是ContextThemeWrapper;
  • 第三透传给activity的是dialog没有生产的事件,所以onTouchEvent返回false才调用passThrough.
  • 最初 也是特地须要留神的 须要设置对话框的2个办法:

    setCanceledOnTouchOutside(false)
    setCancelable(false)

    如果没有这2行会产生什么? 理论运行一下就会发现最初2个问题其实是一个问题

这个实现简略平安甚至还带着几分优雅~

———- 0628

果然还是有问题!

发现滚动是好的, 然而点击操作有偏移! y坐标总是偏移了一个statusbarHeight的间隔,传到上层的Activity就是点击的地位向上偏了, 尽管能够获取到statusbar高度并且强行批改MotionEvent(x, y)信息,但底层的activity也有可能是是FULLSCREEN的,兴许会引入问题, 最终发现咱们理论须要的y就是MotionEventrawY, 于是再创立一个新的MotionEvent对象传入就ok, 于是有:

<code class="diff">     private fun passThrough(ev: MotionEvent): Boolean {
-        return activity?.dispatchTouchEvent(ev) ?: false
+        val e = MotionEvent.obtain(ev.downTime, ev.eventTime, ev.action,
+            ev.rawX, ev.rawY,
+            ev.pressure, ev.size, ev.metaState, ev.xPrecision, ev.yPrecision, ev.deviceId, ev.edgeFlags)
+        return activity?.dispatchTouchEvent(e) ?: false
     }

原始的地位信息(ev.rawX, ev.rawY)(而不是(ev.x, ev.y + statusBarHeight))就是咱们须要透传给activity的地位信息, 让activity本人决定要如何解决触摸,这样防止了地位信息的强行批改.

另外一个问题: 当点击上层activity视图上的输入框时, 输入法无奈弹起!

这个有点辣手, 不过个别输入法不弹是因为没有获取到焦点(没有其它谬误的状况下),那有让dialog去除焦点或者禁止获取焦点的办法么? 十分侥幸的是咱们有一个针对Window的flag: FLAG_NOT_FOCUSABLE, 于是在创立对话框后获取到它的window并设置:

window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

如此,输入法果然依照预期弹起了~!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:android对话框透传Touch事件

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址