You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android中Window.Callback无法接收Motion事件及Toolbar回调检测方案

解决Toolbar替换Window.Callback导致拦截失效的问题

首先咱们得把问题根源理清楚:当你调用setSupportActionBar(Toolbar)setActionBar(Toolbar)时,ToolbarActionBar会创建自己的ToolbarCallbackWrapper,直接替换掉Window的Callback——这就把你之前设置的包装类给覆盖了,导致你的Motion事件拦截逻辑完全失效。

要在不继承基类Activity的前提下解决这个问题,我推荐一个轻量且非频繁检测的方案:让你的Window.Callback包装类具备「自我恢复」的能力,每次事件触发时自动检查并夺回Callback的控制权。

实现步骤

1. 改造你的Window.Callback包装类

创建一个带自我恢复逻辑的PersistentWindowCallback,核心是在每次处理事件前检查当前Window的Callback是否还是自己,如果被替换了就立刻重新设置回去:

public class PersistentWindowCallback implements Window.Callback {
    private final WeakReference<Window> mWindowRef;
    private Window.Callback mOriginalCallback;

    public PersistentWindowCallback(Window window, Window.Callback originalCallback) {
        mWindowRef = new WeakReference<>(window);
        mOriginalCallback = originalCallback;
    }

    // 核心逻辑:确保当前Window的Callback是我们的实例
    private void ensureCallbackIsActive() {
        Window window = mWindowRef.get();
        if (window != null && window.getCallback() != this) {
            // 保存新的原始Callback(也就是Toolbar设置的那个)
            mOriginalCallback = window.getCallback();
            // 重新设置我们的Callback,夺回事件拦截权
            window.setCallback(this);
        }
    }

    // 在这里添加你的Motion事件拦截逻辑
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        ensureCallbackIsActive();
        // 示例:拦截并处理触摸事件
        // Log.d("MotionInterceptor", "捕获到触摸事件:" + event.getAction());
        return mOriginalCallback.dispatchTouchEvent(event);
    }

    // 所有Window.Callback方法都需要调用ensureCallbackIsActive,再转发给原始Callback
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        ensureCallbackIsActive();
        return mOriginalCallback.dispatchKeyEvent(event);
    }

    @Override
    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
        ensureCallbackIsActive();
        return mOriginalCallback.dispatchKeyShortcutEvent(event);
    }

    // 请实现Window.Callback的所有其他方法,逻辑和上面一致
    // ...
}

2. 在Activity中初始化

和你之前的初始化逻辑几乎一致,只是换成我们的持久化Callback:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 正常初始化Toolbar
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    // 设置我们的持久化Callback
    final Window window = getWindow();
    final Window.Callback originalCallback = window.getCallback();
    final PersistentWindowCallback callbackWrapper = new PersistentWindowCallback(window, originalCallback);
    window.setCallback(callbackWrapper);
}

为什么这个方案有效?

  • 非频繁检测:只有当有事件触发(触摸、按键等)时才会检查Callback状态,不会做无意义的轮询或全局布局监听,完全符合你的需求。
  • 自动恢复:一旦Toolbar替换了Callback,第一次事件触发时就会立刻把我们的Callback重新设置回去,之后所有事件都会先经过我们的拦截逻辑,再转发给Toolbar的Callback。
  • 无基类依赖:不需要继承自定义Activity,直接在任意Activity中初始化即可,灵活性拉满。
  • 避免内存泄漏:用WeakReference持有Window,不会因为Window的生命周期导致内存泄漏问题。

备选方案(不推荐,仅作参考)

如果你能接受反射,可以通过监听Window的mCallback字段变化来触发恢复逻辑,但反射存在兼容性风险(不同Android版本可能字段名或访问权限变化),所以上面的方案是更稳妥的选择。

内容的提问来源于stack exchange,提问作者ZolkiBy

火山引擎 最新活动