织梦CMS - 轻松建站从此开始!

罗索实验室

当前位置: 主页 > 嵌入式开发 > Android >

Android Touch事件分发过程

落鹤生 发布于 2014-09-10 14:12 点击:次 
用户触摸屏幕产生一个触摸消息,系统底层将该消息转发给ViewRoot ( ViewRootImpl ),ViewRoot产生一个DISPATCHE_POINTER的消息,并且在handleMessage中处理该消息,最终会通过 deliverPointerEvent(MotionEvent event)来处理该消息。在该函数中会调用mView.dispatchTouc
TAG: 触摸  手势  OnTouch  

尽管网络上已经有很多关于这个话题的优秀文章了,但还是写了这篇文章,主要还是为了加强自己的记忆吧,自己过一遍总比看别人的分析要深刻得多,那就走起吧。

简单示例

     先看一个示例 :

 

布局文件 :

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  2.     xmlns:tools="http://schemas.android.com/tools" 
  3.     android:id="@+id/container" 
  4.     android:layout_width="match_parent" 
  5.     android:layout_height="match_parent" 
  6.     android:layout_gravity="center" 
  7.     tools:context="com.example.touch_event.MainActivity" 
  8.     tools:ignore="MergeRootFrame" > 
  9.  
  10.     <Button 
  11.         android:id="@+id/my_button" 
  12.         android:layout_width="match_parent" 
  13.         android:layout_height="wrap_content" 
  14.         android:text="@string/hello_world" /> 
  15.  
  16. </FrameLayout> 

