您的位置:首页 > 移动开发 > Android开发

Android事件分发机制详解

2016-09-08 13:58 417 查看

1. 概述

       Android日常研发时,与View接触占据相当多的时间,而关于View的知识,主要集中在View的绘制和View对于点击事件的处理。关于View的绘制过程,可以查看一下这篇文章的介绍;关于View处理点击事件,可能有人会认为在
onTouchEvent()
这个方法处理点击事件就行了,不错,具体的处理过程确实是在这个方法中,但是点击事件在View间是怎么分发的?怎么确定当前View想要处理点击事件?这些问题在本篇文章中都会一一解决。

       点击事件的分发,在具体的View中是有细小差别的,比如说LinearLayout中事件的分发和RelativeLayout中事件的分发就有一些不同的地方。所以本篇只介绍一个通用的事件分发机制,不针对具体View的源代码进行分析。

2. 点击事件类型

点击事件类型有好多种,参考
MotionEvent
类,在这里主要介绍四种常见的事件类型。

类型说明
ACTION_DOWN手指接触到屏幕事件
ACTION_UP手指抬起离开屏幕事件
ACTION_MOVE手指在屏幕上面滑动事件
ACTION_CANCEL点击事件由于某种原因取消,比如说手指滑到屏幕外
       一个正常完整的点击事件一般是从
ACTION_DOWN
事件(手指接触到屏幕)开始,到
ACTION_UP
事件(手指抬起离开屏幕)结束,中间可以有滑动操作,如下所示。

ACTION_DOWN —> (ACTION_MOVE —> ACTION_MOVE —> ACTION_MOVE) —> ACTION_UP


注意手指滑动事件是一个连续的事件,在滑动过程中会一直触发,并不是只触发一次。

3. 方法介绍

点击事件分发机制中,一般会由三个方法控制,并不是上面所说的关注
onTouchEvent()
方法就行了。

dispatchTouchEvent(),事件的分发方法,一般由父布局调用,将点击事件传递到子View。返回true,代表事件被消费;返回false,表示事件未被消费,事件会继续传递下去。
onInterceptTouchEvent(),是否拦截点击事件,如果返回true,表示拦截事件,调用自身
onTouchEvent()
处理点击事件;如果返回false,不拦截点击事件,则将点击事件传递到子View。
onTouchEvent(),处理点击事件的具体方法
注意:View类和Activity类中仅仅有dispatchTouchEvent()和onTouchEvent()两个方法,并没有onInterceptTouchEvent()方法;上述三个方法在ViewGroup中都存在

注意:此文中”消费”一词的意思并不是指调用了onTouchEvent()方法,而是指onTouchEvent()方法或者OnTouchListener()返回了true,表示事件被消费

4. 点击事件分发过程

4.1 Activity事件处理

       最先获取点击事件的是Activity,也就是所有View获取到的点击事件都是由Activity传递下去的,Activity会调用顶层的ViewGroup的
