TouchEvent事件传递机制

来源:互联网 发布:百度源码 编辑:程序博客网 时间:2024/06/07 04:04

涉及的知识点:

主要涉及的方法:

方法用途public boolean dispatchTouchEvent(MotionEvent ev)用来分发TouchEventpublic boolean onInterceptTouchEvent(MotionEvent ev)用来拦截TouchEventpublic boolean onTouchEvent(MotionEvent ev)用来处理TouchEvent

拥有这三个方法的类:

类型相关类方法Activity类ActivitydispatchTouchEvent(); onTouchEvent();View容器(ViewGroup的子类)FrameLayout、LinearLayou、ListView、ScrollVIew…dispatchTouchEvent(); onInterceptTouchEvent(); onTouchEvent();View控件(非ViewGroup子类)Button、TextView、EditText……dispatchTouchEvent(); onTouchEvent();

ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent事件

三个方法的具体作用:

方法用途dispatchTouchEvent()用来分派事件。其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法onInterceptTouchEvent()用来拦截事件。ViewGroup默认为(return false)表示不对事件进行拦截,事件将向下传递(传递给其子View);若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递,则事件将由当前ViewGroup类(onTouchEvent)来处理onTouchEvent()用来处理事件。返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View);返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理

三个方法并不总是同时存在的

无拦截的touch事件处理过程

流程图

(视图嵌套关系 Activity–>out–>middle–>center,点击center) 


分析

分发过程中,所有组件都不会处理事件,且事件并不会被center的onTouchEvent方法“消费”(ruturn false),则事件会层层逆向传递回到Activity。对于后续的move、up事件,由于第一个down事件已经确定由Activity处理事件,故up事件由Activity的dispatchTouchEvent直接分发给自己的onTouchEvent方法处理。 
即,onInterceptTouchEvent()来决定是否将事件传递给子View处理,onTouchEvent()用来对具体事件的处理(对一些功能的实现)并且决定了是否将处理事件的能力返回给父View.

实例分析

一次点击(抬起)操作,当前的activity分别会响应一次ActionDown,n次ActionMove(n>=0),1次ActionUp事件

Demo示图

例. 视图嵌套 Activity–>ViewA–>ViewB–>ViewC(其中ViewA、ViewB继承至FrameLayout,ViewC继承自View) 


情况一(点击ViewC,过程中无任何拦截)

(注:logout中的Main表示Activity)

情况二(点击ViewC,过程中存在拦截操作)

点击ViewC, 只在ViewA中对事件下发过程进行拦截onInterceptTouchEvent{…..return true;},并且ViewA中的onTouchEvent不处理事件(return false),事件回传 


情况三(点击ViewB, 对事件回传过程进行截获)

只在ViewA中对事件回传过程进行截获onTouchEvent{…..return true;}


(ActionDown结束后,后续的事件(Move、Up)传递过程中,只有被确定处理该touch事件的view才进行onTouchEvent方法的响应) 
说明:一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。

情况四(点击ViewC, 对事件分发过程进行拦截):

在ViewB中对事件分发过程进行拦截dispatchTouchEvent{…..return true;}




////////////////////////////华丽的分割线//////////////////////////////////

以下是补充:

MotionEvent:

一、一些常量

常见的动作常量:

    public static final int ACTION_DOWN             = 0;单点触摸动作

    public static final int ACTION_UP               = 1;单点触摸离开动作
    public static final int ACTION_MOVE             = 2;触摸点移动动作
    public static final int ACTION_CANCEL           = 3;触摸动作取消
     public static final int ACTION_OUTSIDE          = 4;触摸动作超出边界
    public static final int ACTION_POINTER_DOWN     = 5;多点触摸动作
    public static final int ACTION_POINTER_UP       = 6;多点离开动作
   以下是一些非touch事件
    public static final int ACTION_HOVER_MOVE       = 7;
    public static final int ACTION_SCROLL           = 8;
    public static final int ACTION_HOVER_ENTER      = 9;
    public static final int ACTION_HOVER_EXIT       = 10;

掩码常量

ACTION_MASK = 0X000000ff动作掩码
 ACTION_POINTER_INDEX_MASK = 0X0000ff00触摸点索引掩码

ACTION_POINTER_INDEX_SHIFT = 8 获取触摸点索引需要移动的位数


二、相关方法

