zoukankan      html  css  js  c++  java
  • Android事件分发机制详解

    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事件相关方法 功能说明 Activity ViewGroup View
    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中对于事件的处理才是最快捷的解决问题的方式。

  • 相关阅读:
    thinkphp5 tp5 命名空间 报错 Namespace declaration statement has to be the very first statement in the script
    开启 php 错误 提示 php-fpm 重启 nginx 500错误 解决办法 wdlinux lnmp 一键包 php脚本无法解析执行
    js 设置 cookie 定时 弹出层 提示层 下次访问 不再显示 弹窗 getCookie setCookie setTimeout
    php 二维数组 转字符串 implode 方便 mysql in 查询
    nginx 重启 ps -ef|grep nginx kill -HUP 主进程号
    jquery bootstrap help-block input 表单 提示 帮助 信息
    jquery 倒计时 60秒 短信 验证码 js ajax 获取
    jQuery如何获取同一个类标签的所有的值 遍历
    linux下C语言文件操作相关函数
    gcc,gdb用法
  • 原文地址:https://www.cnblogs.com/miaozhenzhong/p/5930963.html
Copyright © 2011-2022 走看看