1. 程式人生 > >Android觸控式螢幕事件派發機制詳解與原始碼分析一(View篇)

Android觸控式螢幕事件派發機制詳解與原始碼分析一(View篇)

【工匠若水 http://blog.csdn.net/yanbober

Notice:閱讀完該篇之後如果想繼續深入閱讀Android觸控式螢幕事件派發機制詳解與原始碼分析下一篇請點選《Android觸控式螢幕事件派發機制詳解與原始碼分析二(ViewGroup篇)》檢視。

1 背景

最近在簡書和微博還有Q群看見很多人說Android自定義控制元件(View/ViewGroup)如何學習?為啥那麼難?其實答案很簡單:“基礎不牢,地動山搖。”

不扯蛋了,進入正題。就算你不自定義控制元件,你也必須要了解Android控制元件的觸控式螢幕事件傳遞機制(之所以說觸控式螢幕是因為該系列以觸控式螢幕的事件機制分析為主,對於類似TV裝置等的物理事件機制的分析雷同但有區別。哈哈,誰讓我之前是做Android TV BOX的,悲催!),只有這樣才能將你的控制元件事件運用的如魚得水。接下來的控制元件觸控式螢幕事件傳遞機制分析依據Android 5.1.1原始碼(API 22)。

2 基礎例項現象

2-1 例子

從一個例子分析說起吧。如下是一個很簡單不過的Android例項: 
這裡寫圖片描述

<code class="language-xml hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-pi" style="color: rgb(0, 102, 102); box-sizing: border-box;"><?xml version="1.0" encoding="utf-8"?></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">LinearLayout</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">xmlns:android</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:orientation</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"vertical"</span>
    <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:gravity</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"center"</span>
    <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:layout_width</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"fill_parent"</span>
    <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:layout_height</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"fill_parent"</span>
    <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:id</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@+id/mylayout"</span>></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">Button
</span>        <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:id</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@+id/my_btn"</span>
        <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:layout_width</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"match_parent"</span>
        <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:layout_height</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"wrap_content"</span>
        <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:text</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"click test"</span>/></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">LinearLayout</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>