getAction()方法返回的是int类型,用到的只有低16位,其中:低八位是动作的类型,高8位是触摸点索引值的表示(单点为0,双点为1)


获得动作类型: int action = event.getAction() & ACTION_MASK 或者使用 getActionMasked()


获得触摸点索引类型: int pointerIndex = (event.getAction() & ACTION_POINTER_INDEX_MASK ) >> ACTION_POINTER_INDEX_SHIFT 


或者使用 getActionIndex()


为什么要有索引信息?

有了索引信息,我们可以在onTOuchEvent事件中判断传进来的MotionEvent对象对应的是单点信息还是多点信息。


下面的代码段能使用户在屏幕上拖动一个对象。它记录了初始点的位置,计算点移动的距离,并将对象移动到新的位置。它正确的处理了这种情况:当第一个手指把控件拖到一个位置,然后按下第二个手指,且第二个手指与同一个控件上。当用户抬起第一个手指时,控件不会跑到第二个手指的位置同时第二个手指可以继续拖动控件。

  1. // The ‘active pointer’ is the one currently moving our object.  
  2. private int mActivePointerId = INVALID_POINTER_ID;  
  3.   
  4. @Override  
  5. public boolean onTouchEvent(MotionEvent ev) {  
  6.     // Let the ScaleGestureDetector inspect all events.  
  7.     mScaleDetector.onTouchEvent(ev);  
  8.                
  9.     final int action = MotionEventCompat.getActionMasked(ev);   
  10.           
  11.     switch (action) {   
  12.     case MotionEvent.ACTION_DOWN: {  
  13.         final int pointerIndex = MotionEventCompat.getActionIndex(ev);   
  14.         final float x = MotionEventCompat.getX(ev, pointerIndex);   
  15.         final float y = MotionEventCompat.getY(ev, pointerIndex);   
  16.               
  17.         // Remember where we started (for dragging)  
  18.         mLastTouchX = x;  
  19.         mLastTouchY = y;  
  20.         // Save the ID of this pointer (for dragging)  
  21.         mActivePointerId = MotionEventCompat.getPointerId(ev, 0);  
  22.         break;  
  23.     }  
  24.               
  25.     case MotionEvent.ACTION_MOVE: {  
  26.         // Find the index of the active pointer and fetch its position  
  27.         final int pointerIndex =   
  28.                 MotionEventCompat.findPointerIndex(ev, mActivePointerId);    
  29.               
  30.         final float x = MotionEventCompat.getX(ev, pointerIndex);  
  31.         final float y = MotionEventCompat.getY(ev, pointerIndex);  
  32.               
  33.         // Only move if the ScaleGestureDetector isn't processing a gesture.  
  34.         if (!mScaleDetector.isInProgress()) {  
  35.             // Calculate the distance moved  
  36.             final float dx = x - mLastTouchX;  
  37.             final float dy = y - mLastTouchY;  
  38.   
  39.             mPosX += dx;  
  40.             mPosY += dy;  
  41.   
  42.             invalidate();  
  43.         }  
  44.         // Remember this touch position for the next move event  
  45.         mLastTouchX = x;  
  46.         mLastTouchY = y;  
  47.   
  48.         break;  
  49.     }  
  50.               
  51.     case MotionEvent.ACTION_UP: {  
  52.         mActivePointerId = INVALID_POINTER_ID;  
  53.         break;  
  54.     }  
  55.               
  56.     case MotionEvent.ACTION_CANCEL: {  
  57.         mActivePointerId = INVALID_POINTER_ID;  
  58.         break;  
  59.     }  
  60.           
  61.     case MotionEvent.ACTION_POINTER_UP: {  
  62.               
  63.         final int pointerIndex = MotionEventCompat.getActionIndex(ev);   
  64.         final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);   
  65.   
  66.         if (pointerId == mActivePointerId) {  
  67.             // This was our active pointer going up. Choose a new  
  68.             // active pointer and adjust accordingly.  
  69.             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;  
  70.             mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex);   
  71.             mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex);   
  72.             mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);  
  73.         }  
  74.         break;  
  75.     }  
  76.     }         
  77.     return true;  
  78. }  

MotionEvent还包含了移动操作中其它历史移动数据以方便处理触控的移动操作.

android sdk对于这个类的描述中就有这么一句:

For efficiency, motion events with ACTION_MOVE may batch together multiple movement samples within a single object.