Android PopuWindow实现物理返回键监听

【存在问题】

正常情况下,PopuWindow是无法监听到物理返回键事件的,因为PopuWindow的最顶层的PopupDecorView mDecorViewdispatchKeyEvent中将返回键给拦截掉了,我们瞅一瞅PupoWindow的这段源码:

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                if (getKeyDispatcherState() == null) {
                    return super.dispatchKeyEvent(event);
                }

                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
                    final KeyEvent.DispatcherState state = getKeyDispatcherState();
                    if (state != null) {
                        state.startTracking(event, this);
                    }
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP) {
                    final KeyEvent.DispatcherState state = getKeyDispatcherState();
                    if (state != null && state.isTracking(event) && !event.isCanceled()) {
                        dismiss();
                        return true;
                    }
                }
                return super.dispatchKeyEvent(event);
            } else {
                return super.dispatchKeyEvent(event);
            }
        }

正因为如此,即使我们像网上所说的这样设置,也是无效的:

 mPopupWindow.getContentView().setFocusable(true);
 mPopupWindow.getContentView().setFocusableInTouchMode(true);
 mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
 mPopupWindow.getContentView().setOnKeyListener(new View.OnKeyListener() {
     @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
           Log.i("min77","监听到返回键");
           return true;
        }
});

【问题分析】

从PopuWindow的源码可以知道,以下三种情况,会调用到dismiss方法:
1、当设置setFocusable(true)setOutsideTouchable(false),点击PopuWindow外部会调用dismiss方法
2、当用户按下物理返回键的时候,也会调用到dismiss方法
3、代码自己调用了dismiss方法去关闭PupoWindow

【问题解决】

基于上述分析,我们可以自定义一个CustomPopupWindow 继承 PopupWindow,并重写dismiss()方法,在里面使用new Exception().getStackTrace()或者StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();方法获取到调用栈
1)假如存在dispatchKeyEvent,那么就是用户按了物理返回键
2)假如存在onTouchEvent,那么就是用户点击了PopupWindow外部
3)剩下的就是代码自身调用的dismiss
下面这段代码就是只处理物理返回键事件而已,其他事件都是默认关闭PopupWindow

public class CustomPopupWindow extends PopupWindow {
    private OnBackPressListener mOnBackPressListener;

    public CustomPopupWindow(View contentView, int width, int height, boolean focusable) {
        super(contentView, width, height, focusable);
    }

    public CustomPopupWindow(Context context) {
        super(context);
    }

    public CustomPopupWindow() {
    }

    public CustomPopupWindow(View contentView) {
        super(contentView);
    }

    public CustomPopupWindow(int width, int height) {
        super(width, height);
    }

    public CustomPopupWindow(View contentView, int width, int height) {
        super(contentView, width, height);
    }

    @Override
    public void dismiss() {
        StackTraceElement[] stackTrace = new Exception().getStackTrace();
//        for (int i = 0; i < stackTrace.length; i++) {
//            Logger.i("key = " + stackTrace[i]);
//        }

        if (stackTrace.length >= 2 && "dispatchKeyEvent".equals(stackTrace[1].getMethodName())) {
            //按了返回键
            if(mOnBackPressListener != null){
                mOnBackPressListener.onBack();
            }else {
                dismiss2();
            }

        } else {
            //点击外部或者点击关闭调用了dismiss,直接让弹窗消失
            dismiss2();
        }

    }

    //让弹窗消失
    private void dismiss2() {
        super.dismiss();
    }


    public OnBackPressListener getOnBackPressListener() {
        return mOnBackPressListener;
    }

    public void setOnBackPressListener(OnBackPressListener onBackPressListener) {
        this.mOnBackPressListener = onBackPressListener;
    }

    public interface OnBackPressListener{
        void onBack();
    }
}
Android中,返回(通常表示为Back)的监听是为了处理应用程序中的导航行为。当用户按下设备的返回时,你可以捕获这个事件并根据应用的状态执行相应的操作,如关闭当前活动、退回到上一屏幕或显示一个自定义的确认提示。 以下是实现Android返回监听的基本步骤: 1. **继承`Activity`或其子类**:在创建一个新的Activity时,确保它直接或间接地继承了`Activity`类,因为返回的行为主要由Activity管理。 ```java public class MyActivity extends AppCompatActivity { //... } ``` 2. **重写`onBackPressed()`方法**:覆盖`onBackPressed()`方法来定义当用户按回车时应该执行的操作。在这个方法中,你可以检查条件并决定如何响应。 ```java @Override public void onBackPressed() { if (/* 判断条件 */) { // 执行特定操作 } else { super.onBackPressed(); // 如果不需要特殊处理,调用父类的方法以关闭当前Activity } } ``` 3. **处理特殊情况**:例如,在某些场景下,你可能不希望让系统默认的返回行为发生,这时可以在`onOptionsItemSelected()`方法里处理`android.R.id.home`动作,或者在`onCreateOptionsMenu()`和`onOptionsItemSelected(MenuItem item)`中处理菜单项的"返回"功能。 4. **注意模式切换**:如果你的应用包含模式切换(如夜间模式),还需要在适当的地方保存和恢复状态,以便在返回被点击后能够正确恢复。 相关问题-- 1. Android中为什么要重写`onBackPressed()`方法? 2. 如何处理用户长按返回? 3. 如何避免Activity在没有完成所有操作就关闭的情况?
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值