MainActivity文件:

  1. public class MainActivity extends Activity { 
  2.  
  3.     @Override 
  4.     protected void onCreate(Bundle savedInstanceState) { 
  5.         super.onCreate(savedInstanceState); 
  6.         setContentView(R.layout.activity_main); 
  7.  
  8.         Button mBtn = (Button) findViewById(R.id.my_button); 
  9.         mBtn.setOnTouchListener(new OnTouchListener() { 
  10.  
  11.             @Override 
  12.             public boolean onTouch(View v, MotionEvent event) { 
  13.                 Log.d("""### onTouch : " + event.getAction()); 
  14.                 return false
  15.             } 
  16.         }); 
  17.         mBtn.setOnClickListener(new OnClickListener() { 
  18.  
  19.             @Override 
  20.             public void onClick(View v) { 
  21.                 Log.d("""### onClick : " + v); 
  22.             } 
  23.         }); 
  24.  
  25.     } 
  26.  
  27.     @Override 
  28.     public boolean dispatchTouchEvent(MotionEvent ev) { 
  29.         Log.d("""### activity dispatchTouchEvent"); 
  30.         return super.dispatchTouchEvent(ev); 
  31.     } 

当用户点击按钮时会输出如下Log,

08-31 03:03:56.116: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.116: D/(1560): ### onTouch : 0
08-31 03:03:56.196: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.196: D/(1560): ### onTouch : 1
08-31 03:03:56.196: D/(1560): ### onClick : android.widget.Button{52860d98 VFED..C. ...PH... 0,0-1080,144 #7f05003d app:id/my_button}
 

我们可以看到首先执行了Activity中的dispatchTouchEvent方法,然后执行了onTouch方法,然后再是 dispatchTouchEvent --> onTouch, 最后才是执行按钮的点击事件。这里我们可能有个疑问,为什么dispatchTouchEvent和onTouch都执行了两次,而onClick才执行 了一次 ? 为什么两次的Touch事件的action不一样,action 0 和 action 1到底代表了什么 ? 

覆写过onTouchEvent的朋友知道,一般来说我们在该方法体内都会处理集中touch类型的事件,有ACTION_DOWN、 ACTION_MOVE、ACTION_UP等,不过上面我们的例子中并没有移动,只是单纯的按下、抬起。因此,我们的触摸事件也只有按下、抬起,因此有 2次touch事件,而action分别为0和1。我们看看MotionEvent中的一些变量定义吧:

  1. public final class MotionEvent extends InputEvent implements Parcelable { 
  2.     // 代码省略 
  3.     public static final int ACTION_DOWN             = 0;    // 按下事件 
  4.     public static final int ACTION_UP               = 1;    // 抬起事件 
  5.     public static final int ACTION_MOVE             = 2;    // 手势移动事件 
  6.     public static final int ACTION_CANCEL           = 3;    // 取消 
  7.   // 代码省略 

可以看到,代表按下的事件为0,抬起事件为1,也证实了我们上面所说的。

在看另外两个场景:

1、我们点击按钮外的区域,输出Log如下 :

08-31 03:04:45.408: D/(1560): ### activity dispatchTouchEvent08-31
03:04:45.512: D/(1560): ### activity dispatchTouchEvent

2、我们在onTouch函数中返回true, 输出Log如下 :

08-31 03:06:04.764: D/(1612): ### activity dispatchTouchEvent
08-31 03:06:04.764: D/(1612): ### onTouch : 0
08-31 03:06:04.868: D/(1612): ### activity dispatchTouchEvent
08-31 03:06:04.868: D/(1612): ### onTouch : 1

以上两个场景为什么会这样呢 ?   我们继续往下看吧。
 

Android Touch事件分发

    那么整个事件分发的流程是怎样的呢 ?  

    简单来说就是用户触摸手机屏幕会产生一个触摸消息,最终这个触摸消息会被传送到ViewRoot ( 看4.2的源码时这个类改成了ViewRootImpl )的InputHandler,ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁,根据ViewRoot的定义,发现它并不是一个View类型,而是一个Handler。InputHandler是一个接口类型,用于处理KeyEvent和TouchEvent类型的事件,我们看看源码 :

  1. public final class ViewRoot extends Handler implements ViewParent, 
  2.         View.AttachInfo.Callbacks { 
  3.             // 代码省略 
  4.     private final InputHandler mInputHandler = new InputHandler() { 
  5.         public void handleKey(KeyEvent event, Runnable finishedCallback) { 
  6.             startInputEvent(finishedCallback); 
  7.             dispatchKey(event, true); 
  8.         } 
  9.         public void handleMotion(MotionEvent event, Runnable finishedCallback) { 
  10.             startInputEvent(finishedCallback); 
  11.             dispatchMotion(event, true);      // 1、handle 触摸消息 
  12.         } 
  13.     }; 
  14.        // 代码省略 
  15.     // 2、分发触摸消息 
  16.     private void dispatchMotion(MotionEvent event, boolean sendDone) { 
  17.         int source = event.getSource(); 
  18.         if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 
  19.             dispatchPointer(event, sendDone);      // 分发触摸消息 
  20.         } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 
  21.             dispatchTrackball(event, sendDone); 
  22.         } else { 
  23.             // TODO 
  24.             Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event); 
  25.             if (sendDone) { 
  26.                 finishInputEvent(); 
  27.             } 
  28.         } 
  29.     } 
  30.     // 3、通过Handler投递消息 
  31.     private void dispatchPointer(MotionEvent event, boolean sendDone) { 
  32.         Message msg = obtainMessage(DISPATCH_POINTER); 
  33.         msg.obj = event; 
  34.         msg.arg1 = sendDone ? 1 : 0
  35.         sendMessageAtTime(msg, event.getEventTime()); 
  36.     } 
  37.     @Override 
  38.     public void handleMessage(Message msg) {
  39. // ViewRoot覆写handlerMessage来处理各种消息 
  40.         switch (msg.what) { 
  41.             // 代码省略 
  42.         case DO_TRAVERSAL: 
  43.             if (mProfile) { 
  44.                 Debug.startMethodTracing("ViewRoot"); 
  45.             } 
  46.  
  47.             performTraversals(); 
  48.  
  49.             if (mProfile) { 
  50.                 Debug.stopMethodTracing(); 
  51.                 mProfile = false
  52.             } 
  53.             break
  54.  
  55.         case DISPATCH_POINTER: {
  56. // 4、处理DISPATCH_POINTER类型的消息,即触摸屏幕的消息 
  57.             MotionEvent event = (MotionEvent) msg.obj; 
  58.             try { 
  59.                 deliverPointerEvent(event); // 5、处理触摸消息 
  60.             } finally { 
  61.                 event.recycle(); 
  62.                 if (msg.arg1 != 0) { 
  63.                     finishInputEvent(); 
  64.                 } 
  65.                 if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!"); 
  66.             } 
  67.         } break
  68.         // 代码省略 
  69.     } 
  70.     // 6、真正的处理事件 
  71.     private void deliverPointerEvent(MotionEvent event) { 
  72.         if (mTranslator != null) { 
  73.             mTranslator.translateEventInScreenToAppWindow(event); 
  74.         } 
  75.         boolean handled; 
  76.         if (mView != null && mAdded) { 
  77.             // enter touch mode on the down 
  78.             boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; 
  79.             if (isDown) { 
  80.                 ensureTouchMode(true); // 如果是ACTION_DOWN事件则进入触摸模式,否则为按键模式。 
  81.             } 
  82.             if(Config.LOGV) { 
  83.                 captureMotionLog("captureDispatchPointer", event); 
  84.             } 
  85.             if (mCurScrollY != 0) { 
  86.                 event.offsetLocation(0, mCurScrollY);   // 物理坐标向逻辑坐标的转换 
  87.             } 
  88.             if (MEASURE_LATENCY) { 
  89.                 lt.sample("A Dispatching TouchEvents", System.nanoTime()
  90.  - event.getEventTimeNano()); 
  91.             } 
  92. // 7、分发事件,如果是窗口类型,则这里的mView对应的就是PhonwWindow中的DecorView
  93. //,否则为根视图的ViewGroup。 
  94.             handled = mView.dispatchTouchEvent(event); 
  95.             // 代码省略    
  96.         } 
  97.     } 
  98.    // 代码省略 
  99. }  

经过层层迷雾,不管代码7处的mView是DecorView还是非窗口界面的根视图,其本质都是ViewGroup,即触摸事件最终被根视图ViewGroup进行分发!!!

我们就以Activity为例来分析这个过程,我们知道显示出来的Activity有一个顶层窗口,这个窗口的实现类是PhoneWindow, PhoneWindow中的内容区域是一个DecorView类型的View,这个View这就是我们在手机上看到的内容,这个DecorView是 FrameLayout的子类,Activity的的dispatchTouchEvent实际上就是调用PhoneWindow的 dispatchTouchEvent,我们看看源代码吧,进入Activity的dispatchTouchEvent函数 :

  1. public boolean dispatchTouchEvent(MotionEvent ev) { 
  2.       if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
  3.           onUserInteraction(); 
  4.       } 
  5.       if (getWindow().superDispatchTouchEvent(ev)) {
  6. // 1、调用的是PhoneWindow的superDispatchTouchEvent(ev) 
  7.           return true
  8.       } 
  9.       return onTouchEvent(ev); 
  10.   } 
  11.  
  12.   public void onUserInteraction() { 
  13.   } 

可以看到,如果事件为按下事件,则会进入到onUserInteraction()这个函数,该函数为空实现,我们暂且不管它。继续看,发现touch事 件的分发调用了getWindow().superDispatchTouchEvent(ev)函数,getWindow()获取到的实例的类型为 PhoneWindow类型,你可以在你的Activity类中使用如下方式查看getWindow()获取到的类型:

 Log.d("", "### Activiti中getWindow()获取的类型是 : " + this.getWindow()) ;

输出:

08-31 03:40:17.036: D/(1688): ### Activiti中getWindow()获取的类型是 : com.android.internal.policy.impl.PhoneWindow@5287fe38
OK,废话不多说,我们还是继续看PhoneWindow中的superDispatchTouchEvent函数吧。

  1. @Override 
  2. public boolean superDispatchTouchEvent(MotionEvent event) { 
  3.     return mDecor.superDispatchTouchEvent(event); 

恩,调用的是mDecor的superDispatchTouchEvent(event)函数,这个mDecor就是我们上面所说的DecorView 类型,也就是我们看到的Activity上的所有内容的一个顶层ViewGroup,即整个ViewTree的根节点。看看它的声明吧。

  1. // This is the top-level view of the window, containing the window decor. 
  2. private DecorView mDecor; 

DecorView

      那么我继续看看DecorView到底是个什么玩意儿吧。

  1.    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { 
  2.         /* package */int mDefaultOpacity = PixelFormat.OPAQUE; 
  3.  
  4.         /** The feature ID of the panel, or -1 if this is the application's DecorView */ 
  5.         private final int mFeatureId; 
  6.  
  7.         private final Rect mDrawingBounds = new Rect(); 
  8.  
  9.         private final Rect mBackgroundPadding = new Rect(); 
  10.  
  11.         private final Rect mFramePadding = new Rect(); 
  12.  
  13.         private final Rect mFrameOffsets = new Rect(); 
  14.  
  15.         private boolean mChanging; 
  16.  
  17.         private Drawable mMenuBackground; 
  18.         private boolean mWatchingForMenu; 
  19.         private int mDownY; 
  20.  
  21.         public DecorView(Context context, int featureId) { 
  22.             super(context); 
  23.             mFeatureId = featureId; 
  24.         } 
  25.  
  26.         @Override 
  27.         public boolean dispatchKeyEvent(KeyEvent event) { 
  28.             final int keyCode = event.getKeyCode(); 
  29.             // 代码省略 
  30.             return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) 
  31.                     : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); 
  32.         } 
  33.  
  34.         @Override 
  35.         public boolean dispatchTouchEvent(MotionEvent ev) { 
  36.             final Callback cb = getCallback(); 
  37.             return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super 
  38.                     .dispatchTouchEvent(ev); 
  39.         } 
  40.  
  41.         @Override 
  42.         public boolean dispatchTrackballEvent(MotionEvent ev) { 
  43.             final Callback cb = getCallback(); 
  44.             return cb != null && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) : super 
  45.                     .dispatchTrackballEvent(ev); 
  46.         } 
  47.  
  48.         public boolean superDispatchKeyEvent(KeyEvent event) { 
  49.             return super.dispatchKeyEvent(event); 
  50.         } 
  51.  
  52.         public boolean superDispatchTouchEvent(MotionEvent event) { 
  53.             return super.dispatchTouchEvent(event); 
  54.         } 
  55.  
  56.         public boolean superDispatchTrackballEvent(MotionEvent event) { 
  57.             return super.dispatchTrackballEvent(event); 
  58.         } 
  59.  
  60.         @Override 
  61.         public boolean onTouchEvent(MotionEvent event) { 
  62.             return onInterceptTouchEvent(event); 
  63.         } 
  64. // 代码省略 

可以看到,DecorView继承自FrameLayout, 它对于touch事件的分发( dispatchTouchEvent )、处理都是交给super类来处理,也就是FrameLayout来处理,我们在FrameLayout中没有看到相应的实现,那继续跟踪到 FrameLayout的父类,即ViewGroup,我们看到了dispatchTouchEvent的实现,那我们就先看ViewGroup (Android 2.3 源码)是如何进行事件分发的吧。

ViewGroup的Touch事件分发

  1. /** 
  2.  * {@inheritDoc} 
  3.  */ 
  4. @Override 
  5. public boolean dispatchTouchEvent(MotionEvent ev) { 
  6.     if (!onFilterTouchEventForSecurity(ev)) { 
  7.         return false
  8.     } 
  9.  
  10.     final int action = ev.getAction(); 
  11.     final float xf = ev.getX(); 
  12.     final float yf = ev.getY(); 
  13.     final float scrolledXFloat = xf + mScrollX; 
  14.     final float scrolledYFloat = yf + mScrollY; 
  15.     final Rect frame = mTempRect; 
  16.  
  17.     boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
  18.  
  19.     if (action == MotionEvent.ACTION_DOWN) { 
  20.         if (mMotionTarget != null) { 
  21.             // this is weird, we got a pen down, but we thought it was 
  22.             // already down! 
  23.             // XXX: We should probably send an ACTION_UP to the current 
  24.             // target. 
  25.             mMotionTarget = null
  26.         } 
  27.         // If we're disallowing intercept or if we're allowing and we didn't 
  28.         // intercept 
  29.         if (disallowIntercept || !onInterceptTouchEvent(ev))
  30.          // 1、是否禁用拦截、是否拦截事件 
  31.             // reset this event's action (just to protect ourselves) 
  32.             ev.setAction(MotionEvent.ACTION_DOWN); 
  33.             // We know we want to dispatch the event down, find a child 
  34.             // who can handle it, start with the front-most child. 
  35.             final int scrolledXInt = (int) scrolledXFloat; 
  36.             final int scrolledYInt = (int) scrolledYFloat; 
  37.             final View[] children = mChildren; 
  38.             final int count = mChildrenCount; 
  39.  
  40.             for (int i = count - 1; i >= 0; i--)
  41.         // 2、迭代所有子view,查找触摸事件在哪个子view的坐标范围内 
  42.                 final View child = children[i]; 
  43.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 
  44.                         || child.getAnimation() != null) { 
  45.                     child.getHitRect(frame);
  46.                // 3、获取child的坐标范围 
  47.                     if (frame.contains(scrolledXInt, scrolledYInt))
  48.     // 4、判断发生该事件坐标是否在该child坐标范围内 
  49.                         // offset the event to the view's coordinate system 
  50.                         final float xc = scrolledXFloat - child.mLeft; 
  51.                         final float yc = scrolledYFloat - child.mTop; 
  52.                         ev.setLocation(xc, yc); 
  53.                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
  54.                         if (child.dispatchTouchEvent(ev))
  55.      // 5、child处理该事件 
  56.                             // Event handled, we have a target now. 
  57.                             mMotionTarget = child; 
  58.                             return true
  59.                         } 
  60.                         // The event didn't get handled, try the next view. 
  61.                         // Don't reset the event's location, it's not 
  62.                         // necessary here. 
  63.                     } 
  64.                 } 
  65.             } 
  66.         } 
  67.     } 
  68.  
  69.     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || 
  70.             (action == MotionEvent.ACTION_CANCEL); 
  71.  
  72.     if (isUpOrCancel) { 
  73.         // Note, we've already copied the previous state to our local 
  74.         // variable, so this takes effect on the next event 
  75.         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; 
  76.     } 
  77.  
  78.     // The event wasn't an ACTION_DOWN, dispatch it to our target if 
  79.     // we have one. 
  80.     final View target = mMotionTarget; 
  81.     if (target == null) { 
  82.         // We don't have a target, this means we're handling the 
  83.         // event as a regular view. 
  84.         ev.setLocation(xf, yf); 
  85.         if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 
  86.             ev.setAction(MotionEvent.ACTION_CANCEL); 
  87.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
  88.         } 
  89.         return super.dispatchTouchEvent(ev); 
  90.     } 
  91.  
  92.     // if have a target, see if we're allowed to and want to intercept its 
  93.     // events 
  94.     if (!disallowIntercept && onInterceptTouchEvent(ev)) { 
  95.         final float xc = scrolledXFloat - (float) target.mLeft; 
  96.         final float yc = scrolledYFloat - (float) target.mTop; 
  97.         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
  98.         ev.setAction(MotionEvent.ACTION_CANCEL); 
  99.         ev.setLocation(xc, yc); 
  100.         if (!target.dispatchTouchEvent(ev)) { 
  101.             // target didn't handle ACTION_CANCEL. not much we can do 
  102.             // but they should have. 
  103.         } 
  104.         // clear the target 
  105.         mMotionTarget = null
  106.         // Don't dispatch this event to our own view, because we already 
  107.         // saw it when intercepting; we just want to give the following 
  108.         // event to the normal onTouchEvent(). 
  109.         return true
  110.     } 
  111.  
  112.     if (isUpOrCancel) { 
  113.         mMotionTarget = null
  114.     } 
  115.  
  116.     // finally offset the event to the target's coordinate system and 
  117.     // dispatch the event. 
  118.     final float xc = scrolledXFloat - (float) target.mLeft; 
  119.     final float yc = scrolledYFloat - (float) target.mTop; 
  120.     ev.setLocation(xc, yc); 
  121.  
  122.     if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 
  123.         ev.setAction(MotionEvent.ACTION_CANCEL); 
  124.         target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
  125.         mMotionTarget = null
  126.     } 
  127.  
  128.     return target.dispatchTouchEvent(ev); 

这个函数代码比较长,我们只看上文中标注的几个关键点。首先在代码1处可以看到一个条件判断,如果disallowIntercept 和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否 禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。 那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?onInterceptTouchEvent就是ViewGroup对事件进行拦截的一个函数,返回该函数返回false则表示不拦截事件,反之则表示拦截。第二个条件是是 对onInterceptTouchEvent方法的返回值取反,也就是说如果我们在onInterceptTouchEvent方法中返回false, 就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值的整体 变为false,从而跳出了这个条件判断。例如我们需要实现ListView滑动删除某一项的功能,那么可以通过在onInterceptTouchEvent返回true,并且在onTouchEvent中实现相关的判断逻辑,从而实现该功能。

 

   进入代码1内部的if后,有一个for循环,遍历了当前ViewGroup下的所有子child view,如果触摸该事件的坐标在某个child view的坐标范围内,那么该child view来处理这个触摸事件,即调用该child view的dispatchTouchEvent。如果该child view是ViewGroup类型,那么继续执行上面的判断,并且遍历子view;如果该child view不是ViewGroup类型,那么直接调用的是View中的dispatchTouchEvent方法,除非这个child view的类型覆写了该方法。我们看看View中的dispatchTouchEvent函数:

View的Touch事件分发

  1. /** 
  2.  * Pass the touch screen motion event down to the target view, or this 
  3.  * view if it is the target. 
  4.  * 
  5.  * @param event The motion event to be dispatched. 
  6.  * @return True if the event was handled by the view, false otherwise. 
  7.  */ 
  8. public boolean dispatchTouchEvent(MotionEvent event) { 
  9.     if (!onFilterTouchEventForSecurity(event)) { 
  10.         return false
  11.     } 
  12.  
  13.     if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 
  14.             mOnTouchListener.onTouch(this, event)) { 
  15.         return true
  16.     } 
  17.     return onTouchEvent(event); 

函数中,首先判断该事件是否符合安全策略,然后判断该view是否是enable的 ,以及是否设置了Touch Listener,mOnTouchListener即我们通过setOnTouchListener设置的。

  1. /** 
  2.  * Register a callback to be invoked when a touch event is sent to this view. 
  3.  * @param l the touch listener to attach to this view 
  4.  */ 
  5. public void setOnTouchListener(OnTouchListener l) { 
  6.     mOnTouchListener = l; 

如果mOnTouchListener.onTouch(this, event)返回false则继续执行onTouchEvent(event);如果mOnTouchListener.onTouch(this, event)返回true,则表示该事件被消费了,不再传递,因此也不会执行onTouchEvent(event)。这也验证了我们上文中留下的场景2,当onTouch函数返回true时,点击按钮,但我们的点击事件没有执行。那么我们还是先来看看onTouchEvent(event)函数到底做了什么吧。

  1. /** 
  2.  * Implement this method to handle touch screen motion events. 
  3.  * 
  4.  * @param event The motion event. 
  5.  * @return True if the event was handled, false otherwise. 
  6.  */ 
  7. public boolean onTouchEvent(MotionEvent event) { 
  8.     final int viewFlags = mViewFlags; 
  9.  
  10.     if ((viewFlags & ENABLED_MASK) == DISABLED)
  11.         // 1、判断该view是否enable 
  12.         // A disabled view that is clickable still consumes the touch 
  13.         // events, it just doesn't respond to them. 
  14.         return (((viewFlags & CLICKABLE) == CLICKABLE || 
  15.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); 
  16.     } 
  17.  
  18.     if (mTouchDelegate != null) { 
  19.         if (mTouchDelegate.onTouchEvent(event)) { 
  20.             return true
  21.         } 
  22.     } 
  23.  
  24.     if (((viewFlags & CLICKABLE) == CLICKABLE || 
  25.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))
  26.  // 2、是否是clickable或者long clickable 
  27.         switch (event.getAction()) { 
  28.             case MotionEvent.ACTION_UP: 
  29.                    // 抬起事件 
  30.                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0
  31.                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) { 
  32.                     // take focus if we don't have it already and we should in 
  33.                     // touch mode. 
  34.                     boolean focusTaken = false
  35.                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 
  36.                         focusTaken = requestFocus(); 
  37.       // 获取焦点 
  38.                     } 
  39.  
  40.                     if (!mHasPerformedLongPress) { 
  41.                         // This is a tap, so remove the longpress check 
  42.                         removeLongPressCallback(); 
  43.  
  44.                         // Only perform take click actions if we were in the pressed state 
  45.                         if (!focusTaken) { 
  46.                             // Use a Runnable and post this rather than calling 
  47.                             // performClick directly. This lets other visual state 
  48.                             // of the view update before click actions start. 
  49.                             if (mPerformClick == null) { 
  50.                                 mPerformClick = new PerformClick(); 
  51.                             } 
  52.                             if (!post(mPerformClick))
  53.      // post 
  54.                                 performClick();
  55.           // 3、点击事件处理 
  56.                             } 
  57.                         } 
  58.                     } 
  59.  
  60.                     if (mUnsetPressedState == null) { 
  61.                         mUnsetPressedState = new UnsetPressedState(); 
  62.                     } 
  63.  
  64.                     if (prepressed) { 
  65.                         mPrivateFlags |= PRESSED; 
  66.                         refreshDrawableState(); 
  67.                         postDelayed(mUnsetPressedState, 
  68.                                 ViewConfiguration.getPressedStateDuration()); 
  69.                     } else if (!post(mUnsetPressedState)) { 
  70.                         // If the post failed, unpress right now 
  71.                         mUnsetPressedState.run(); 
  72.                     } 
  73.                     removeTapCallback(); 
  74.                 } 
  75.                 break
  76.  
  77.             case MotionEvent.ACTION_DOWN: 
  78.                 if (mPendingCheckForTap == null) { 
  79.                     mPendingCheckForTap = new CheckForTap(); 
  80.                 } 
  81.                 mPrivateFlags |= PREPRESSED; 
  82.                 mHasPerformedLongPress = false
  83.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 
  84.                 break
  85.  
  86.             case MotionEvent.ACTION_CANCEL: 
  87.                 mPrivateFlags &= ~PRESSED; 
  88.                 refreshDrawableState(); 
  89.                 removeTapCallback(); 
  90.                 break
  91.  
  92.             case MotionEvent.ACTION_MOVE: 
  93.                 final int x = (int) event.getX(); 
  94.                 final int y = (int) event.getY(); 
  95.  
  96.                 // Be lenient about moving outside of buttons 
  97.                 int slop = mTouchSlop; 
  98.                 if ((x < 0 - slop) || (x >= getWidth() + slop) || 
  99.                         (y < 0 - slop) || (y >= getHeight() + slop)) { 
  100.                     // Outside button 
  101.                     removeTapCallback(); 
  102.                     if ((mPrivateFlags & PRESSED) != 0) { 
  103.                         // Remove any future long press/tap checks 
  104.                         removeLongPressCallback(); 
  105.  
  106.                         // Need to switch from pressed to not pressed 
  107.                         mPrivateFlags &= ~PRESSED; 
  108.                         refreshDrawableState(); 
  109.                     } 
  110.                 } 
  111.                 break
  112.         } 
  113.         return true
  114.     } 
  115.  
  116.     return false

我们看到,在onTouchEvent函数中就是对ACTION_UP、ACTION_DOWN、ACTION_MOVE等几个事件进行处理,而最重要的 就是UP事件了,因为这个里面包含了对用户点击事件的处理,或者是说对于用户而言相对重要一点,因此放在了第一个case中。在ACTION_UP事件中 会判断该view是否enable、是否clickable、是否获取到了焦点,然后我们看到会通过post方法将一个PerformClick对象投递 给UI线程,如果投递失败则直接调用performClick函数执行点击事件。

  1. /** 
  2.  * Causes the Runnable to be added to the message queue. 
  3.  * The runnable will be run on the user interface thread. 
  4.  * 
  5.  * @param action The Runnable that will be executed. 
  6.  * 
  7.  * @return Returns true if the Runnable was successfully placed in to the 
  8.  *         message queue.  Returns false on failure, usually because the 
  9.  *         looper processing the message queue is exiting. 
  10.  */ 
  11. public boolean post(Runnable action) { 
  12.     Handler handler; 
  13.     if (mAttachInfo != null) { 
  14.         handler = mAttachInfo.mHandler; 
  15.     } else { 
  16.         // Assume that post will succeed later 
  17.         ViewRoot.getRunQueue().post(action); 
  18.         return true
  19.     } 
  20.  
  21.     return handler.post(action); 

我们看看PerformClick类吧。

  1. private final class PerformClick implements Runnable { 
  2.     public void run() { 
  3.         performClick(); 
  4.     } 

可以看到,其内部就是包装了View类中的performClick()方法。再看performClick()方法:

  1. /** 
  2.   * Register a callback to be invoked when this view is clicked. If this view is not 
  3.   * clickable, it becomes clickable. 
  4.   * 
  5.   * @param l The callback that will run 
  6.   * 
  7.   * @see #setClickable(boolean) 
  8.   */ 
  9.  public void setOnClickListener(OnClickListener l) { 
  10.      if (!isClickable()) { 
  11.          setClickable(true); 
  12.      } 
  13.      mOnClickListener = l; 
  14.  } 
  15.  
  16.  /** 
  17.   * Call this view's OnClickListener, if it is defined. 
  18.   * 
  19.   * @return True there was an assigned OnClickListener that was called, false 
  20.   *         otherwise is returned. 
  21.   */ 
  22.  public boolean performClick() { 
  23.      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 
  24.  
  25.      if (mOnClickListener != null) { 
  26.          playSoundEffect(SoundEffectConstants.CLICK); 
  27.          mOnClickListener.onClick(this); 
  28.          return true
  29.      } 
  30.  
  31.      return false
  32.  } 

代码很简单,主要就是调用了mOnClickListener.onClick(this);方法,即执行用户通过setOnClickListener设置进来的点击事件处理Listener。

总结

用户触摸屏幕产生一个触摸消息,系统底层将该消息转发给ViewRoot ( ViewRootImpl ),ViewRoot产生一个DISPATCHE_POINTER的消息,并且在handleMessage中处理该消息,最终会通过 deliverPointerEvent(MotionEvent event)来处理该消息。在该函数中会调用mView.dispatchTouchEvent(event)来分发消息,该mView是一个 ViewGroup类型,因此是ViewGroup的dispatchTouchEvent(event),在该函数中会遍历所有的child view,找到该事件的触发的左边与每个child view的坐标进行对比,如果触摸的坐标在该child view的范围内,则由该child view进行处理。如果该child view是ViewGroup类型,则继续上一步的查找过程;否则执行View中的dispatchTouchEvent(event)函数。在View 的dispatchTouchEvent(event)中首先判断该控件是否enale以及mOnTouchListent是否为空,如果 mOnTouchListener不为空则执行mOnTouchListener.onTouch(event)方法,如果该方法返回false则再执行 View中的onTouchEvent(event)方法,并且在该方法中执行mOnClickListener.onClick(this, event) ;方法; 如果mOnTouchListener.onTouch(event)返回true则不会执行onTouchEvent方法,因此点击事件也不会被执行。

(bboyfeiyu)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www1.rosoo.net/a/201409/17054.html]
本文出处:CSDN博客 作者:bboyfeiyu 原文
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容