<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # MotionEvent 詳解 Android MotionEvent 詳解,之前用了兩篇文章 [事件分發機制原理][customview/dispatch-touchevent-theory] 和 [事件分發機制詳解][customview/dispatch-touchevent-source] 來講解事件分發,而作為事件分發主角之一的 MotionEvent 并沒有過多的說明,本文就帶大家了解 MotionEvent 的相關內容,簡要介紹觸摸事件,主要包括 單點觸控、多點觸控、鼠標事件 以及 getAction() 和 getActionMasked() 的區別。 Android 將所有的輸入事件都放在了 MotionEvent 中,隨著安卓的不斷發展壯大,MotionEvent 也開始變得越來越復雜,下面是我自己整理的 MotionEvent 大事記: | 版本號 | 更新內容 | | :-------------------: | --------------------------- | | Android 1.0 (API 1 ) | 支持單點觸控和軌跡球的事件。 | | Android 1.6 (API 4 ) | 支持手勢。 | | Android 2.2 (API 8 ) | 支持多點觸控。 | | Android 3.1 (API 12) | 支持觸控筆,鼠標,鍵盤,操縱桿,游戲控制器等輸入工具。 | 以上僅僅是簡要的說明幾次比較大的變動,細小的修復和更新不計其數,此處就不一一列出了,反正也沒人關心這些東西。 MotionEvent 負責集中處理所有類型設備的輸入事件,但是由于某些設備使用的幾率較小本文會忽略講解,或者簡要講解,例如: 1、軌跡球只出現在最早的設備上,現代的設備上已經見不到了,本文不再敘述。 2、觸控筆和手指處理流程基本相同,不再多說。 3、鼠標在手機上使用概率也比較小,會在文末簡要介紹。 ## 單點觸控 單點觸控就非常簡單啦,入門的工程師都會用,上一篇文章也簡要介紹過,主要涉及以下幾個事件: | 事件 | 簡介 | | -------------- | ------------------------ | | ACTION_DOWN | 手指 **初次接觸到屏幕** 時觸發。 | | ACTION_MOVE | 手指 **在屏幕上滑動** 時觸發,會多次觸發。 | | ACTION_UP | 手指 **離開屏幕** 時觸發。 | | ACTION_CANCEL | 事件 **被上層攔截** 時觸發。 | | ACTION_OUTSIDE | 手指 **不在控件區域** 時觸發。 | 和以下的幾個方法: | 方法 | 簡介 | | :---------- | ---------------------- | | getAction() | 獲取事件類型。 | | getX() | 獲得觸摸點在當前 View 的 X 軸坐標。 | | getY() | 獲得觸摸點在當前 View 的 Y 軸坐標。 | | getRawX() | 獲得觸摸點在整個屏幕的 X 軸坐標。 | | getRawY() | 獲得觸摸點在整個屏幕的 Y 軸坐標。 | > 關于 `get` 和 `getRaw` 的區別可以參考這一篇文章 [安卓自定義View基礎-坐標系][customview/CoordinateSystem] 單點觸控一次簡單的交互流程是這樣的: **手指落下(ACTION_DOWN) -> 多次移動(ACTION_MOVE) -> 離開(ACTION_UP)** > * 本次事例中 ACTION_MOVE 有多次觸發。 > * 如果僅僅是單擊(手指按下再抬起),不會觸發 ACTION_MOVE。 ![單點觸摸事件流程](http://ww4.sinaimg.cn/large/005Xtdi2jw1f8oz1704ylg30bo0jqgmx.gif) 針對單點觸控的事件處理一般是這樣寫的: ```java @Override public boolean onTouchEvent(MotionEvent event) { // ▼ 注意這里使用的是 getAction(),先埋一個小尾巴。 switch (event.getAction()){ case MotionEvent.ACTION_DOWN: // 手指按下 break; case MotionEvent.ACTION_MOVE: // 手指移動 break; case MotionEvent.ACTION_UP: // 手指抬起 break; case MotionEvent.ACTION_CANCEL: // 事件被攔截 break; case MotionEvent.ACTION_OUTSIDE: // 超出區域 break; } return super.onTouchEvent(event); } ``` 相信小伙伴對此已經非常熟悉了,經常使用的東西,我也不啰嗦了。 但其中有兩個比較特殊的事件: `ACTION_CANCEL` 和 `ACTION_OUTSIDE` 。 為什么說特殊呢,因為它們是由程序觸發而產生的,而且觸發條件也非常特殊,通常情況下即便不處理這兩個事件也沒有什么問題。接下來我們就扒一扒它們的真面目: ### ACTION_CANCEL **`ACTION_CANCEL` 的觸發條件是事件被上層攔截**,然而我們在 [事件分發機制原理][customview/dispatch-touchevent-theory] 一文中了解到當事件被上層 View 攔截的時候,ChildView 是收不到任何事件的,ChildView 收不到任何事件,自然也不會收到 `ACTION_CANCEL` 了,所以說這個 `ACTION_CANCEL` 的正確觸發條件并不是這樣,那么是什么呢? **事實上,只有上層 View 回收事件處理權的時候,ChildView 才會收到一個 `ACTION_CANCEL` 事件。** 這樣說可能不太容易理解,咱舉個例子? ![](http://ww1.sinaimg.cn/large/005Xtdi2jw1f9auctayt4j302s03o744.jpg) > 例如:上層 View 是一個 RecyclerView,它收到了一個 `ACTION_DOWN` 事件,由于這個可能是點擊事件,所以它先傳遞給對應 ItemView,詢問 ItemView 是否需要這個事件,然而接下來又傳遞過來了一個 `ACTION_MOVE` 事件,且移動的方向和 RecyclerView 的可滑動方向一致,所以 RecyclerView 判斷這個事件是滾動事件,于是要收回事件處理權,這時候對應的 ItemView 會收到一個 `ACTION_CANCEL` ,并且不會再收到后續事件。 > > **通俗一點?** > > RecyclerView:兒砸,這里有一個 `ACTION_DOWN` 你看你要不要。 > ItemView :好嘞,我看看。 > RecyclerView:噫?居然是移動事件`ACTION_MOVE`,我要滾起來了,兒砸,我可能要把你送去你姑父家(緩存區)了,在這之前給你一個 `ACTION_CANCEL`,你要收好啊。 > ItemView :…... > > 這是實際開發中最有可能見到 `ACTION_CANCEL` 的場景了。 ### ACTION_OUTSIDE `ACTION_OUTSIDE`的觸發條件更加奇葩,從字面上看,outside 意思不就是超出區域么?然而不論你如何滑動超出控件區域都不會觸發 `ACTION_OUTSIDE` 這個事件。相信很多魔法師都對此很是疑惑,說好的超出區域呢? 實際上這個事件根本就不是在這里用的,看官方解釋(裝一下逼): > ?A movement has happened outside of the normal bounds of the UI element. This does not provide a full gesture, but only the initial location of the movement/touch. > > 一個觸摸事件已經發生了UI元素的正常范圍之外。因此不再提供完整的手勢,只提供 運動/觸摸 的初始位置。 我們知道,正常情況下,如果初始點擊位置在該視圖區域之外,該視圖根本不可能會收到事件,然而,萬事萬物都不是絕對的,肯定還有一些特殊情況,你可曾還記得點擊 Dialog 區域外關閉嗎?Dialog 就是一個特殊的視圖(沒有占滿屏幕大小的窗口),能夠接收到視圖區域外的事件(雖然在通常情況下你根本用不到這個事件),除了 Dialog 之外,你最可能看到這個事件的場景是懸浮窗,當然啦,想要接收到視圖之外的事件需要一些特殊的設置。 > 設置視圖的 WindowManager 布局參數的 flags為[`FLAG_WATCH_OUTSIDE_TOUCH`](http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_WATCH_OUTSIDE_TOUCH),這樣點擊事件發生在這個視圖之外時,該視圖就可以接收到一個 `ACTION_OUTSIDE` 事件。 > > 參見StackOverflow:[How to dismiss the dialog with click on outside of the dialog?](http://stackoverflow.com/questions/8384067/how-to-dismiss-the-dialog-with-click-on-outside-of-the-dialog) 由于這個事件用到的幾率比較小,此處就不展開敘述了,以后用到的時候再詳細講解。 ## 多點觸控 Android 在 2.2 版本的時候開始支持多點觸控,一旦出現了多點觸控,很多東西就突然之間變得麻煩起來了,首先要解決的問題就是 **多個手指同時按在屏幕上,會產生很多的事件,這些事件該如何區分呢?** 為了區分這些事件,工程師們用了一個很簡單的辦法--**編號,當手指第一次按下時產生一個唯一的號碼,手指抬起或者事件被攔截就回收編號,就這么簡單。** **第一次按下的手指特殊處理作為主指針,之后按下的手指作為輔助指針**,然后隨之衍生出來了以下事件(注意增加的事件和事件簡介的變化): | 事件 | 簡介 | | --------------------------- | ------------------------------ | | ACTION_DOWN | **第一個** 手指 **初次接觸到屏幕** 時觸發。 | | ACTION_MOVE | 手指 **在屏幕上滑動** 時觸發,會多次觸發。 | | ACTION_UP | **最后一個** 手指 **離開屏幕** 時觸發。 | | **ACTION_POINTER_DOWN** | 有非主要的手指按下(**即按下之前已經有手指在屏幕上**)。 | | **ACTION_POINTER_UP** | 有非主要的手指抬起(**即抬起之后仍然有手指在屏幕上**)。 | | 以下事件類型不推薦使用 | ------------------ | | ~~ACTION_POINTER\_1\_DOWN~~ | 第 2 個手指按下,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_2\_DOWN~~ | 第 3 個手指按下,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_3\_DOWN~~ | 第 4 個手指按下,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_1\_UP~~ | 第 2 個手指抬起,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_2\_UP~~ | 第 3 個手指抬起,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_3\_UP~~ | 第 4 個手指抬起,已廢棄,不推薦使用。 | 和以下方法: | 方法 | 簡介 | | ------------------------------- | ---------------------------------------- | | getActionMasked() | 與 `getAction()` 類似,**多點觸控必須使用這個方法獲取事件類型**。 | | getActionIndex() | 獲取該事件是哪個指針(手指)產生的。 | | getPointerCount() | 獲取在屏幕上手指的個數。 | | getPointerId(int pointerIndex) | 獲取一個指針(手指)的唯一標識符ID,在手指按下和抬起之間ID始終不變。 | | findPointerIndex(int pointerId) | 通過PointerId獲取到當前狀態下PointIndex,之后通過PointIndex獲取其他內容。 | | getX(int pointerIndex) | 獲取某一個指針(手指)的X坐標 | | getY(int pointerIndex) | 獲取某一個指針(手指)的Y坐標 | 由于多點觸控部分涉及內容比較多,也很復雜,我準備單獨用一篇文章進行詳細敘述,所以這里只敘述一些基礎的內容作為鋪墊: ### getAction() 與 getActionMasked() 當多個手指在屏幕上按下的時候,會產生大量的事件,如何在獲取事件類型的同時區分這些事件就是一個大問題了。 一般來說我們可以通過為事件添加一個int類型的index屬性來區分,但是我們知道谷歌工程師是有潔癖的(在 [自定義View分類與流程][customview/CustomViewProcess] 的onMeasure中已經見識過了),為了添加一個通常數值不會超過10的index屬性就浪費一個int大小的空間簡直是不能忍受的,于是工程師們將這個index屬性和事件類型直接合并了。 int類型共32位(0x00000000),他們用最低8位(0x000000**ff**)表示事件類型,再往前的8位(0x0000**ff**00)表示事件編號,以手指按下為例講解數值是如何合成的: > ACTION_DOWN 的默認數值為 (0x00000000) > ACTION_POINTER_DOWN 的默認數值為 (0x00000005) | 手指按下 | 觸發事件(數值) | | :-----: | :--------------------------------------- | | 第1個手指按下 | ACTION_DOWN (0x0000**00**00) | | 第2個手指按下 | ACTION_POINTER_DOWN (0x0000**01**05) | | 第3個手指按下 | ACTION_POINTER_DOWN (0x0000**02**05) | | 第4個手指按下 | ACTION_POINTER_DOWN (0x0000**03**05) | **注意:** 上面表格中用粗體標示出的數值,可以看到隨著按下手指數量的增加,這個數值也是一直變化的,進而導致我們使用 `getAction()` 獲取到的數值無法與標準的事件類型進行對比,為了解決這個問題,他們創建了一個 `getActionMasked()` 方法,這個方法可以清除index數值,讓其變成一個標準的事件類型。 **1、多點觸控時必須使用 `getActionMasked()` 來獲取事件類型。** **2、單點觸控時由于事件數值不變,使用 `getAction()` 和 `getActionMasked()` 兩個方法都可以。** **3、使用 getActionIndex() 可以獲取到這個index數值。不過請注意,getActionIndex() 只在 down 和 up 時有效,move 時是無效的。** 目前來說獲取事件類型使用 `getActionMasked()` 就行了,但是如果一定要編譯時兼容古董版本的話,可以考慮使用這樣的寫法: ```java final int action = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) ? event.getActionMasked() : event.getAction(); switch (action){ case MotionEvent.ACTION_DOWN: // TODO break; } ``` ### PointId 雖然前面剛剛說了一個 actionIndex,可以使用 getActionIndex() 獲得,但通過 actionIndex 字面意思知道,這個只表示事件的序號,而且根據其說明文檔解釋,這個 ActionIndex 只有在手指按下(down)和抬起(up)時是有用的,在移動(move)時是沒有用的,事件追蹤非常重要的一環就是移動(move),然而它卻沒卵用,這也太不實在了 ( ̄Д ̄)? **鄭重聲明:追蹤事件流,請認準 PointId,這是唯一官方指定標準,不要相信 ActionIndex 那個小婊砸。** PointId 在手指按下時產生,手指抬起或者事件被取消后消失,是一個事件流程中唯一不變的標識,可以在手指按下時 通過 `getPointerId(int pointerIndex)` 獲得。 (參數中的 pointerIndex 就是 actionIndex) 關于事件流的追蹤等問題在講解多點觸控時再詳細講解。 ## 歷史數據(批處理) 由于我們的設備非常靈敏,手指稍微移動一下就會產生一個移動事件,所以移動事件會產生的特別頻繁,為了提高效率,系統會將近期的多個移動事件(move)按照事件發生的順序進行排序打包放在同一個 MotionEvent 中,與之對應的產生了以下方法: | 事件 | 簡介 | | --------------------------------- | ---------------------------------------- | | getHistorySize() | 獲取歷史事件集合大小 | | getHistoricalX(int pos) | 獲取第pos個歷史事件x坐標<br/> (pos < getHistorySize()) | | getHistoricalY(int pos) | 獲取第pos個歷史事件y坐標<br/> (pos < getHistorySize()) | | getHistoricalX (int pin, int pos) | 獲取第pin個手指的第pos個歷史事件x坐標<br/> (pin < getPointerCount(), pos < getHistorySize() ) | | getHistoricalY (int pin, int pos) | 獲取第pin個手指的第pos個歷史事件y坐標<br/> (pin < getPointerCount(), pos < getHistorySize() ) | **注意:** 1. pin 全稱是 pointerIndex,表示第幾個手指,此處為了節省空間使用了縮寫。 2. 歷史數據只有 ACTION_MOVE 事件。 3. 歷史數據單點觸控和多點觸控均可以用。 下面是官方文檔給出的一個簡單使用示例: ```java void printSamples(MotionEvent ev) { final int historySize = ev.getHistorySize(); final int pointerCount = ev.getPointerCount(); for (int h = 0; h < historySize; h++) { System.out.printf("At time %d:", ev.getHistoricalEventTime(h)); for (int p = 0; p < pointerCount; p++) { System.out.printf(" pointer %d: (%f,%f)", ev.getPointerId(p), ev.getHistoricalX(p, h), ev.getHistoricalY(p, h)); } } System.out.printf("At time %d:", ev.getEventTime()); for (int p = 0; p < pointerCount; p++) { System.out.printf(" pointer %d: (%f,%f)", ev.getPointerId(p), ev.getX(p), ev.getY(p)); } } ``` ## 獲取事件發生的時間 獲取事件發生的時間。 | 方法 | 簡介 | | ------------------------------- | ------------ | | getDownTime() | 獲取手指按下時的時間。 | | getEventTime() | 獲取當前事件發生的時間。 | | getHistoricalEventTime(int pos) | 獲取歷史事件發生的時間。 | > 1. pos 表示歷史數據中的第幾個數據。( pos < getHistorySize() ) > 2. 返回值類型為 long,單位是毫秒。 ## 獲取壓力(接觸面積大小) MotionEvent支持獲取某些輸入設備(手指或觸控筆)的與屏幕的接觸面積和壓力大小,主要有以下方法: > 描述中使用了手指,觸控筆也是一樣的。 | 方法 | 簡介 | | ---------------------------------------- | ---------------------------- | | getSize () | 獲取第1個手指與屏幕接觸面積的大小 | | getSize (int pin) | 獲取第pin個手指與屏幕接觸面積的大小 | | getHistoricalSize (int pos) | 獲取歷史數據中第1個手指在第pos次事件中的接觸面積 | | getHistoricalSize (int pin, int pos) | 獲取歷史數據中第pin個手指在第pos次事件中的接觸面積 | | getPressure () | 獲取第一個手指的壓力大小 | | getPressure (int pin) | 獲取第pin個手指的壓力大小 | | getHistoricalPressure (int pos) | 獲取歷史數據中第1個手指在第pos次事件中的壓力大小 | | getHistoricalPressure (int pin, int pos) | 獲取歷史數據中第pin個手指在第pos次事件中的壓力大小 | > 1. pin 全稱是 pointerIndex,表示第幾個手指。(pin < getPointerCount() ) > 2. pos 表示歷史數據中的第幾個數據。( pos < getHistorySize() ) **注意:** **1、獲取接觸面積大小和獲取壓力大小是需要硬件支持的。** **2、非常不幸的是大部分設備所使用的電容屏不支持壓力檢測,但能夠大致檢測出接觸面積。** **3、大部分設備的 `getPressure()` 是使用接觸面積來模擬的。** **4、由于某些未知的原因(可能系統版本和硬件問題),某些設備不支持該方法。** 我用不同的設備對這兩個方法進行了測試,然而不同設備測試出來的結果不相同,之后經過我多方查證,發現是系統問題,有的設備上只有 `getSize()` 能用,有的設備上只有 `getPressure()` 能用,而有的則兩個都不能用。 **由于獲取接觸面積和獲取壓力大小受系統和硬件影響,使用的時候一定要進行數據檢測,以防因為設備問題而導致程序出錯。** ## 鼠標事件 由于觸控筆事件和手指事件處理流程大致相同,所以就不講解了,這里講解一下與鼠標相關的幾個事件: | 事件 | 簡介 | | ------------------ | ---------------------------------------- | | ACTION_HOVER_ENTER | 指針移入到窗口或者View區域,但沒有按下。 | | ACTION_HOVER_MOVE | 指針在窗口或者View區域移動,但沒有按下。 | | ACTION_HOVER_EXIT | 指針移出到窗口或者View區域,但沒有按下。 | | ACTION_SCROLL | 滾輪滾動,可以觸發水平滾動(AXIS_HSCROLL)或者垂直滾動(AXIS_VSCROLL) | 注意: 1、這些事件類型是 安卓4.0 (API 14) 才添加的。 2、使用 ` getActionMasked()` 獲得這些事件類型。 3、這些事件不會傳遞到 [onTouchEvent(MotionEvent)](https://developer.android.com/reference/android/view/View.html#onTouchEvent(android.view.MotionEvent)) 而是傳遞到 [onGenericMotionEvent(MotionEvent)](https://developer.android.com/reference/android/view/View.html#onGenericMotionEvent(android.view.MotionEvent)) 。 ## 輸入設備類型判斷 輸入設備類型判斷也是安卓4.0 (API 14) 才添加的,主要包括以下幾種設備: | 設備類型 | 簡介 | | ----------------- | ---- | | TOOL_TYPE_ERASER | 橡皮擦 | | TOOL_TYPE_FINGER | 手指 | | TOOL_TYPE_MOUSE | 鼠標 | | TOOL_TYPE_STYLUS | 手寫筆 | | TOOL_TYPE_UNKNOWN | 未知類型 | **使用 `getToolType(int pointerIndex)` 來獲取對應的輸入設備類型,pointIndex可以為0,但必須小于 `getPointerCount()`。** ## 總結 雖然本文標題是 MotionEvent 詳解,但由于 MotionEvent 實在太龐大了,本文只能涉及一些比較常用的內容,某些不太常用的內容就在以后用到的時候再詳細介紹吧,像游戲手柄等輸入設備由于我暫時不做游戲開發,也沒有過多了解,所以就不介紹給大家啦。 由于個人水平有限,文章中可能會出現錯誤,如果你覺得哪一部分有錯誤,或者發現了錯別字等內容,歡迎在評論區告訴我,另外,據說關注 [作者微博](http://weibo.com/GcsSloop) 不僅能第一時間收到新文章消息,還能變帥哦。 ## 參考資料 [MotionEvent ](https://developer.android.com/reference/android/view/MotionEvent.html) [Android MotionEvent詳解](http://www.jianshu.com/p/0c863bbde8eb) ## About Me ### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a> <a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a> [customview/CoordinateSystem]: http://www.gcssloop.com/customview/CoordinateSystem "安卓自定義View基礎-坐標系" [customview/CustomViewProcess]: http://www.gcssloop.com/customview/CustomViewProcess "安卓自定義View進階-分類與流程" [customview/dispatch-touchevent-theory]: http://www.gcssloop.com/customview/dispatch-touchevent-theory "安卓自定義View進階-事件分發機制原理" [customview/dispatch-touchevent-source]: http://www.gcssloop.com/customview/dispatch-touchevent-source "安卓自定義View進階-事件分發機制詳解"
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看