Android程序猿移动开发首页投稿(暂停使用,暂停投稿)

浅析Android事件分发机制

2016-07-18  本文已影响403人  iflymoon

事件介绍

在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件。
Android中与Touch事件相关的方法包括:

能够包含这些方法的控件包括:ViewGroup、View、Activity。下图列出了这3种控件和上述3个Touch事件的对应关系,Yes代表改控件有这个事件,反之No则代表没有。

Touch事件相关方法 方法功能 ViewGroup View Activity
dispatchTouchEvent(MotionEvent ev) 事件分发 Yes Yes Yes
onInterceptTouchEvent(MotionEvent ev) 事件拦截 Yes No No
onTouchEvent(MotionEvent ev) 事件响应 Yes Yes Yes

从这张图中我们可以看到ViewGroup有与Touch事件相关的三个事件,而Activity没有onInterceptTouchEvent事件。另外需要注意的是这里的View是最小单位(如TextView),也就是无法向这个最小View中添加子View,它没有onInterceptTouchEvent事件。

事件分析

为了方便理解,我们先看一张我从网上找的一个Android事件分发流程图:



下面我来对这个流程图进行解读

案例测试

下面为了测试一下事件分发的先后顺序,我写了一个Demo进行测试,自定义一个ViewGroup重写它的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,文件MyLinearLayout.java代码如下:

public class MyLinearLayout extends LinearLayout{    
  public MyLinearLayout(Context context) {        
    super(context);    
  }    
  public MyLinearLayout(Context context, AttributeSet attrs) {        
    super(context, attrs);    
  }    
  public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {        
    super(context, attrs, defStyleAttr);    
  }    
  @Override    
  public boolean dispatchTouchEvent(MotionEvent ev) {        
    Log.i(MainActivity.TAG, "MyLinearLayout-->dispatchTouchEvent-->" + TouchEventUtil.getTouchAction(ev.getAction()));        
    return super.dispatchTouchEvent(ev);    
  }    
  @Override    
  public boolean onInterceptTouchEvent(MotionEvent ev) {        
    Log.i(MainActivity.TAG, "MyLinearLayout-->onInterceptTouchEvent-->" + TouchEventUtil.getTouchAction(ev.getAction()));        
    return super.onInterceptTouchEvent(ev);    
  }    
  @Override    
  public boolean onTouchEvent(MotionEvent event) {        
    Log.i(MainActivity.TAG, "MyLinearLayout-->onTouchEvent-->" + TouchEventUtil.getTouchAction(event.getAction()));        
    return super.onTouchEvent(event);    
  }
}

自定义一个View重写它的dispatchTouchEvent、onTouchEvent,文件MyTextView.java代码如下:

public class MyTextView extends TextView{    
  public MyTextView(Context context) {        
    super(context);    
  }    
  public MyTextView(Context context, AttributeSet attrs) {        
    super(context, attrs);    
  }    
  public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {        
    super(context, attrs, defStyleAttr);    
  }    
  @Override    
  public boolean onTouchEvent(MotionEvent event) {        
    Log.i(MainActivity.TAG, "MyTextView-->onTouchEvent-->" + TouchEventUtil.getTouchAction(event.getAction()));        
    return super.onTouchEvent(event);    
  }    
  @Override    
  public boolean dispatchTouchEvent(MotionEvent event) {        
    Log.i(MainActivity.TAG, "MyTextView-->dispatchTouchEvent-->" + TouchEventUtil.getTouchAction(event.getAction()));        
    return super.dispatchTouchEvent(event);    
  }
}

MainActivity.java中重写dispatchTouchEvent、onTouchEvent,代码如下:

public class MainActivity extends Activity {    
  public static final String TAG = "EventTest";    
  @Override    
  protected void onCreate(Bundle savedInstanceState) {        
    super.onCreate(savedInstanceState);        
    setContentView(R.layout.activity_main);    
  }    
  @Override    
  public boolean onTouchEvent(MotionEvent event) {        
    Log.i(TAG, "MainActivity-->onTouchEvent-->" + TouchEventUtil.getTouchAction(event.getAction()));        
    return super.onTouchEvent(event);    
  }    
  @Override    
  public boolean dispatchTouchEvent(MotionEvent ev) {        
    Log.i(TAG, "MainActivity-->dispatchTouchEvent-->" + TouchEventUtil.getTouchAction(ev.getAction()));        
    return super.dispatchTouchEvent(ev);    
  }
}

布局文件activity_main.xml如下:

<?xml version="1.0" encoding="utf-8"?>         
<me.my.touchevent.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"    
  android:layout_height="match_parent"    
  android:gravity="center">    
  <me.my.touchevent.MyTextView        
    android:layout_width="wrap_content"        
    android:layout_height="wrap_content"        
    android:text="点击我">    
  </me.my.touchevent.MyTextView>
