Android 事件拦截机制

1 概述

由于android系统是的控件是基于View和ViewGroup的树形结构,其控件嵌套后使用,故Activity,ViewGroup和View之间存在事件传递的问题。

事件传递涉及到三个方法:

  1. dispatchTouchEvent 事件的分发,通常是由上层(viewgroup)向下层(view)分发
  2. onInterceptTouchEvent 事件的拦截,阻止事件继续分发。此方法view是没有的,因为view已经是事件分发底层控件,故不需要拦截。
  3. onTouchEvent 事件的处理,通过是由下层向上层递归。

2 事件传递例子

这里我们涉及到activity、viewgroup和view三部分,首先我们自定义一个ViewGroup,简单的继承LinearLayout就好,重写其三个事件传递相关方法:

public class MyViewGroup extends LinearLayout {
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "MyLayout onTouchEvent " + Utils.getTouchAction(event));
        return super.onTouchEvent(event);
    }

    @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("event", "MyLayout dispatchTouchEvent " + Utils.getTouchAction(event));
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        Log.d("event", "MyLayout onInterceptTouchEvent " + Utils.getTouchAction(event));
        return super.onInterceptTouchEvent(event);
    }
}

这里,Utils.getTouchAction 是用于获取点击事件的分类,如Down/Up。然后再自定义View,这里简单的继承TextView,重写两个事件传递相关方法,(view没有onInterceptTouchEvent方法)

public class MyView extends TextView{
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "MyView onTouchEvent " + Utils.getTouchAction(event));
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("event", "MyView dispatchTouchEvent " + Utils.getTouchAction(event));
        return super.dispatchTouchEvent(event);
    }
}

最终,在activity中将以上两个控件使用起来:

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyViewGroup viewGroup = new MyViewGroup(this);
        MyView view = new MyView(this);
        viewGroup.addView(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        setContentView(viewGroup);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "MyActivity onTouchEvent " + Utils.getTouchAction(event));
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("event", "MyActivity dispatchTouchEvent " + Utils.getTouchAction(event));
        return super.dispatchTouchEvent(event);
    }
}

点击后的日志如下:

Android 事件拦截机制_第1张图片

如上图,可知Touch的Down事件传递流程:

  1. 最先由activity拿到,经有activity的dispatchTouchEvent往下传递
  2. 接着收到该事件的是ViewGroup,同样ViewGroup的dispatchTouchEvent也往下传递
  3. ViewGroup往下传递的过程中,会调用到其onInterceptTouchEvent事件。若onInterceptTouchEvent返回false(默认返回false),则继续向view传递。
  4. 接着收到此事件的是View的dispatchTouchEvent,由于view已经是最底层的接收者,故这里不再往下传递
  5. 接着调用了View的onTouchEvent事件,由于view里面的onTouchEvent。
  6. 下面就是反过来往上传递给ViewGroup的onTouchEvent。
  7. 最终回到activity的OnTouchEvent,由于都没有处理,此Down事件就此结束。

接着手指拿起,开始响应Up事件

  1. UP事件同Down事件一样,首先由activity拿到,然后activity的dispatchTouchEvent分发。
  2. 由于之前的Down事件View、ViewGroup都没有处理(使用默认的返回false),故此时Up事件(其实还包括Moving事件等,下同)会直接跳过View和ViewGroup,直接传递给activity的OnTouchEvent。

3 事件传递机制分析和截断

在楼上的例子中,我们看了正常的一次事件传递流程,接下来我们就要考虑几个问题了,dispatchTouchEvent这个方法是用来分发事件的,必不可少;但是onInterceptTouchEvent用来干嘛的呢?我们将上面的例子改一下。

修改MyViewGroup文件的onInterceptTouchEvent,如下:

public boolean onInterceptTouchEvent(MotionEvent event) {
    Log.e("event", "MyLayout onInterceptTouchEvent " + Utils.getTouchAction(event));
    //这里注释掉默认的回调(默认的是false)
    //return super.onInterceptTouchEvent(event);
    return true;  
}

然后我们再看看运行的结果:

Android 事件拦截机制_第2张图片

这里我们看到,ViewGroup的onInterceptTouchEvent事件返回true后,此事件就没有再往下传递给View了,包括响应的Up事件,都在该ViewGroup层停止了往下的传递。也就是说onInterceptTouchEvent事件阻止了onInterceptTouchEvent把事件继续往下传输,而是直接调用OnTouchEvent处理事件

同样,由于之前的Down事件View、ViewGroup都没有处理(使用默认的返回false),故Up事件会直接跳过View和ViewGroup,直接传递给activity的OnTouchEvent。

了解了onInterceptTouchEvent是用来截断事件传递的,我们在看看onTouchEvent,此方法是用于处理
事件的。事件的处理流程一般是由底层向上层传递的。我们看看onTouchEvent事件能否截断呢?

恢复ViewGroup的修改,然后修改MyView的OnTouchEvent方法,如下:

public boolean onTouchEvent(MotionEvent event) {
    Log.e("event", "MyView onTouchEvent " + Utils.getTouchAction(event));
    return true;
}

可以看到运行后的输出如下:

Android 事件拦截机制_第3张图片

这里可以看到两点区别:

  1. 由于View处理了OnTouchEvent事件,故Up事件也会再次传递下来。
  2. 同样,由于View处理了OnTouchEvent事件且返回了true,表示消耗了此事件,则不会再往上层的OnTouchEvent传递。

4 总结

  1. 事件的分发是由上层往下层,通过dispatchTouchEvent方法分发,最先接收到事件的是activity,然后向ViewGroup分发,ViewGroup再向View分发。
  2. 事件的分发过程可以使用onInterceptTouchEvent中断,onInterceptTouchEvent返回true后,此事件将不再往下分发。
  3. 事件的处理流程和事件的分发是相反的,事件分发到最底层(View层)或者onInterceptTouchEvent截断层后,不再往下分发,而是调用该层的onTouchEvent方法,然后递归向上层回调onTouchEvent方法,知道activity。
  4. 某一层的onTouchEvent事件返回true的时候,表示消耗了此事件,则不再往上层回调其他onTouchEvent,此事件结束。
  5. 一个点击流程包括了Down Moving Up三类主要事件,在事件传递机制中,若某一底层未处理Down事件,则其后续的Moving Up事件不会再传递到该层。

你可能感兴趣的:(android,事件传递)