一、 Android分发机制概述:

Android如此受欢迎,就在于其优秀的交互性,这其中,Android优秀的事件分发机制功不可没。那么,作为一个优秀的程序员,要想做一个具有良好交互性的应用,必须透彻理解Android的事件分发机制。

要想充分理解android的分发机制,需要先对以下几个知识点有所了解:

① View和ViewGroup什么?

② 事件

③ View 事件的分发机制

④ ViewGroup事件的分发机制

下面,就让我们沿着大致方向,开始事件分发的探究之旅吧……

二、 View和ViewGroup

Android的UI界面都是由View和ViewGroup及其派生类组合而成的。其中,View是所有UI组件的基类,而ViewGroup是容纳这些组件的容器,其本身也是从View派生出来的,也就是说ViewGroup的父类就是View。

通常来说,Button、ImageView、TextView等控件都是继承父类View来实现的。RelativeLayout、LinearLayout、FrameLayout等布局都是继承父类ViewGroup来实现的。

三、事件:

当手指触摸到View或ViewGroup派生的控件后,将会触发一系列的触发响应事件,如:

onTouchEvent、onClick、onLongClick等。每个View都有自己处理事件的回调方法,开发人员只需要重写这些回调方法,就可以实现需要的响应事件。

而事件通常重要的有如下三种:

  MotionEvent.ACTION_DOWN 按下View,是所有事件的开始

  MotionEvent.ACTION_MOVE 滑动事件

  MotionEvent.ACTION_UP 与down对应,表示抬起

事件的响应原理:

在android开发设计模式中,最广泛应用的就是监听、回调,进而形成了事件响应的过程。