</me.my.touchevent.MyLinearLayout>

其中有个工具类TouchEventUtil代码如下:

public class TouchEventUtil {    
  public static String getTouchAction(int action){        
    switch (action){            
      case MotionEvent.ACTION_DOWN:                
        return "ACTION_DOWN";            
      case MotionEvent.ACTION_MOVE:                
        return "ACTION_MOVE";            
      case MotionEvent.ACTION_UP:                
        return "ACTION_UP";        
    }        
    return "";    
  }
}

上述代码都比较简单,我就不做过多解释了,下面我们来看不同情况下的执行结果:

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)

运行结果如下:



结果分析:
代码运行后,事件首先由MainActivity的dispatchTouchEvent方法分发给MyLinearLayout控件的dispatchTouchEvent,而该控件的dispatchTouchEvent返回super,表示将事件交给MyLinearLayout的onInterceptTouchEvent进行处理,此时该控件的onInterceptTouchEvent返回super,表示不拦截该事件,所以事件传递给MyTextView的dispatchTouchEvent,此时改控件返回super,表示将事件交给MyTextView的onTouchEvent进行处理,由于3个控件的onTouchEvent都是返回super,所以依次由MyTextView的onTouchEvent向上传递。

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout super.dispatchTouchEvent(ev) true super.onTouchEvent(ev)
MyTextView super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)

运行结果如下:



结果分析:
代码运行后,事件首先由MainActivity的dispatchTouchEvent方法分发给MyLinearLayout控件的dispatchTouchEvent,而该控件的dispatchTouchEvent返回super,表示将事件交给MyLinearLayout的onInterceptTouchEvent进行处理,此时该控件的onInterceptTouchEvent返回true,表示拦截该事件,所以事件不会再向下传递,而是有该控件的onTouchEvent进行处理,由于MyLinearLayout的onTouchEvent返回super,所以事件继续向上传递给MainActivity的onTouchEvent。

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout true super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)

运行结果如下:



结果分析:
代码运行后,事件首先由MainActivity的dispatchTouchEvent方法分发给MyLinearLayout控件的dispatchTouchEvent,而该控件的dispatchTouchEvent返回true,表示将事件消费,所以事件在此则停止传递。

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView false --- super.onTouchEvent(ev)

运行结果如下:



结果分析:
代码运行后,事件首先由MainActivity的dispatchTouchEvent方法分发给MyLinearLayout控件的dispatchTouchEvent,而该控件的dispatchTouchEvent返回super,表示将事件交给MyLinearLayout的onInterceptTouchEvent进行处理,此时该控件的onInterceptTouchEvent返回super,表示不拦截该事件,所以事件传递给MyTextView的dispatchTouchEvent,此时改控件返回false,表示将事件传递给父控件的onTouchEvent进行处理,此时MyLinearLayout的onTouchEvent返回super,所以事件继续向上传递给MainActivity的onTouchEvent。

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView super.dispatchTouchEvent(ev) --- true

运行结果如下:



结果分析:
代码运行后,事件首先由MainActivity的dispatchTouchEvent方法分发给MyLinearLayout控件的dispatchTouchEvent,而该控件的dispatchTouchEvent返回super,表示将事件交给MyLinearLayout的onInterceptTouchEvent进行处理,此时该控件的onInterceptTouchEvent返回super,表示不拦截该事件,所以事件传递给MyTextView的dispatchTouchEvent,此时改控件返回super,表示将事件传递给改控件的onTouchEvent进行处理,此时MyTextView的onTouchEvent返回true,表示将事件进行消费,所以事件到此停止传递。
以上的返回值组合有很多,在此就不一一列举了,小伙伴们也可以自己试一试的哈。

关于ACTION_MOVE和ACTION_UP的介绍

上面讲解的都是针对ACTION_DOWN的事件传递,ACTION_MOVE和ACTION_UP在传递的过程中并不是和ACTION_DOWN一样,下面我来分析一下ACTION_MOVE和ACTION_UP在传递的过程中的规律。我们依旧使用上面的测试案例找寻规律,这次我们在点击TextView时进行少量滑动:

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout true super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)

为了方便观看,运行结果我绘制成ACTION_MOVE和ACTION_UP的事件流向图,下面的测试均是如此:


控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView true --- super.onTouchEvent(ev)

结果流向图:


控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView super.dispatchTouchEvent(ev) --- true

结果流向图:


控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
MyLinearLayout super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) true
MyTextView super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)

结果流向图:


控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
MainActivity super.dispatchTouchEvent(ev) --- true
MyLinearLayout super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
MyTextView super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)

结果流向图:



由此我们已经可以看出规律了:
ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。
好了,到此我关于Android事件分发机制的讲解就告一段落了,后面也许还会有补充,不足之处望大家指正,共同学习交流!

上一篇 下一篇

猜你喜欢

热点阅读