Windows Phone开发之 禁用back键

禁用页面回退实现方法
本文介绍如何在开发过程中,通过在XAML文件中注册事件和后台实现方法来禁用页面的回退操作,确保业务流程的连续性和用户体验的一致性。


在开发过程中,因为页面是无状态的,可能会导致页面的回退操作逆向业务需求而禁用回退

两步可以轻松搞定


第一 在XAML文件注册事件:

BackKeyPress="PhoneApplicationPage_BackKeyPress">


第二在后台实现方法:

private void PhoneApplicationPage_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
        {
            e.Cancel = true;
        } 

或者直接实现重载

        protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
        {
            e.Cancel = true;
        }


<think> 我们正在Kotlin Compose环境中使用WindowManager和addView创建窗口,并需要监听返回Back)事件。 由于我们是通过WindowManager添加的视图,这个视图通常是一个ViewGroup(例如ComposeView),我们需要在视图的上下文中处理返回。 在Android中,返回事件通常由Activity处理,但是当我们创建一个悬浮窗口(使用WindowManager)时,这个窗口可能没有关联Activity,所以我们需要自己处理。 有两种主要方法: 1. 在窗口的根视图上设置按监听(setOnKeyListener),并检测返回KeyEvent.KEYCODE_BACK)。 2. 如果窗口中的视图是Compose UI,我们可以利用Compose中的按事件处理。 步骤: 第一步:创建窗口并添加视图 第二步:为根视图设置按监听,或者在Compose中使用Modifier.keyEventHandler来处理。 但是注意:悬浮窗口(使用WindowManager)默认不会接收按事件,我们需要在创建窗口时设置相应的标志(FLAG)来允许接收按事件。 具体操作: 1. 在创建WindowManager.LayoutParams时,需要设置flags,允许接收输入事件,并且通常需要设置FLAG_NOT_FOCUSABLE(如果不希望窗口获取焦点,则设置这个标志,但这样就不能接收按事件)。因此,如果我们希望接收按事件,则不能设置FLAG_NOT_FOCUSABLE,或者设置FLAG_NOT_FOCUSABLE的同时设置FLAG_NOT_TOUCH_MODAL,这样窗口可以接收外部触摸事件,同时也能够接收按事件?实际上,对于按事件,我们需要窗口能够获取焦点。 因此,建议如下: 如果我们希望窗口能够接收按事件,那么我们应该设置FLAG_NOT_FOCUSABLE为false,即让窗口可以获取焦点。或者我们不设置FLAG_NOT_FOCUSABLE,而是设置其他标志,同时确保窗口可以获取焦点。 但是,如果我们希望窗口在显示时,后面的Activity仍然可以接收触摸事件,我们可以设置FLAG_NOT_TOUCH_MODAL,这样窗口外的触摸事件会传递给下层的窗口,但窗口内的触摸事件会自己处理。同时,如果我们希望窗口接收按事件,那么我们需要设置FLAG_NOT_FOCUSABLE为false,或者不设置该标志(默认是可获取焦点的)。 然而,有时我们希望窗口不获取焦点(以免弹出软盘等),那么我们可以设置FLAG_NOT_FOCUSABLE,然后通过其他方式拦截返回?实际上,设置FLAG_NOT_FOCUSABLE后,窗口无法接收按事件。因此,我们需要权衡。 另一种思路:在Activity中重写onKeyDown或onBackPressed方法,但是这样会影响到整个Activity。如果我们的悬浮窗口是全局的(比如在其他应用之上),那么这种方法就不适用。 因此,针对悬浮窗口,我们通常需要: - 设置WindowManager.LayoutParams的flags为FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL(这样窗口不会获取焦点,但触摸事件会自己处理,并且按事件不会传递给窗口)。但是这样设置后,窗口就无法接收按事件了。 所以,为了解决这个问题,我们可以: 1. 不设置FLAG_NOT_FOCUSABLE,这样窗口可以获取焦点,从而可以接收按事件。但是这样会导致窗口获取焦点,可能会影响输入法弹出等。 2. 使用一个透明的Activity来承载这个窗口?这样并不好。 实际上,有一个标志FLAG_WATCH_OUTSIDE_TOUCH,但是它只用于触摸事件。 因此,我们可能需要改变思路:让窗口可以获取焦点(即不设置FLAG_NOT_FOCUSABLE),然后通过设置inputType等来避免弹出软盘(如果需要的话)。 具体实现: 方法一:在根View上设置OnKeyListener 步骤: a. 创建窗口视图时,获取根视图(比如FrameLayout)。 b. 设置根视图的setOnKeyListener。 方法二:在Compose UI中处理 如果窗口中的视图是用Compose写的,那么我们可以使用Modifier.onKeyEvent(在Compose中)来处理按事件。但注意,这要求Compose视图能够获取焦点,所以我们需要先使用Modifier.focusRequester来请求焦点。 考虑到我们使用WindowManager添加的视图,建议使用方法一,因为这样更直接,而且可以确保在根视图上捕获事件。 具体代码示例: 第一步:创建窗口 第二步:设置视图的按监听 注意:我们必须确保窗口能够接收按事件,所以LayoutParams的flags不能包含FLAG_NOT_FOCUSABLE。 示例LayoutParams: val params = WindowManager.LayoutParams( ... flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, // 不要设置FLAG_NOT_FOCUSABLE ... ) 然后,在添加到窗口之前,为根视图设置按监听: val rootView = ... // 你的视图,比如一个ComposeView包装在一个FrameLayout中 rootView.setOnKeyListener { v, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { // 处理返回 true // 表示事件已消费 } false } 但是,请注意:如果窗口获取了焦点,那么返回事件会先传递到该窗口的根视图。如果窗口没有获取焦点,则不会触发。 另外,如果我们在Compose中处理,可以这样做(在ComposeView内部): Modifier .focusRequester(focusRequester) .focusable() .onKeyEvent { keyEvent -> if (keyEvent.nativeKeyEvent.keyCode == KeyEvent.KEYCODE_BACK) { if (keyEvent.nativeKeyEvent.action == KeyEvent.ACTION_UP) { // 处理 true } else { true } } else { false } } 并且,我们需要在Compose组件加载时请求焦点: LaunchedEffect(Unit) { focusRequester.requestFocus() } 但是,这种方法需要确保ComposeView能够获取焦点,并且窗口能够接收按事件(同样要求LayoutParams不设置FLAG_NOT_FOCUSABLE)。 总结: 1. 使用WindowManager添加窗口时,确保LayoutParams中没有设置FLAG_NOT_FOCUSABLE,这样窗口才能接收按事件。 2. 在根视图(ViewGroup)上设置OnKeyListener,或者在Compose中使用按修饰符(需要请求焦点)来监听返回。 注意:如果窗口中有多个可获取焦点的视图,返回事件可能被其他视图处理,所以最好在根视图上监听。 另外,如果窗口是全局的(比如在其他应用之上),那么我们需要申请悬浮窗权限,并且可能需要使用TYPE_APPLICATION_OVERLAY(针对Android O及以上)。 代码示例(简化): 创建窗口: val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager val params = WindowManager.LayoutParams( width, height, type, // 对于Android O及以上,使用WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY flags, // 设置flags,确保不包含WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE format ) val rootView = FrameLayout(context).apply { // 设置背景,否则可能无法接收事件 setBackgroundColor(Color.TRANSPARENT) } // 添加ComposeView val composeView = ComposeView(context).apply { setContent { // 你的Compose UI } } rootView.addView(composeView) // 设置按监听 rootView.setOnKeyListener { _, keyCode, event -> if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { // 处理返回,比如移除窗口 windowManager.removeView(rootView) true } false } // 添加视图 windowManager.addView(rootView, params) 这样,当用户按下返回时,就会触发监听器,然后我们可以移除窗口或者执行其他操作。 但是,注意:由于我们设置了FLAG_NOT_FOCUSABLE为false(即默认可获取焦点),所以窗口会获取焦点,可能会影响其他输入操作(比如输入法可能不会自动弹出到下层Activity)。因此,我们需要根据实际场景权衡。 如果不想让窗口获取焦点,同时又想监听返回,是否有办法? 在Android中,如果窗口不可聚焦,那么它不会接收按事件。所以,如果你的窗口不可聚焦,那么上述方法无效。 这时,我们可以考虑在Activity级别拦截返回,但这样会影响整个Activity,而且如果窗口是全局的(跨应用),则无法通过Activity拦截。 因此,对于悬浮窗口,如果希望监听返回,通常需要让窗口可聚焦。 另外,还有一种方法:使用InputMonitor(仅系统应用可用),普通应用无法使用。 所以,对于普通应用,我们只能让窗口可聚焦。 综上所述,在Kotlin Compose环境中,通过WindowManager和addView创建的窗口,监听返回的方法是: 1. 在WindowManager.LayoutParams中不要设置FLAG_NOT_FOCUSABLE(即可聚焦)。 2. 在窗口的根视图上设置setOnKeyListener,监听返回KeyEvent.KEYCODE_BACK)的ACTION_UP事件。 注意:在监听器中,返回true表示事件被消费,不再传递;返回false表示继续传递。 如果窗口中有Compose UI,并且你希望只在Compose的某个组件上监听返回,那么可以在Compose中使用按修饰符,并确保该组件可获取焦点(同时整个窗口可聚焦)。 最后,由于返回事件可能被其他可聚焦的组件消费(比如EditText),所以建议在根视图上监听,以确保捕获所有返回事件。 相关问题: 1. 如果窗口中有多个可聚焦的视图,返回事件如何传递? 2. 如何在不获取焦点的情况下监听返回?(答案:普通应用无法做到) 3. 在Compose UI中处理返回事件与在View树中处理有何不同? 4. 如何让窗口在显示时不自动获取焦点?(实际上,如果不设置FLAG_NOT_FOCUSABLE,添加窗口后它会自动获取焦点) 注意:在Android中,窗口的焦点管理是自动的,当我们添加一个窗口时,如果该窗口是可聚焦的,那么它会自动获取焦点。如果我们希望窗口不自动获取焦点,可以尝试在添加窗口后,调用其他窗口的requestFocus,但这样可能会引起闪烁等问题。 因此,通常的做法是:让窗口可聚焦,并监听返回,然后在不需要时移除窗口。 </think> 在 Kotlin Compose 环境中,通过 `WindowManager` 和 `addView` 创建的悬浮窗口监听返回Back),需解决两个关问题:**窗口级别的返回拦截** 和 **窗口内 Compose 组件的按处理**。以下是实现方案: --- ### 一、窗口级别的返回拦截 当悬浮窗口获得焦点时,通过根布局的 `OnKeyListener` 捕获返回事件: ```kotlin val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager val params = WindowManager.LayoutParams( // 关:必须允许窗口接收输入事件 flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 初始设为不可聚焦 .or(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN) .or(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH), type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY // Android 8.0+ 必需 ) // 创建根布局(必须为 ViewGroup) val rootView = FrameLayout(context).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) // 设置按监听 setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { // 处理返回逻辑(如关闭窗口) windowManager.removeView(rootView) true // 事件已消费,不再传递 } else false } } // 添加 ComposeView 到根布局 val composeView = ComposeView(context).apply { setContent { /* Compose UI 内容 */ } } rootView.addView(composeView) // 显示窗口 windowManager.addView(rootView, params) ``` #### 关点: 1. **焦点控制**: - 初始使用 `FLAG_NOT_FOCUSABLE` 避免影响下层 Activity - **动态获取焦点**:当需要拦截返回时,更新标志位: ```kotlin params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv() windowManager.updateViewLayout(rootView, params) rootView.requestFocus() // 请求焦点以接收按事件 ``` 2. **权限要求**: - Android 8.0+ 必须声明 `TYPE_APPLICATION_OVERLAY` 类型 - 需在 Manifest 添加权限: ```xml <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> ``` --- ### 二、窗口内 Compose 组件的按处理 在 Compose UI 中,通过 `Modifier.keyEvents` 监听按: ```kotlin @Composable fun FloatingContent() { var text by remember { mutableStateOf("Press Back") } val focusRequester = remember { FocusRequester() } Box( modifier = Modifier .fillMaxSize() .focusRequester(focusRequester) .focusable() // 允许组件获取焦点 .onKeyEvent { event -> if (event.nativeKeyEvent.keyCode == KeyEvent.KEYCODE_BACK && event.nativeKeyEvent.action == KeyEvent.ACTION_UP) { text = "Back Pressed!" true // 消费事件 } else false } ) { Text(text) } // 自动请求焦点(组件加载时) LaunchedEffect(Unit) { focusRequester.requestFocus() } } ``` #### 关点: 1. **焦点链传递**: - Compose 组件需通过 `focusRequester` 主动请求焦点 - 窗口根布局必须允许焦点传递(未设置 `FLAG_NOT_FOCUSABLE`) 2. **事件冲突解决**: - 若窗口和 Compose 同时监听,事件优先由最内层组件消费 - 需协调处理逻辑(如根布局拦截后不传递给 Compose) --- ### 三、完整流程示例 ```kotlin // 创建悬浮窗口 fun showFloatingWindow(context: Context) { val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager val params = WindowManager.LayoutParams( width = 600, height = 400, type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, format = PixelFormat.TRANSLUCENT ) val rootView = FrameLayout(context).apply { setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { windowManager.removeView(this) true } else false } } val composeView = ComposeView(context).apply { setContent { Box(Modifier.background(Color.White)) { FloatingContent() // 包含按监听的Compose组件 } } } rootView.addView(composeView) windowManager.addView(rootView, params) } ``` --- ### ✅ 注意事项 1. **权限处理**: - Android 6.0+ 需动态申请 `SYSTEM_ALERT_WINDOW`: ```kotlin Settings.canDrawOverlays(context) // 检查权限 ``` 2. **焦点管理**: - 窗口显示时调用 `rootView.requestFocus()` 确保接收按 - 避免 `FLAG_NOT_FOCUSABLE` 和按监听的冲突 3. **兼容性**: - Android 8.0 以下使用 `TYPE_PHONE` 代替 `TYPE_APPLICATION_OVERLAY` --- ### 相关问题 1. 如何在悬浮窗失去焦点时自动关闭? 2. Compose 多组件场景下如何统一处理返回事件? 3. 如何实现悬浮窗拖动时临时禁用返回监听? 4. `WindowManager` 跨进程通信时如何传递按事件? [^1]: Android 开发者文档 - 系统悬浮窗权限管理 [^2]: Compose 官方文档 - 焦点控制与按交互
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值