<code class="language-android hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">ListenerActivity</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Activity</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">implements</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">View</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">OnTouchListener</span>, <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">View</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">OnClickListener</span> {</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> LinearLayout mLayout;
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> Button mButton;

    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onCreate</span>(Bundle savedInstanceState) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mLayout = (LinearLayout) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>.findViewById(R.id.mylayout);
        mButton = (Button) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>.findViewById(R.id.my_btn);

        mLayout.setOnTouchListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
        mButton.setOnTouchListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);

        mLayout.setOnClickListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
        mButton.setOnClickListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
    }

    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> <span class="hljs-title" style="box-sizing: border-box;">onTouch</span>(View v, MotionEvent event) {
        Log.i(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"OnTouchListener--onTouch-- action="</span>+event.getAction()+<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">" --"</span>+v);
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
    }

    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onClick</span>(View v) {
        Log.i(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"OnClickListener--onClick--"</span>+v);
    }
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li></ul>

2-2 現象

如上程式碼很簡單,但凡學過幾天Android的人都能看懂吧。Activity中有一個LinearLayout(ViewGroup的子類,ViewGroup是View的子類)佈局,佈局中包含一個按鈕(View的子類);然後分別對這兩個控制元件設定了Touch與Click的監聽事件,具體執行結果如下:

  1. 當穩穩的點選Button時列印如下: 
    這裡寫圖片描述
  2. 當穩穩的點選除過Button以外的其他地方時列印如下: 
    這裡寫圖片描述
  3. 當收指點選Button時按在Button上晃動了一下鬆開後的列印如下: 
    這裡寫圖片描述

機智的你看完這個結果指定知道為啥吧? 
我們看下onTouch和onClick,從引數都能看出來onTouch比onClick強大靈活,畢竟多了一個event引數。這樣onTouch裡就可以處理ACTION_DOWN、ACTION_UP、ACTION_MOVE等等的各種觸控。現在來分析下上面的列印結果;在1中,當我們點選Button時會先觸發onTouch事件(之所以列印action為0,1各一次是因為按下擡起兩個觸控動作被觸發)然後才觸發onClick事件;在2中也同理類似1;在3中會發現onTouch被多次調運後才調運onClick,是因為手指晃動了,所以觸發了ACTION_DOWN->ACTION_MOVE…->ACTION_UP。

如果你眼睛比較尖你會看見onTouch會有一個返回值,而且在上面返回了false。你可能會疑惑這個返回值有啥效果?那就驗證一下吧,我們將上面的onTouch返回值改為ture。如下:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> <span class="hljs-title" style="box-sizing: border-box;">onTouch</span>(View v, MotionEvent event) {
        Log.i(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"OnTouchListener--onTouch-- action="</span>+event.getAction()+<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">" --"</span>+v);
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

再次點選Button結果如下: 
這裡寫圖片描述 
看見了吧,如果onTouch返回true則onClick不會被調運了。

2-3 總結結論

好了,經過這個簡單的例項驗證你可以總結髮現:

  1. Android控制元件的Listener事件觸發順序是先觸發onTouch,其次onClick。
  2. 如果控制元件的onTouch返回true將會阻止事件繼續傳遞,返回false事件會繼續傳遞。

對於伸手黨碼農來說其實到這足矣應付常規的App事件監聽處理使用開發了,但是對於複雜的事件監聽處理或者想自定義控制元件的碼農來說這才是剛剛開始,只是個熱身。既然這樣那就繼續嘍。。。

3 Android 5.1.1(API 22) View觸控式螢幕事件傳遞原始碼分析

3-1 寫在前面的話

其實Android原始碼無論哪個版本對於觸控式螢幕事件的傳遞機制都類似,這裡只是選用了目前最新版本的原始碼來分析而已。分析Android View事件傳遞機制之前有必要先看下原始碼的一些關係,如下是幾個繼承關係圖: 
這裡寫圖片描述 
這裡寫圖片描述

怎麼樣?看了官方這個繼承圖是不是明白了上面例子中說的LinearLayout是ViewGroup的子類,ViewGroup是View的子類,Button是View的子類關係呢?其實,在Android中所有的控制元件無非都是ViewGroup或者View的子類,說高尚點就是所有控制元件都是View的子類。

這裡通過繼承關係是說明一切控制元件都是View,同時View與ViewGroup又存在一些區別,所以該模組才只單單先分析View觸控式螢幕事件傳遞機制。

3-2 從View的dispatchTouchEvent方法說起

在Android中你只要觸控控制元件首先都會觸發控制元件的dispatchTouchEvent方法(其實這個方法一般都沒在具體的控制元件類中,而在他的父類View中),所以我們先來看下View的dispatchTouchEvent方法,如下:

<code class="language-android hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> boolean <span class="hljs-title" style="box-sizing: border-box;">dispatchTouchEvent</span>(MotionEvent <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>) {
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// If the event should be handled by accessibility focus first.</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.isTargetAccessibilityFocus()) {
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// We don't have focus or no virtual descendant has it, do not handle the event.</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!isAccessibilityFocusedViewOrHost()) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
            }
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// We have focus and got the event, then use normal event dispatch.</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.setTargetAccessibilityFocus(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>);
        }

        boolean result = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mInputEventConsistencyVerifier != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
            mInputEventConsistencyVerifier.onTouchEvent(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>);
        }

        final <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> actionMasked = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getActionMasked();
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (actionMasked == MotionEvent.ACTION_DOWN) {
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Defensive cleanup for new gesture</span>
            stopNestedScroll();
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (onFilterTouchEventForSecurity(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>)) {
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//noinspection SimplifiableIfStatement</span>
            ListenerInfo li = mListenerInfo;
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (li != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> && li.mOnTouchListener != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>)) {
                result = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
            }

            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!result && onTouchEvent(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>)) {
                result = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
            }
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!result && mInputEventConsistencyVerifier != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
            mInputEventConsistencyVerifier.onUnhandledEvent(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>);
        }

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Clean up after nested scrolls if this is the end of a gesture;</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// also cancel it if we tried an ACTION_DOWN but we didn't want the rest</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// of the gesture.</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> result;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li></ul>

dispatchTouchEvent的程式碼有點長,咱們看重點就可以。前面都是設定一些標記和處理input與手勢等傳遞,到24行的if (onFilterTouchEventForSecurity(event))語句判斷當前View是否沒被遮住等,接著26行定義ListenerInfo區域性變數,ListenerInfo是View的靜態內部類,用來定義一堆關於View的XXXListener等方法;接著if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))語句就是重點,首先li物件自然不會為null,li.mOnTouchListener呢?你會發現ListenerInfo的mOnTouchListener成員是在哪兒賦值的呢?怎麼確認他是不是null呢?通過在View類裡搜尋可以看到:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
     * Register a callback to be invoked when a touch event is sent to this view.
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> l the touch listener to attach to this view
     */</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">setOnTouchListener</span>(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>

li.mOnTouchListener是不是null取決於控制元件(View)是否設定setOnTouchListener監聽,在上面的例項中我們是設定過Button的setOnTouchListener方法的,所以也不為null;接著通過位與運算確定控制元件(View)是不是ENABLED 的,預設控制元件都是ENABLED 的;接著判斷onTouch的返回值是不是true。通過如上判斷之後如果都為true則設定預設為false的result為true,那麼接下來的if (!result && onTouchEvent(event))就不會執行,最終dispatchTouchEvent也會返回true。而如果if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))語句有一個為false則if (!result && onTouchEvent(event))就會執行,如果onTouchEvent(event)返回false則dispatchTouchEvent返回false,否則返回true。

