【UI】【View】View事件分发(一)

要了解view事件分发机制,首先要了解一些基础知识。

一、

事件基于先捕获然后冒泡的形式。

在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获。

在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。

事件的捕获和冒泡是整个事件的传递流程,但是在实际的传递过程中,Android中则表现的相对复杂。

主要表现在可以控制每层事件是否继续传递(由事件分发和事件拦截协同进行),以及事件的具体消费(由事件消响应进行,但需要注意的是,事件分发自身也具有事件消费能力)。

也就是事件分发、拦截和响应。(本文主要讲解事件分发)

Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元不具有事件分发和事件拦截(因为它没有自己的子View)。


事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

当有监听到事件时,首先由Activity的捕获到,进入事件分发处理流程。无论是Activity还是View,如前文所说,事件分发自身也具有消费能力,

如果事件分发返回true,表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。至此,事件已经完结。如果你不想Activity中的任何控件具有任何的事件消费能力,

最简答的方法可以重写此Activity的dispatchTouchEvent方法,直接返回true就ok。

如果事件分发返回 false,表明事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。

当然了,如果本层控件已经是Activity,那么事件将被系统消费或处理。

如果事件分发返回系统默认的 super.dispatchTouchEvent(ev),事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理

(如果本层控件是Activity,由于其没有事件拦截,因此将直接将事件传递到子View,并交给子View的事件分发进行处理)。

事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev) 

如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;

如果返回结果是false;则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。

如果返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,交由子View的dispatchTouchEvent进行处理。

 事件响应:public boolean onTouchEvent(MotionEvent ev)

如果onTouchEvent返回true,表示onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的冒泡。

如果onTouchEvent返回false,事件在onTouchEvent中处理后继续向上层View冒泡,且有上层View的onTouchEvent进行处理。

如果返回super.onTouchEvent(ev),则默认处理的逻辑和返回false时相同。

总结:从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,

只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,

但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否具有冒泡特是由onTouchEvent的返回值决定的。


二、

如下图,是一个简单的View树:



当我们发起动作,触摸事件是由Activity传给PhoneWindow再传到根mDercorView中。也就是从上图的树根,按照前序遍历,把触摸事件向下传。事件会先传到ViewGroup1,然后遍历它下面的三个子View,如果这三个子View都没有消费这个事件,就会尝试调用ViewGruop1消费这个事件。如果事件没有被消费,则ViewGroup2就会重复ViewGroup1的进行步骤。如果三个ViewGroup都没有消费掉事件,则事件会传到ViewGroup0去试着消费,如果也没有消费掉,最后就会传到Activity中进行消费。

====================================================================================================================================


以上描述了事件的传递过程,下面我们来看一下代码层级是如何进行的。

首先,一个触摸事件最先就是包装成一个MotionEvent给发送到ActivitydispatchTounchEvent了。那么我们先来看下这个方法:

(关于MotionEvent类请看另一篇博客,MotionEvent类详解


1)进入Activity的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) { 
   if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();    
   }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true; 
   }  
  return onTouchEvent(ev);
}


这里调用了 Activity所对应的 WindowsuperDispatchTouchEvent(ev)方法来进行事件的分发。事实上也是调用 Window的唯一实现类 PhoneWindow中的这个方法。在这个方法中,直接调用了 mDecor.superDispatchTouchEvent,代表事件终于 mDecor。就是我们 ActivitysetContent中所设置的 View的父容器中。

下面的几个知识点是对以上描述的总结:

  1. MotionEvent现在是传到Activity的顶级View的,我们的事件分发就是从这个顶级View向它的子View进行分发的。
  2. 事件分发就是把事件(MotionEvent) 按照先序遍历所有节点,直到找到一个View消费掉这个事件。所谓的消费这个事件,就是相应的ViewOntouchListener返回true或者OntouchEvent()返回为true
  3. 事件分发主要由三个函数控制,分别是dispatchTouchEvent分发事件,onInterceptTouchEvent拦截事件,onTouchEvent响应事件。

2)进入View的dispatchTouchEvent

[java]  view plain  copy
  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);  
  18.     }  


看13行,首先判断mOnTouchListener不为null,并且view是enable的状态,然后 mOnTouchListener.onTouch(this, event)返回true。如果这三个条件都满足,直接return true ,下面的onTouchEvent(event)不会被执行。


2)mOnTouchListener是什么:

[java]  view plain  copy
  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;  
  7.  }  

其实是我们自己给控件设置的listener,用的方法是setOnTouchListener。

如果我们在OnTouchListener中重写onTouch方法,可以选择return true或者false.

那么如果return true,View自己的onTouchEvent方法不会被执行了


已经解决一个常见的问题:View的onTouchListener和onTouchEvent的调用顺序关系。


3)View 的onTouchEvent

以下是源码:

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


猜你喜欢

转载自blog.csdn.net/caibaozixiaobai/article/details/51781503
今日推荐