在不覆盖 ViewGroup 或 View 方法的情况下拦截 click/touch 事件

Intercept click/touch event without overriding ViewGroup or View methods

有什么方法可以 intercept/decorate 查看触摸事件而不扩展 View 或包装在某些 ViewGroup 中(可以拦截子事件)?

假设我有 ExpandableListView 来处理项目点击事件。如果我在适配器 OnClickListenerOnTouchListener 中设置适配器返回的膨胀项目视图, ExpandableListView 不会执行相应的操作(组扩展),因为项目的侦听器使用了事件。

我不想使用ExpandableListView#setOnItemClickListener的原因是,我想在不使用ExpandableListView依赖的情况下装饰适配器中的点击事件。

我找到了解决这个问题的有效方法。

解决方案:在 OnTouchListener 中收集事件克隆,然后将它们分派到父视图。

private final Queue<MotionEntry> consumedEvents = new LinkedList<>();
private final AtomicBoolean isDispatching = new AtomicBoolean(false);
...
    groupView.setOnTouchListener(new OnTouchListener() {
        @Override 
        public boolean onTouch(View v, MotionEvent e) {
            // we don't want to handle re-dispatched event...
            if (isDispatching.get()) {
                return false; 
            }
            // create clone as event might be changed by parent
            MotionEvent clone = MotionEvent.obtain(e);
            MotionEntry entry = new MotionEntry(v, clone);
            consumedEvents.add(entry);

            // consume ACTION_DOWN in order to receive subsequent motion events 
            // like ACTION_MOVE, ACTION_CANCEL/ACTION_UP...
            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                return true;
            }
            // we do not want to handle canceled motion...
            if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
                consumedEvents.clear();
                return false;
            }
            // at this moment we have intercepted whole motion 
            // = re-dispatch to parent in order to apply default handling...
            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                dispatchEvents();
            }
            return true;
        }
    });
...

派送方式:

private void dispatchEvents() {
    isDispatching.set(true);
    while (!consumedEvents.isEmpty()) {
        MotionEntry entry = consumedEvents.poll();

        ViewGroup parent = (ViewGroup) entry.view.getParent();
        if (parent == null || entry.view.getVisibility() != View.VISIBLE) {
            continue; // skip dispatching to detached/invisible view
        }
        // make position relative to parent...
        entry.event.offsetLocation(entry.view.getLeft(), entry.view.getTop());
        entry.event.setSource(PARENT_DISPATCHER);
        parent.dispatchTouchEvent(entry.event);

        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
            clickListener.onClick(entry.view);
        }
    }
    isDispatching.set(false);
}

帮手class

private class MotionEntry {
    private final View view;
    private final MotionEvent event;

    public MotionEntry(View view, MotionEvent event) {
        this.view = view;
        this.event = event;
    }
}