以Button的OnClick为例,因为Button也是一个View,所以它也拥有View父类的方法,在View中源码如下:

  1 /**定义接口成员变量*/  2   3 protected OnClickListener mOnClickListener;  4   5     /**  6   7      * Interface definition for a callback to be invoked when a view is clicked.  8   9      */ 10  11     public interface OnClickListener { 12  13         /** 14  15          * Called when a view has been clicked. 16  17          * 18  19          * @param v The view that was clicked. 20  21          */ 22  23         void onClick(View v); 24  25     } 26  27 /** 28  29      * Register a callback to be invoked when this view is clicked. If this view is not 30  31      * clickable, it becomes clickable. 32  33      * 34  35      * @param l The callback that will run 36  37      * 38  39      * @see #setClickable(boolean) 40  41      */ 42  43     public void setOnClickListener(OnClickListener l) { 44  45         if (!isClickable()) { 46  47             setClickable(true); 48  49         } 50  51         mOnClickListener = l; 52  53 } 54  55   56  57 /** 58  59      * Call this view's OnClickListener, if it is defined. 60  61      * 62  63      * @return True there was an assigned OnClickListener that was called, false 64  65      *         otherwise is returned. 66  67      */ 68  69     public boolean performClick() { 70  71         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 72  73   74  75         if (mOnClickListener != null) { 76  77             playSoundEffect(SoundEffectConstants.CLICK); 78  79             mOnClickListener.onClick(this); 80  81             return true; 82  83         } 84  85   86  87         return false; 88  89 } 90  91 /**触摸了屏幕后,实现并调用的方法*/ 92  93 public boolean onTouchEvent(MotionEvent event) { 94  95            ….. 96  97                    if (mPerformClick == null) { 98  99                                     mPerformClick = new PerformClick();100 101                                 }102 103                                 if (!post(mPerformClick)) {104 105                                     performClick();106 107                                 }108 109            …..110 111

以上是View源码中关键代码行,以Button为例,假设需要在一个布局上添加一个按钮,并实现它的OnClick事件,需要如下步骤:

1、 OnClickListener类是一个当控件被点击后进行回调的一个接口,它完成被点击后的回调通知。

2、 创建一个按钮Button,并设置监听事件,对这个Button进行setOnClickListener操作

3、 当手指触摸到Button按钮,通过一系列方法(之后将会详细讲解,这里暂时忽略),触发并执行到onTouchEvent方法并执行mPerformClick方法,在mPerformClick方法中,首先会判断注 册的mOnClickListener是否为空,若不为空,它就会回调之前注册的onClick方法,进而执行用户自定义代码。

事件响应机制,简单来说上面的例子就已经基本上诠释了

注册一个监听对象

实现监听对象的监听事件

当某一触发事件到来,在触发事件中通过注册过的监听对象,回调注册对象的响应事件,来完成用户自定义实现。

但凡明白了这一个简单的事件响应的过程,就离事件驱动开发整个过程就不远了,大道至简,请完全理解了这个例子,再继续之后的学习,事半功倍。

四、 View事件的分发机制:

通过上面的例子,我们初步的接触了View的事件分发机制,再进一步了解。首先,我们要熟悉dispatchTouchEvent和onTouchEvent两个函数,这两个函数都是View的函数,要理解View事件的分发机制,只要清楚这两个函数就基本上清楚了。

在这里先提醒一句,这里的“分发”是指一个触摸或点击的事件发生,分发给当前触摸控件所监听的事件(如OnClick、onTouch等),进而来决定是控件的哪个函数来响应此次事件。

dispatchTouchEvent:

此函数负责事件的分发,你只需要记住当触摸一个View控件,首先会调用这个函数就行,在这个函数体里决定将事件分发给谁来处理。

onTouchEvent:

此函数负责执行事件的处理,负责处理事件,主要处理MotionEvent.ACTION_DOWN、

MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP这三个事件。

public boolean onTouchEvent (MotionEvent event)

参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。

那么它是如何执行这个流程的呢?我们还以布局上的按钮为例,看看它是如何实现的。(看图①)

图①

我们知道,View做为所有控件的父类,它本身定义了很多接口来监听触摸在View上的事件,如OnClickListener(点击)、OnLongClickListener(长按)、OnTouchListener(触摸监听)等,那么当手指触摸到View时候,该响应“点击”还是”触摸”呢,就是根据dispatchTouchEvent和onTouchEvent这两个函数组合实现的,我们之下的讨论,仅对常用的“点击OnClick”和“触摸onTouch”来讨论,顺藤摸瓜,找出主线,进而搞清楚View的事件分发机制。

对于上面的按钮,点击它一下,我们期望2种结果,第一种:它响应一个点击事件。第二种:不响应点击事件。

第一种源码:

 1 public class MainActivity extends Activity implements OnClickListener ,OnTouchListener{ 2  3   private Button btnButton; 4  5   @Override 6  7   protected void onCreate(Bundle savedInstanceState) { 8  9        super.onCreate(savedInstanceState);10 11        setContentView(R.layout.activity_main);12 13        btnButton=(Button) findViewById(R.id.btn);14 15        btnButton.setOnClickListener(this);16 17        btnButton.setOnTouchListener(this);18 19        }20 21  22 23   @Override24 25   public void onClick(View v) {26 27        // TODO Auto-generated method stub28 29        switch (v.getId()) {30 31        case R.id.btn:32 33              Log.e("View", "onClick===========>");34 35              break;36 37        default:38 39              break;40 41        }42 43   }44 45  46 47   @Override48 49   public boolean onTouch(View v, MotionEvent event) {50 51        // TODO Auto-generated method stub52 53        Log.e("View", "onTouch..................................");54 55        return false;56 57   }58 59 }

(图②)

第二种源码:

 1 public class MainActivity extends Activity implements OnClickListener ,OnTouchListener{ 2  3   private Button btnButton; 4  5   @Override 6  7   protected void onCreate(Bundle savedInstanceState) { 8  9        super.onCreate(savedInstanceState);10 11        setContentView(R.layout.activity_main);12 13        btnButton=(Button) findViewById(R.id.btn);14 15        btnButton.setOnClickListener(this);16 17        btnButton.setOnTouchListener(this);18 19        }20 21  22 23   @Override24 25   public void onClick(View v) {26 27        // TODO Auto-generated method stub28 29        switch (v.getId()) {30 31        case R.id.btn:32 33              Log.e("View", "onClick===========>");34 35              break;36 37        default:38 39              break;40 41        }42 43   }44 45  46 47   @Override48 49   public boolean onTouch(View v, MotionEvent event) {50 51        // TODO Auto-generated method stub52 53        Log.e("View", "onTouch..................................");54 55        return true;56 57   }58 59 }

(图③)

结果分析:

上面两处代码,第一种执行了OnClick函数和OnTouch函数,第二种执行了OnTouch函数,并没有执行OnClick函数,而且对两处代码进行比较,发现只有在onTouch处返回值true和false不同。当onTouch返回false,onClick被执行了,返回true,onClick未被执行。

为什么会这样呢?我们只有深入源码才能分析出来。

前面提到,触摸一个View就会执行dispatchTouchEvent方法去“分发”事件, 既然触摸的是按钮Button,那么我们就查看Button的源码,寻找dispatchTouchEvent方法,Button源码中没有dispatchTouchEvent方法,但知道Button继承自TextView,寻找TextView,发现它也没有dispatchTouchEvent方法,继续查找TextView的父类View,发现View有dispatchTouchEvent方法,那我们就分析dispatchTouchEvent方法。

主要代码如下:

 1 public boolean dispatchTouchEvent(MotionEvent event) { 2  3         if (onFilterTouchEventForSecurity(event)) { 4  5             //noinspection SimplifiableIfStatement 6  7             if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 8  9                     mOnTouchListener.onTouch(this, event)) {10 11                 return true;12 13             }14 15  16 17             if (onTouchEvent(event)) {18 19                 return true;20 21             }22 23         }24 25         return false;26 27 }

分析:

先来看dispatchTouchEvent函数返回值,如果返回true,表明事件被处理了,反之,表明事件未被处理。

 1 public boolean dispatchTouchEvent(MotionEvent event) { 2  3         if (onFilterTouchEventForSecurity(event)) { 4  5             //noinspection SimplifiableIfStatement 6  7             if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 8  9                     mOnTouchListener.onTouch(this, event)) {10 11                 return true;12 13             }14 15  16 17             if (onTouchEvent(event)) {18 19                 return true;20 21             }22 23         }24 25         return false;26 27 }

这个判定很重要,mOnTouchListener != null,判断该控件是否注册了OnTouchListener对象的监听,(mViewFlags & ENABLED_MASK) == ENABLED,判断当前的控件是否能被点击(比如Button默认可以点击,ImageView默认不许点击,看到这里就了然了),mOnTouchListener.onTouch(this, event)这个是关键,这个调用,就是回调你注册在这个View上的mOnTouchListener对象的onTouch方法,如果你在onTouch方法里返回false,那么这个判断语句就跳出,去执行下面的程序,否则,当前2个都返回了true,自定义onTouch方法也返回true,条件成立,就直接返回了,不再执行下面的程序。接下来,if (onTouchEvent(event)) 这个判断很重要,能否回调OnClickListener接口的onClick函数,关键在于此,可以肯定的是,如果上面if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&

mOnTouchListener.onTouch(this, event))返回true,那么就不会执行并回调OnClickListener接口的onClick函数。

接下来,我们看onTouchEvent这个函数,看它是如何响应点击事件的。

主要代码如下:

  1 public boolean onTouchEvent(MotionEvent event) {  2   3         final int viewFlags = mViewFlags;  4   5    6   7         if ((viewFlags & ENABLED_MASK) == DISABLED) {  8   9             if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) { 10  11                 mPrivateFlags &= ~PRESSED; 12  13                 refreshDrawableState(); 14  15             } 16  17             // A disabled view that is clickable still consumes the touch 18  19             // events, it just doesn't respond to them. 20  21             return (((viewFlags & CLICKABLE) == CLICKABLE || 22  23                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); 24  25         } 26  27   28  29         if (mTouchDelegate != null) { 30  31             if (mTouchDelegate.onTouchEvent(event)) { 32  33                 return true; 34  35             } 36  37         } 38  39   40  41         if (((viewFlags & CLICKABLE) == CLICKABLE || 42  43                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 44  45             switch (event.getAction()) { 46  47                 case MotionEvent.ACTION_UP: 48  49                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 50  51                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) { 52  53                         // take focus if we don't have it already and we should in 54  55                         // touch mode. 56  57                         boolean focusTaken = false; 58  59                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 60  61                             focusTaken = requestFocus(); 62  63                         } 64  65   66  67                         if (prepressed) { 68  69                             // The button is being released before we actually 70  71                             // showed it as pressed.  Make it show the pressed 72  73                             // state now (before scheduling the click) to ensure 74  75                             // the user sees it. 76  77                             mPrivateFlags |= PRESSED; 78  79                             refreshDrawableState(); 80  81                        } 82  83   84  85                         if (!mHasPerformedLongPress) { 86  87                             // This is a tap, so remove the longpress check 88  89                             removeLongPressCallback(); 90  91   92  93                             // Only perform take click actions if we were in the pressed state 94  95                             if (!focusTaken) { 96  97                                 // Use a Runnable and post this rather than calling 98  99                                 // performClick directly. This lets other visual state100 101                                 // of the view update before click actions start.102 103                                 if (mPerformClick == null) {104 105                                     mPerformClick = new PerformClick();106 107                                 }108 109                                 if (!post(mPerformClick)) {110 111                                     performClick();112 113                                 }114 115                             }116 117                         }118 119  120 121                         if (mUnsetPressedState == null) {122 123                             mUnsetPressedState = new UnsetPressedState();124 125                         }126 127  128 129                         if (prepressed) {130 131                             postDelayed(mUnsetPressedState,132 133                                     ViewConfiguration.getPressedStateDuration());134 135                         } else if (!post(mUnsetPressedState)) {136 137                             // If the post failed, unpress right now138 139                             mUnsetPressedState.run();140 141                         }142 143                         removeTapCallback();144 145                     }146 147                     break;148 149  150 151                 case MotionEvent.ACTION_DOWN:152 153                     mHasPerformedLongPress = false;154 155  156 157                     if (performButtonActionOnTouchDown(event)) {158 159                         break;160 161                     }162 163  164 165                     // Walk up the hierarchy to determine if we're inside a scrolling container.166 167                     boolean isInScrollingContainer = isInScrollingContainer();168 169  170 171                     // For views inside a scrolling container, delay the pressed feedback for172 173                     // a short period in case this is a scroll.174 175                     if (isInScrollingContainer) {176 177                         mPrivateFlags |= PREPRESSED;178 179                         if (mPendingCheckForTap == null) {180 181                             mPendingCheckForTap = new CheckForTap();182 183                         }184 185                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());186 187                     } else {188 189                         // Not inside a scrolling container, so show the feedback right away190 191                         mPrivateFlags |= PRESSED;192 193                         refreshDrawableState();194 195                         checkForLongClick(0);196 197                     }198 199                     break;200 201  202 203                 case MotionEvent.ACTION_CANCEL:204 205                     mPrivateFlags &= ~PRESSED;206 207                     refreshDrawableState();208 209                     removeTapCallback();210 211                     break;212 213  214 215                 case MotionEvent.ACTION_MOVE:216 217                     final int x = (int) event.getX();218 219                     final int y = (int) event.getY();220 221  222 223                     // Be lenient about moving outside of buttons224 225                     if (!pointInView(x, y, mTouchSlop)) {226 227                         // Outside button228 229                         removeTapCallback();230 231                         if ((mPrivateFlags & PRESSED) != 0) {232 233                             // Remove any future long press/tap checks234 235                             removeLongPressCallback();236 237  238 239                             // Need to switch from pressed to not pressed240 241                             mPrivateFlags &= ~PRESSED;242 243                             refreshDrawableState();244 245                         }246 247                     }248 249                     break;250 251             }252 253             return true;254 255         }256 257  258 259         return false;260 261 }262 263     public boolean performClick() {264 265         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);266 267  268 269         if (mOnClickListener != null) {270 271             playSoundEffect(SoundEffectConstants.CLICK);272 273             mOnClickListener.onClick(this);274 275             return true;276 277         }278 279  280 281         return false;282 283     }

代码量太大了,不过不要紧,我们通过主要代码分析一下。

  1 public boolean onTouchEvent(MotionEvent event) {  2   3        4   5         //控件不能被点击  6   7         if ((viewFlags & ENABLED_MASK) == DISABLED) {  8   9  10  11         } 12  13 //委托代理别的View去实现 14  15         if (mTouchDelegate != null) { 16  17             if (mTouchDelegate.onTouchEvent(event)) { 18  19                 return true; 20  21             } 22  23         } 24  25         //控件能够点击或者长按 26  27         if (((viewFlags & CLICKABLE) == CLICKABLE || 28  29                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 30  31             switch (event.getAction()) { 32  33             //抬起事件 34  35                 case MotionEvent.ACTION_UP: 36  37                           …... 38  39                             if (!focusTaken) { 40  41                                 // Use a Runnable and post this rather than calling 42  43                                 // performClick directly. This lets other visual state 44  45                                 // of the view update before click actions start. 46  47                                 if (mPerformClick == null) { 48  49                                     mPerformClick = new PerformClick(); 50  51                                 } 52  53                                 if (!post(mPerformClick)) { 54  55                         //这里就是去执行回调注册的onClick函数,实现点击 56  57                                     performClick(); 58  59                                 } 60  61                             } 62  63                             …… 64  65                     break; 66  67            //按下事件 68  69                 case MotionEvent.ACTION_DOWN: 70  71                       72  73                     …… 74  75                     break; 76  77   78  79                …… 80  81            //移动事件 82  83                 case MotionEvent.ACTION_MOVE: 84  85                      …… 86  87                     break; 88  89             } 90  91         92  93             return true; 94  95         } 99         return false;100 101 }102 103  

从上面主要代码可以看出onTouchEvent传参MotionEvent类型,它封装了触摸的活动事件,其中就有MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP三个事件。我们在来看看onTouchEvent的返回值,因为onTouchEvent是在dispatchTouchEvent事件分发处理中调用的,

 1 public boolean dispatchTouchEvent(MotionEvent event) { 2  3          …… 4  5             if (onTouchEvent(event)) { 6  7                 return true; 8  9             }10 11 return fasle;12 13         }

如果onTouchEvent返回true,dispatchTouchEvent就返回true,表明事件被处理了,反之,事件未被处理。

程序的关键在 if (((viewFlags & CLICKABLE) == CLICKABLE ||

(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))的判断里,我们发现无论switch的分支在什么地方跳出,返回都是true。这就表明,无论是三个事件中的哪一个,都会返回true。

参照下图,结合上述,不难理解View的分发机制了。

(图④)

四、 ViewGroup事件分发机制:

ViewGroup事件分发机制较View的稍微复杂一些,不过对View的机制只要精确的理解后,仔细看过这一节,睡几觉起来,估计也就悟出来了,学习就是这么奇怪,当下理解不了或模糊的地方,只要脑子有印象,忽然一夜好像就懂了。

先来看下面的一个简单布局,我们将通过例子,了解ViewGroup+View的android事件处理机制。

(图⑤)

上图由:黑色为线性布局LinearLayout,紫色为相对布局RelativeLayout,按钮Button三部分组成。RelativeLayout为LinearLayout的子布局,Button为RelativeLayout的子布局。以下RelativeLayout简称(R),LinearLayout简称(L),Button简称(B)。

经过前面讲解,我们首先知道这样两件事情。

1、(R)和(L)的父类是ViewGroup,(B)的父类是View。

2、dispatchTouchEvent这个函数很重要,不论是ViewGroup还是View,都由它来处理事件的消费和传递。

下面,我们通过横向和纵向两个维度,通过源码和图解的方式,充分理解事件的传递机制。

先来看整体的事件传递过程:

(图⑥)

当手指点击按钮B时,事件传递的顺序是从底向上传递的,也就是按照L->R->B的顺序由下往上逐层传递,响应正好相反,是自上而下。

L首先接收到点击事件,L的父类是ViewGroup类,并将事件传递给dispatchTouchEvent方法,dispatchTouchEvent函数中判断该控件L是否重载了onInterceptTouchEvent方法进行事件拦截,onInterceptTouchEvent默认返回false不拦截,那么dispatchTouchEvent方法将事件传递给R去处理(进入第2流程处理),如果返回true表示当前L控件拦截了事件向其它控件的传递,交给它自己父类View的dispatchTouchEvent去处理,在父方法的dispatchTouchEvent中,将会按照前面讲的View的事件处理机制去判断,比如判断L是否重载了onTouch方法,是否可点击,是否做了监听等事件。

R也是ViewGroup的子类,因此与第1流程基本相似,如果onInterceptTouchEvent返回了false,表示事件将不拦截继续传递给B。

B是View的子类,它没有onInterceptTouchEvent方法,直接交给自己父类View的dispatchTouchEvent去处理,流程同不再敷述。

总结:

onInterceptTouchEvent只有ViewGroup才有,当一个控件是继承自ViewGroup而来的,那么它就可能会有子控件,因此,才有可能传递给子控件,而继承自View的控件,不会有子控件,也就没有onInterceptTouchEvent函数了。

通过dispatchTouchEvent分发的控件返回值True和false,表示当前控件是否消费了传递过来的事件,如果消费了,返回True,反之false。消费了,就不再继续传递了,没有消费,如果有子控件将继续传递。

啰嗦点,如果想再深层次了解一下,再次从源码ViewGroup来分析一个L控件的事件传递过程,请看下图:

(图⑦)

结合上面的图例,下面列出ViewGroup源码来分析一下,我们只需要分析ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、dispatchTransformedTouchEvent三个方法即可。

  1 public boolean dispatchTouchEvent(MotionEvent ev) {  2   3         if (mInputEventConsistencyVerifier != null) {  4   5             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);  6   7         }  8   9   10  11         boolean handled = false; 12  13         if (onFilterTouchEventForSecurity(ev)) { 14  15             final int action = ev.getAction(); 16  17             final int actionMasked = action & MotionEvent.ACTION_MASK; 18  19   20  21             // Handle an initial down. 22  23             if (actionMasked == MotionEvent.ACTION_DOWN) { 24  25                 // Throw away all previous state when starting a new touch gesture. 26  27                 // The framework may have dropped the up or cancel event for the previous gesture 28  29                 // due to an app switch, ANR, or some other state change. 30  31                 cancelAndClearTouchTargets(ev); 32  33                 resetTouchState(); 34  35             } 36  37   38  39             // Check for interception. 40  41             final boolean intercepted; 42  43             if (actionMasked == MotionEvent.ACTION_DOWN 44  45                     || mFirstTouchTarget != null) { 46  47                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 48  49                 if (!disallowIntercept) { 50  51                     intercepted = onInterceptTouchEvent(ev); 52  53                     ev.setAction(action); // restore action in case it was changed 54  55                 } else { 56  57                     intercepted = false; 58  59                 } 60  61             } else { 62  63                 // There are no touch targets and this action is not an initial down 64  65                 // so this view group continues to intercept touches. 66  67                 intercepted = true; 68  69             } 70  71   72  73             // Check for cancelation. 74  75             final boolean canceled = resetCancelNextUpFlag(this) 76  77                     || actionMasked == MotionEvent.ACTION_CANCEL; 78  79   80  81             // Update list of touch targets for pointer down, if needed. 82  83             final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; 84  85             TouchTarget newTouchTarget = null; 86  87             boolean alreadyDispatchedToNewTouchTarget = false; 88  89             if (!canceled && !intercepted) { 90  91                 if (actionMasked == MotionEvent.ACTION_DOWN 92  93                         || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) 94  95                         || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 96  97                     final int actionIndex = ev.getActionIndex(); // always 0 for down 98  99                     final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)100 101                             : TouchTarget.ALL_POINTER_IDS;102 103  104 105                     // Clean up earlier touch targets for this pointer id in case they106 107                     // have become out of sync.108 109                     removePointersFromTouchTargets(idBitsToAssign);110 111  112 113                     final int childrenCount = mChildrenCount;114 115                     if (childrenCount != 0) {116 117                         // Find a child that can receive the event.118 119                         // Scan children from front to back.120 121                         final View[] children = mChildren;122 123                         final float x = ev.getX(actionIndex);124 125                         final float y = ev.getY(actionIndex);126 127  128 129                         for (int i = childrenCount - 1; i >= 0; i--) {130 131                             final View child = children[i];132 133                             if (!canViewReceivePointerEvents(child)134 135                                     || !isTransformedTouchPointInView(x, y, child, null)) {136 137                                 continue;138 139                             }140 141  142 143                             newTouchTarget = getTouchTarget(child);144 145                             if (newTouchTarget != null) {146 147                                 // Child is already receiving touch within its bounds.148 149                                 // Give it the new pointer in addition to the ones it is handling.150 151                                 newTouchTarget.pointerIdBits |= idBitsToAssign;152 153                                 break;154 155                             }156 157  158 159                             resetCancelNextUpFlag(child);160 161                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {162 163                                 // Child wants to receive touch within its bounds.164 165                                 mLastTouchDownTime = ev.getDownTime();166 167                                 mLastTouchDownIndex = i;168 169                                 mLastTouchDownX = ev.getX();170 171                                 mLastTouchDownY = ev.getY();172 173                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);174 175                                 alreadyDispatchedToNewTouchTarget = true;176 177                                 break;178 179                             }180 181                         }182 183                     }184 185  186 187                     if (newTouchTarget == null && mFirstTouchTarget != null) {188 189                         // Did not find a child to receive the event.190 191                         // Assign the pointer to the least recently added target.192 193                         newTouchTarget = mFirstTouchTarget;194 195                         while (newTouchTarget.next != null) {196 197                             newTouchTarget = newTouchTarget.next;198 199                         }200 201                         newTouchTarget.pointerIdBits |= idBitsToAssign;202 203                     }204 205                 }206 207             }208 209  210 211             // Dispatch to touch targets.212 213             if (mFirstTouchTarget == null) {214 215                 // No touch targets so treat this as an ordinary view.216 217                 handled = dispatchTransformedTouchEvent(ev, canceled, null,218 219                         TouchTarget.ALL_POINTER_IDS);220 221             } else {222 223                 // Dispatch to touch targets, excluding the new touch target if we already224 225                 // dispatched to it.  Cancel touch targets if necessary.226 227                 TouchTarget predecessor = null;228 229                 TouchTarget target = mFirstTouchTarget;230 231                 while (target != null) {232 233                     final TouchTarget next = target.next;234 235                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {236 237                         handled = true;238 239                     } else {240 241                         final boolean cancelChild = resetCancelNextUpFlag(target.child)242 243                         || intercepted;244 245                         if (dispatchTransformedTouchEvent(ev, cancelChild,246 247                                 target.child, target.pointerIdBits)) {248 249                             handled = true;250 251                         }252 253                         if (cancelChild) {254 255                             if (predecessor == null) {256 257                                 mFirstTouchTarget = next;258 259                             } else {260 261                                 predecessor.next = next;262 263                             }264 265                             target.recycle();266 267                             target = next;268 269                             continue;270 271                         }272 273                     }274 275                     predecessor = target;276 277                     target = next;278 279                 }280 281             }282 283  284 285             // Update list of touch targets for pointer up or cancel, if needed.286 287             if (canceled288 289                     || actionMasked == MotionEvent.ACTION_UP290 291                     || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {292 293                 resetTouchState();294 295             } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {296 297                 final int actionIndex = ev.getActionIndex();298 299                 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);300 301                 removePointersFromTouchTargets(idBitsToRemove);302 303             }304 305         }306 307  308 309         if (!handled && mInputEventConsistencyVerifier != null) {310 311             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);312 313         }314 315         return handled;316 317 }318 319   public boolean onInterceptTouchEvent(MotionEvent ev) {320 321         return false;322 323     }324 325   private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,326 327             View child, int desiredPointerIdBits) {328 329         final boolean handled;330 331  332 333         // Canceling motions is a special case.  We don't need to perform any transformations334 335         // or filtering.  The important part is the action, not the contents.336 337         final int oldAction = event.getAction();338 339         if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {340 341             event.setAction(MotionEvent.ACTION_CANCEL);342 343             if (child == null) {344 345                 handled = super.dispatchTouchEvent(event);346 347             } else {348 349                 handled = child.dispatchTouchEvent(event);350 351             }352 353             event.setAction(oldAction);354 355             return handled;356 357         }358 359  360 361         // Calculate the number of pointers to deliver.362 363         final int oldPointerIdBits = event.getPointerIdBits();364 365         final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;366 367  368 369         // If for some reason we ended up in an inconsistent state where it looks like we370 371         // might produce a motion event with no pointers in it, then drop the event.372 373         if (newPointerIdBits == 0) {374 375             return false;376 377         }378 379  380 381         // If the number of pointers is the same and we don't need to perform any fancy382 383         // irreversible transformations, then we can reuse the motion event for this384 385         // dispatch as long as we are careful to revert any changes we make.386 387         // Otherwise we need to make a copy.388 389         final MotionEvent transformedEvent;390 391         if (newPointerIdBits == oldPointerIdBits) {392 393             if (child == null || child.hasIdentityMatrix()) {394 395                 if (child == null) {396 397                     handled = super.dispatchTouchEvent(event);398 399                 } else {400 401                     final float offsetX = mScrollX - child.mLeft;402 403                     final float offsetY = mScrollY - child.mTop;404 405                     event.offsetLocation(offsetX, offsetY);406 407  408 409                     handled = child.dispatchTouchEvent(event);410 411  412 413                     event.offsetLocation(-offsetX, -offsetY);414 415                 }416 417                 return handled;418 419             }420 421             transformedEvent = MotionEvent.obtain(event);422 423         } else {424 425             transformedEvent = event.split(newPointerIdBits);426 427         }428 429  430 431         // Perform any necessary transformations and dispatch.432 433         if (child == null) {434 435             handled = super.dispatchTouchEvent(transformedEvent);436 437         } else {438 439             final float offsetX = mScrollX - child.mLeft;440 441             final float offsetY = mScrollY - child.mTop;442 443             transformedEvent.offsetLocation(offsetX, offsetY);444 445             if (! child.hasIdentityMatrix()) {446 447                 transformedEvent.transform(child.getInverseMatrix());448 449             }450 451  452 453             handled = child.dispatchTouchEvent(transformedEvent);454 455         }456 457  458 459         // Done.460 461         transformedEvent.recycle();462 463         return handled;464 465     }466 467  

代码量比较大,我们先概述一下各个函数的主要作用。

dispatchTouchEvent主要用来分发事件,函数主要作用是来决定当前的事件是交由自己消费处理,还是交由子控件处理。

onInterceptTouchEvent主要来决定当前控件是否需要拦截传递给子控件,如果返回True表示该控件拦截,并交由自己父类的dispatchTouchEvent处理消费,如果返回false表示不拦截,允许传递给子控件处理。

dispatchTransformedTouchEvent主要根据传来的子控件,决定是自身处理消费,还是交由子控件处理消费。

我们主要来分析一下dispatchTouchEvent函数:

 1     if (actionMasked == MotionEvent.ACTION_DOWN 2  3                     || mFirstTouchTarget != null) { 4  5                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 6  7                 if (!disallowIntercept) { 8  9                     intercepted = onInterceptTouchEvent(ev);10 11                     ev.setAction(action); // restore action in case it was changed12 13                 } else {14 15                     intercepted = false;16 17                 }18 19             } else {20 21                 // There are no touch targets and this action is not an initial down22 23                 // so this view group continues to intercept touches.24 25                 intercepted = true;26 27             }

这段代码,如果当前传递的事件是Down(按下)或者当前触摸链表不为空,那么它调用onInterceptTouchEvent函数,判断是否进行事件拦截处理,通过返回值来决定intercepted变量的值。

接下来if (!canceled && !intercepted){} 这个括号内的代码需要注意了,只有当intercepted返回值为false的时候,才满足这个条件进入代码段。因此,我们结合onInterceptTouchEvent源码,发现它默认值返回的是false,也就说如果你不重载onInterceptTouchEvent方法并令其返回True,它一定是返回false,并能够执行花括号内的代码。

我们分析一下花括号中的代码,if (actionMasked == MotionEvent.ACTION_DOWN

|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)

|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {}判断当前的事件是否是ACTION_DOWN、ACTION_POINTER_DOWN(多点触摸)、ACTION_HOVER_MOVE(悬停),如果是,执行花括号内代码,

final int childrenCount = mChildrenCount;

if (childrenCount != 0) {}判断当前控件是否有子控件,如果大于0,执行花括号内代码,

for (int i = childrenCount - 1; i >= 0; i--)遍历子控件,

if (!canViewReceivePointerEvents(child)

判断当前的down、POINTER_DOWN、HOVER_MOVE三个事件的坐标点是否落在了子控件上,如果落在子控件上,

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

通过dispatchTransformedTouchEvent传递事件,交由子控件判断是否传递或自己消费处理。如果dispatchTransformedTouchEvent返回true,表示子控件已消费处理,并添加此子控件View到触摸链表,并放置链表头,并结束遍历子控件。newTouchTarget = addTouchTarget(child, idBitsToAssign);false表示未处理。

接着分析

 1  if (mFirstTouchTarget == null) { 2  3                 handled = dispatchTransformedTouchEvent(ev, canceled, null, 4  5                         TouchTarget.ALL_POINTER_IDS); 6  7    } else { 8  9        ……10 11 }

mFirstTouchTarget什么时候为空呢?从前面的代码可以看到,如果onInterceptTouchEvent返回为false(也就是不拦截),mFirstTouchTarget就为空,直接交给自己父View执行dispatchTouchEvent去了。如果mFirstTouchTarget不为空,它就取出触摸链表,逐个遍历判断处理,如果前面比如Down事件处理过了,就不再处理了。

更多相关文章

  1. Android的布局方法
  2. Android(安卓)面试题总结之Android(安卓)基础(六)
  3. android2.x中android:layout_marginRight不起作用的解决办法
  4. (二)Android事件分发机制 - ViewGroup篇
  5. 布局属性
  6. 【Android】跑马灯效果(文字滚动)
  7. C#开发Android手机应用全接触(mono for android)
  8. android中的Selector的用法---主要是改变ListView和Button控件的
  9. Android(安卓)ListView中item之间的分割线

随机推荐

  1. Android Studio使用org.apache.http报错
  2. Android清空画布
  3. Android支持Smart Lock 人脸解锁
  4. update android api
  5. Android 特殊字符转义
  6. TextView支持的HTML标签及其他
  7. Android 开源项目分类汇总
  8. android 连接指定wifi
  9. MTK Android Driver鐭ヨ瘑澶у叏
  10. metasploit - exploits