《Android群英传》笔记7——事件拦截机制分析

      

事件拦截机制分析

本文是读了《 Android  群英传》第三章--Android体控件架构与自定义空间详解--之后的读书笔记,感谢作者,在此特别推荐此书。

      我们平时点击屏幕时候,屏幕会根据我们不同的操作,比如单点、滑动、双击等做出不同的响应事件,这个过程我们称之为屏幕触摸事件。Android为这些触摸事件封装了一个类——MotionEvent,如果重写onTouchEvent()方法,就会发现该方法里就是用的MotionEvent这个参数。我们可以看官方里是这样介绍这个类的:

* Object used to report movement (mouse, pen, finger, trackball) events.
* Motion events may hold either absolute or relative movements and other data,
* depending on the type of device.
      也就是说这个类主要用于反应出各种和触摸事件相关的内容,比如可以通过Motion Event.getX()或者MotionEvent.getRawX()方法取出横向的坐标点,可以根据MotionEvent.getAction()获取的值判断出是当前是触发了哪种触摸事件,然后写出对应的监听事件。

      但是我们只有一个屏幕,而在Android中View是树形结构,屏幕中的一个位置可能对应多层View和ViewGroup,那么这个位置触发的触摸事件由哪层View或者ViewGroup来响应呢?此时我们就需要进行事件拦截。

      首先我们先了解下Android处理点击事件的三个方法:


      l dispatchTouchEvent(分发点击事件)

      l onInterceptTouchEvent(拦截点击事件)

      l onTouchEvent(处理点击事件)

      

      因为点击事件的入口都是Acitivity,由它将点击事件传递给指定的View或者ViewGroup来响应,中间的流程可以用下面这个图来表示(感谢网友flueky的专栏整理的图):


      由三色线分别表示返回值为true,false或者super时候点击事件的传递顺序。如果没有看懂这个图,那也没关系,我们可以写个demo来进行说明。

      首先我们写了两个自定义ViewGroup和一个自定义View,将他们的这三个点击事件都输出Log日志,其中View里没有onInterceptTouchEvent,其实想想也是合理的,比较View里不会含有子View了,无需进行拦截。如下所示:

public class ViewGroup_A extends RelativeLayout {

    private static String tag = "ViewGroup_A";

    public ViewGroup_A(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public ViewGroup_A(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e(tag,"--dispatchTouchEvent--A");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        Log.e(tag,"--onInterceptTouchEvent--A");
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(tag, "--onTouchEvent--A");
        return super.onTouchEvent(event);
    }

}
      B和A一样,然后是C:

public class View_C extends View {
    private static String tag = "View_C";

    public View_C(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public View_C(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e(tag,"--dispatchTouchEvent--C");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(tag, "--onTouchEvent--C");
        return super.onTouchEvent(event);
    }
}
      布局文件如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.diit.blocktest.MainActivity">
    <com.diit.blocktest.ViewGroup_A
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.diit.blocktest.ViewGroup_B
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <com.diit.blocktest.View_C
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </com.diit.blocktest.ViewGroup_B>

    </com.diit.blocktest.ViewGroup_A>
</RelativeLayout>
      这样就形成了三个嵌套的布局。这时候我们点击下屏幕看看日志为:


06-05 22:03:00.880 21802-21802/com.diit.blocktest E/ViewGroup_A: --dispatchTouchEvent--A
06-05 22:03:00.881 21802-21802/com.diit.blocktest E/ViewGroup_A: --onInterceptTouchEvent--A
06-05 22:03:00.881 21802-21802/com.diit.blocktest E/ViewGroup_B: --dispatchTouchEvent--B
06-05 22:03:00.881 21802-21802/com.diit.blocktest E/ViewGroup_B: --onInterceptTouchEvent--B
06-05 22:03:00.881 21802-21802/com.diit.blocktest E/View_C: --dispatchTouchEvent--C
06-05 22:03:00.881 21802-21802/com.diit.blocktest E/View_C: --onTouchEvent--C
06-05 22:03:00.881 21802-21802/com.diit.blocktest E/ViewGroup_B: --onTouchEvent--B
06-05 22:03:00.881 21802-21802/com.diit.blocktest E/ViewGroup_A: --onTouchEvent--A

      

      也就是整个流程中的,没有进行任何拦截事件,点击事件从A传到B再传到C,响应事件的顺序从C到B再进行A,按照这样的顺序进行。      

      在默认情况下,每个事件return的值是false,也就是不做任何拦截,会在响应后继续向下一步走。

      此时我们修改下ViewGroup_A里的值,将onInterceptTouchEvent()的返回值改为true,看看情况如何呢?此时运行下,得到的日志为:


06-05 22:17:11.249 7276-7276/com.diit.blocktest E/ViewGroup_A: --dispatchTouchEvent--A
06-05 22:17:11.250 7276-7276/com.diit.blocktest E/ViewGroup_A: --onInterceptTouchEvent--A
06-05 22:17:11.250 7276-7276/com.diit.blocktest E/ViewGroup_A: --onTouchEvent--A


      我们可以看到只是在A里进行了点击分发、拦截和响应事件,也就是说在A的onInterceptTouchEvent()方法后就被拦截了,不会再向A的子View传下去,在A里就对这个点击事件进行了处理。      

      我们继续做个试验,将A的onInterceptTouchEvent()方法返回值改回来,然后将C的onTouchEvent()方法的返回值改为true,此时得到的日志为:


06-05 22:26:07.922 16163-16163/com.diit.blocktest E/ViewGroup_A: --dispatchTouchEvent--A
06-05 22:26:07.922 16163-16163/com.diit.blocktest E/ViewGroup_A: --onInterceptTouchEvent--A
06-05 22:26:07.922 16163-16163/com.diit.blocktest E/ViewGroup_B: --dispatchTouchEvent--B
06-05 22:26:07.922 16163-16163/com.diit.blocktest E/ViewGroup_B: --onInterceptTouchEvent--B
06-05 22:26:07.922 16163-16163/com.diit.blocktest E/View_C: --dispatchTouchEvent--C
06-05 22:26:07.923 16163-16163/com.diit.blocktest E/View_C: --onTouchEvent--C


      此时我们可以发现点击事件从A传到C以后,C进行了响应事件,但在响应后,并没有在它的上级再进行响应。

      基于上面的试验,我们可以得到这个结论:


1. 正常情况下,屏幕的点击事件是由父View向子View一级级传递下去,到了最上层的View开始执行响应事件,而响应事件是由子View向父View一级级传递回去;

2. onInterceptTouchEvent()方法主要用来拦截点击事件向该ViewGroup的子View里进行传递,当返回值为true时候,点击事件在该ViewGroup里被拦截,然后开始执行该ViewGroup的响应事件,并一级级向上传回去;

3. onTouchEvent()方法里的返回值主要是用来拦截响应事件,当返回值为true时候,响应事件在该View或者ViewGroup里被拦截,不会再向其父View传递响应事件。


      由流程图为:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值