dispatchTouchEvent()
方法,将事件分发给ViewGroup。看一下Activity这块的源代码。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {
<span class="hljs-keyword">if</span> (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
<span class="hljs-keyword">if</span> (getWindow().superDispatchTouchEvent(ev)) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
<span class="hljs-keyword">return</span> onTouchEvent(ev);
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>

       当接收到
ACTION_DOWN
事件时候,调用了自身的
onUserInteraction()
方法,这个方法是个空方法,然后通过调用顶层ViewGroup的
dispatchTouchEvent ()
方法将事件传递给ViewGroup。如果在ViewGroup中将事件消费了,就直接返回true;如果没有消费,最终会调用Activity的
onTouchEvent()
方法。也正如上面所说Activity中没有
onInterceptTouchEvent()
方法。

4.2 ViewGroup事件处理

       ViewGroup获取到点击事件后处理过程会根据具体的ViewGroup有不同的操作,这个过程都有一些细小的差别,但是大体思路是不变的,用伪代码来描述ViewGroup处理点击事件的过程。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {
<span class="hljs-comment">// 如果拦截点击事件,不将该事件传递给子View,同时调用本身的onTouchEvent方法来处理事件,并返回布尔值标记是否消费该事件,</span>
<span class="hljs-keyword">if</span> (onInterceptTouchEvent(ev)) {
<span class="hljs-keyword">return</span> onTouchEvent(ev);
}
<span class="hljs-comment">// 如果不拦截点击事件,将事件传递给子View,并返回处理结果的布尔值标记是否消费该事件</span>
<span class="hljs-keyword">if</span> (isConsumed = child.dispatchTouchEvent(ev)){
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
<span class="hljs-comment">// 如果子View也没有消费事件,事件又会从底层向上层传递,最终到activity</span>
<span class="hljs-keyword">return</span> onTouchEvent(ev);
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul>

       ViewGroup拿到点击事件后,首先会调用
onInterceptTouchEvent()
方法来判断是否需要拦截该事件,防止该事件继续传递到子View。如果需要拦截该事件,则返回true,调用本身的
onTouchEvent()
方法处理该事件;如果不需要拦截该事件,调用子View的
dispatchTouchEvent()
方法,将事件传递给子View。如果存在多层ViewGroup嵌套,事件的传递过程也是同样的。

4.3 View事件处理

将事件传递给最底层的View后,例如TextView,看看内部是怎么处理的,源代码太多,截取了比较重要的一段源代码,如下。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent event) {
<span class="hljs-comment">// 其他逻辑</span>
....

<span class="hljs-comment">// 过滤一些不必要的事件</span>
<span class="hljs-keyword">if</span> (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
<span class="hljs-keyword">if</span> (li != <span class="hljs-keyword">null</span> && li.mOnTouchListener != <span class="hljs-keyword">null</span>
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(<span class="hljs-keyword">this</span>, event)) {
result = <span class="hljs-keyword">true</span>;
}

<span class="hljs-keyword">if</span> (!result && onTouchEvent(event)) {
result = <span class="hljs-keyword">true</span>;
}
}

<span class="hljs-comment">// 其他逻辑</span>
....

<span class="hljs-keyword">return</span> result;
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li></ul>

       贴这一段的源代码主要是为了查看
OnTouchListener
相对于
onToucheEvent()
的优先级,可以看见,如果View设置了
OnTouchListener
,并且它消费了点击事件,就不会调用View的
onToucheEvent()
方法,顺便说一下
OnClickListener
会在
onTouchEvent()
方法内部被调用。整个调用的优先级如下。

OnTouchListener > onToucheEvent() > OnClickListener


4.4 点击事件分发小结

       上面对Activity、ViewGroup、View对事件的处理过程进行了详细的介绍,主要集中于
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
三个方法,从下面这个列表来总结一下。

Touch事件相关方法功能说明ActivityViewGroupView
dispatchTouchEvent()事件分发,返回true,代表事件已经被消费;返回false,代表事件未被消费,事件会回传给父布局处理。有该方法有改方法有该方法
onInterceptTouchEvent()事件拦截,返回true,代表当前控件要处理事件;返回false,代表当前控件不拦截事件,事件将会下发给子View无该方法有该方法无该方法
onTouchEvent()事件处理,返回true,代表事件被消费;返回false,代表事件未被消费有该方法有该方法有该方法

5. Touch日志分析

新建一个Activity,复写里面的
dispatchTouchEvent()
onTouchEvent()
方法,仅仅添加日志,不做其他任何处理。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TouchDemoActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span>

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(@Nullable Bundle savedInstanceState) {
<span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch);
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"activity dispatchTouchEvent, ev : "</span> + MotionEvent.actionToString(ev.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.dispatchTouchEvent(ev);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"activity onTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.onTouchEvent(event);
<span class="hljs-keyword">return</span> result;
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li></ul>

Activity的布局文件

<code class="language-java hljs  has-numbering"><?xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"utf-8"</span>?>
<LinearLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
android:orientation=<span class="hljs-string">"vertical"</span>
android:layout_width=<span class="hljs-string">"match_parent"</span>
android:layout_height=<span class="hljs-string">"match_parent"</span>>
<com.wang.demo.touch.view.TouchLayout
android:id=<span class="hljs-string">"@+id/touch_layout"</span>
android:layout_width=<span class="hljs-string">"match_parent"</span>
android:layout_height=<span class="hljs-string">"match_parent"</span>>
<com.wang.demo.touch.view.TouchView
android:layout_width=<span class="hljs-string">"match_parent"</span>
android:layout_height=<span class="hljs-string">"match_parent"</span>/>
</com.wang.demo.touch.view.TouchLayout>
</LinearLayout></code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li></ul>

TouchLayout继承LinearLayout,并复写了
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
三个方法,在这三个方法里面添加日志。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TouchLayout</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LinearLayout</span> {</span>

<span class="hljs-comment">// 构造方法</span>

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout dispatchTouchEvent, ev : "</span> + MotionEvent.actionToString(ev.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.dispatchTouchEvent(ev);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onInterceptTouchEvent</span>(MotionEvent ev) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout onInterceptTouchEvent, ev : "</span> + MotionEvent.actionToString(ev.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.onInterceptTouchEvent(ev);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {

<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout onTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.onTouchEvent(event);
<span class="hljs-keyword">return</span> result;
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li></ul>

TouchView直接继承View,并复写了
dispatchTouchEvent()
onTouchEvent()
方法

,添加日志。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TouchView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">View</span> {</span>

<span class="hljs-comment">// 构造方法</span>

<span class="hljs-annotation">@TargetApi</span>(Build.VERSION_CODES.LOLLIPOP)
<span class="hljs-keyword">public</span> <span class="hljs-title">TouchView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr, <span class="hljs-keyword">int</span> defStyleRes) {
<span class="hljs-keyword">super</span>(context, attrs, defStyleAttr, defStyleRes);
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"view dispatchTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.dispatchTouchEvent(event);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"view onTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.onTouchEvent(event);
<span class="hljs-keyword">return</span> result;
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li></ul>

5.1 事件未被消费

上面代码中,都是调用控件的默认方法对事件处理,也就是说,事件并没有被我们消费。点击屏幕并滑动最后抬起手指,看一下日志输出。



图-1 事件未被消费-日志
标记出三个区域。

第一个区域,是事件从Activity传递到底层View的过程,在这个过程中,是没有控件对点击事件进行消费的。
第二个区域,事件从上到下没有被消费,所以事件又会从底层View回传到ViewGroup并且最终会回传到Activity。
第三个区域,如果控件对第一个获取的事件(ACTION_DOWN)没有进行处理的话,后续的事件(ACTION_MOVE,ACTION_UP等等)就不会传递到这个控件,所以第三个区域的日志都在Activity这一层处理了,并没有向下层传递。
事件传递示图如下所示。



图-2 事件未被消费-事件传递图

5.2 事件被ViewGroup拦截且消费

改动一下
TouchLayout
中的
onInterceptTouchEvent()
方法让它返回true,表示拦截事件。修改后文件如下。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TouchLayout</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LinearLayout</span> {</span>

<span class="hljs-comment">// 构造函数</span>

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout dispatchTouchEvent, ev : "</span> + MotionEvent.actionToString(ev.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.dispatchTouchEvent(ev);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onInterceptTouchEvent</span>(MotionEvent ev) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout onInterceptTouchEvent, ev : "</span> + MotionEvent.actionToString(ev.getAction()));
}
<span class="hljs-comment">// 返回true,拦截事件</span>
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {

<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout onTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-comment">// 返回true,事件被消费</span>
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li></ul>

运行一下,日志如下。



图-3 事件被拦截并消费-日志
标记出两个区域。

第一个区域,事件正常由Activity向下传递,到ViewGroup层后,事件被拦截,ViewGroup直接调用自身的
onTouchEvent()
方法消费该事件
第二个区域,因为ViewGroup消费了最初的事件,所以后续的事件直接传递到了该ViewGroup并进行处理
事件传递如下,黑色线是第一次事件传递过程,红色线为后续事件传递过程。



图-4 事件被拦截并消费-事件传递图

5.3 事件由最底层的View消费

复原
TouchLayout
类,修改
TouchView
中的
onTouchEvent()
方法,让它返回true,表示事件被消费。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TouchView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">View</span> {</span>

<span class="hljs-comment">// 构造函数</span>

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"view dispatchTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.dispatchTouchEvent(event);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"view onTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul>

运行日志如下。



图-5 事件被底层View消费-日志
从日志中可以看见,事件正常由Activity向下传递,最终传递到底层View,并被消费。

这种情况的事件传递如下。



图-6 事件被底层View消费-事件传递图

5.4 ViewGroup设置了OnTouchListener、OnClickListener

DemoMainActivity
中设置
TouchLayout
的OnTouchListener。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TouchDemoActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span>

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(@Nullable Bundle savedInstanceState) {
<span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch);

findViewById(R.id.touch_layout).setOnTouchListener(<span class="hljs-keyword">new</span> View.OnTouchListener() {
<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouch</span>(View v, MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout OnTouchListener, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
});

findViewById(R.id.touch_layout).setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() {
<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span>(View v) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"layout OnClickListener"</span>);
}
});
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"activity dispatchTouchEvent, ev : "</span> + MotionEvent.actionToString(ev.getAction()));
}

<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.dispatchTouchEvent(ev);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"activity onTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.onTouchEvent(event);
<span class="hljs-keyword">return</span> result;
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li></ul>

同时设置
TouchView
中的
onTouchEvent()
返回true,表示消费了该事件。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TouchView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">View</span> {</span>

<span class="hljs-comment">// 构造函数</span>

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"view dispatchTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">boolean</span> result = <span class="hljs-keyword">super</span>.dispatchTouchEvent(event);
<span class="hljs-keyword">return</span> result;
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.d(Constants.LOG_TAG_TOUCH, <span class="hljs-string">"view onTouchEvent, ev : "</span> + MotionEvent.actionToString(event.getAction()));
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul>

查看一下事件日志。



图-7 事件分发机制图
可以看见,虽然ViewGroup设置了OnTouchListener,但是事件是被最底层的View消费的。

我们再设置
TouchView
中的
onTouchEvent()
返回false,不让最底层的View消费事件。查看一下运行日志



图-8 事件分发机制图
       从日志中看,确实是ViewGroup的OnTouchListener将事件消费了,但是为什么没有调用OnClickListenr呢?是因为OnTouchListener高于OnTouchEvent,而OnClickListener的调用时在OnTouchEvent内部,OnTouchListener将事件消费后,并不会调用OnTouchEvent,自然也不会调用OnClickListener了。

5.4 小结

从上面日志中,可以得出一些结论,帮助我们加深对事件分发的理解。

事件虽然是从Activity向底层View传递,在不考虑ViewGroup拦截事件的情况下,最先处理事件(onTouchEvent)的是底层View,如果事件未被底层View消费,事件将会回传给上层的ViewGroup处理(onTouchEvent),若所有的ViewGroup都未消费事件,事件最终会回传到Activity由它做最后的处理(onTouchEvent)。
事件在传递过程中,如果被ViewGroup拦截(onInterceptTouchEvent),该ViewGroup会优先处理该事件。
底层的View或者ViewGroup如果将事件消费了,上层的ViewGroup的OnTouchListener、OnTouchEvetn,OnClickListener都不会被调用。
在同一个View或者ViewGroup的事件处理中,OnTouchListener优先级最高,OnTouchEvent其次,OnClickListener最低。

6. 总结

       文章大体介绍了Android的事件分发机制,并没有针对具体的源代码进行讲解,主要是不同的ViewGroup对于事件的处理在细节上有许多不同,但是在事件处理的大体思路上还是一致的。

而在平时中遇见关于事件处理的问题,去查看具体的View或者ViewGroup中对于事件的处理才是最快捷的解决问题的方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  事件分发机制