這下再看前面的例項部分明白了吧?控制元件觸控就會調運dispatchTouchEvent方法,而在dispatchTouchEvent中先執行的是onTouch方法,所以驗證了例項結論總結中的onTouch優先於onClick執行道理。如果控制元件是ENABLE且在onTouch方法裡返回了true則dispatchTouchEvent方法也返回true,不會再繼續往下執行;反之,onTouch返回false則會繼續向下執行onTouchEvent方法,且dispatchTouchEvent的返回值與onTouchEvent返回值相同。

所以依據這個結論和上面例項列印結果你指定已經大膽猜測認為onClick一定與onTouchEvent有關係?是不是呢?先告訴你,是的。下面我們會分析。

3-2-1 總結結論

在View的觸控式螢幕傳遞機制中通過分析dispatchTouchEvent方法原始碼我們會得出如下基本結論:

  1. 觸控控制元件(View)首先執行dispatchTouchEvent方法。
  2. 在dispatchTouchEvent方法中先執行onTouch方法,後執行onClick方法(onClick方法在onTouchEvent中執行,下面會分析)。
  3. 如果控制元件(View)的onTouch返回false或者mOnTouchListener為null(控制元件沒有設定setOnTouchListener方法)或者控制元件不是enable的情況下會調運onTouchEvent,dispatchTouchEvent返回值與onTouchEvent返回一樣。
  4. 如果控制元件不是enable的設定了onTouch方法也不會執行,只能通過重寫控制元件的onTouchEvent方法處理(上面已經處理分析了),dispatchTouchEvent返回值與onTouchEvent返回一樣。
  5. 如果控制元件(View)是enable且onTouch返回true情況下,dispatchTouchEvent直接返回true,不會呼叫onTouchEvent方法。

上面說了onClick一定與onTouchEvent有關係,那麼接下來就分析分析dispatchTouchEvent方法中的onTouchEvent方法。

3-3 繼續說說View的dispatchTouchEvent方法中調運的onTouchEvent方法

上面說了dispatchTouchEvent方法中如果onTouch返回false或者mOnTouchListener為null(控制元件沒有設定setOnTouchListener方法)或者控制元件不是enable的情況下會調運onTouchEvent,所以接著看就知道了,如下:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> <span class="hljs-title" style="box-sizing: border-box;">onTouchEvent</span>(MotionEvent event) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x = event.getX();
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y = event.getY();
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> viewFlags = mViewFlags;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((viewFlags & ENABLED_MASK) == DISABLED) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
                setPressed(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>);
            }
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// A disabled view that is clickable still consumes the touch</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// events, it just doesn't respond to them.</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mTouchDelegate != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mTouchDelegate.onTouchEvent(event)) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
            }
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">switch</span> (event.getAction()) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> MotionEvent.ACTION_UP:
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>;
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((mPrivateFlags & PFLAG_PRESSED) != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> || prepressed) {
                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// take focus if we don't have it already and we should in</span>
                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// touch mode.</span>
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> focusTaken = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (prepressed) {
                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// The button is being released before we actually</span>
                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// showed it as pressed.  Make it show the pressed</span>
                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// state now (before scheduling the click) to ensure</span>
                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// the user sees it.</span>
                            setPressed(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>, x, y);
                       }

                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!mHasPerformedLongPress) {
                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// This is a tap, so remove the longpress check</span>
                            removeLongPressCallback();

                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Only perform take click actions if we were in the pressed state</span>
                            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!focusTaken) {
                                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Use a Runnable and post this rather than calling</span>
                                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// performClick directly. This lets other visual state</span>
                                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// of the view update before click actions start.</span>
                                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mPerformClick == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                                    mPerformClick = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> PerformClick();
                                }
                                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mUnsetPressedState == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                            mUnsetPressedState = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> UnsetPressedState();
                        }

                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!post(mUnsetPressedState)) {
                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// If the post failed, unpress right now</span>
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;

                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;

                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (performButtonActionOnTouchDown(event)) {
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
                    }

                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Walk up the hierarchy to determine if we're inside a scrolling container.</span>
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> isInScrollingContainer = isInScrollingContainer();

                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// For views inside a scrolling container, delay the pressed feedback for</span>
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// a short period in case this is a scroll.</span>
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mPendingCheckForTap == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                            mPendingCheckForTap = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Not inside a scrolling container, so show the feedback right away</span>
                        setPressed(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>, x, y);
                        checkForLongClick(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>);
                    }
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;

                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> MotionEvent.ACTION_CANCEL:
                    setPressed(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>);
                    removeTapCallback();
                    removeLongPressCallback();
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;

                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Be lenient about moving outside of buttons</span>
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!pointInView(x, y, mTouchSlop)) {
                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Outside button</span>
                        removeTapCallback();
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((mPrivateFlags & PFLAG_PRESSED) != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
                            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Remove any future long press/tap checks</span>
                            removeLongPressCallback();

                            setPressed(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>);
                        }
                    }
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
            }

            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding