# 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。

針對單點觸控的事件處理一般是這樣寫的:
```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` 事件。**
這樣說可能不太容易理解,咱舉個例子?

> 例如:上層 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進階-事件分發機制詳解"
- 0-發現
- AndroidInterview-Q-A
- Android能讓你少走彎路的干貨整理
- LearningNotes
- temp
- temp11
- 部分地址
- 0-待辦任務
- 待補充列表
- 0-未分類
- AndroidView事件分發與滑動沖突處理
- Spannable
- 事件分發機制詳解
- 1-Java
- 1-Java-01基礎
- 未歸檔
- 你應該知道的JDK知識
- 集合框架
- 1-Java-04合集
- Java之旅0
- Java之旅
- JAVA之旅01
- JAVA之旅02
- JAVA之旅03
- JAVA之旅04
- JAVA之旅05
- JAVA之旅06
- JAVA之旅07
- JAVA之旅08
- JAVA之旅09
- java之旅1
- JAVA之旅10
- JAVA之旅11
- JAVA之旅12
- JAVA之旅13
- JAVA之旅14
- JAVA之旅15
- JAVA之旅16
- JAVA之旅17
- JAVA之旅18
- JAVA之旅19
- java之旅2
- JAVA之旅20
- JAVA之旅21
- JAVA之旅22
- JAVA之旅23
- JAVA之旅24
- JAVA之旅25
- JAVA之旅26
- JAVA之旅27
- JAVA之旅28
- JAVA之旅29
- java之旅3
- JAVA之旅30
- JAVA之旅31
- JAVA之旅32
- JAVA之旅33
- JAVA之旅34
- JAVA之旅35
- 1-Java-05辨析
- HashMapArrayMap
- Java8新特性
- Java8接口默認方法
- 圖解HashMap(1)
- 圖解HashMap(2)
- 2-Android
- 2-Android-1-基礎
- View繪制流程
- 事件分發
- AndroidView的事件分發機制和滑動沖突解決
- 自定義View基礎
- 1-安卓自定義View基礎-坐標系
- 2-安卓自定義View基礎-角度弧度
- 3-安卓自定義View基礎-顏色
- 自定義View進階
- 1-安卓自定義View進階-分類和流程
- 10-安卓自定義View進階-Matrix詳解
- 11-安卓自定義View進階-MatrixCamera
- 12-安卓自定義View進階-事件分發機制原理
- 13-安卓自定義View進階-事件分發機制詳解
- 14-安卓自定義View進階-MotionEvent詳解
- 15-安卓自定義View進階-特殊形狀控件事件處理方案
- 16-安卓自定義View進階-多點觸控詳解
- 17-安卓自定義View進階-手勢檢測GestureDetector
- 2-安卓自定義View進階-繪制基本圖形
- 3-安卓自定義View進階-畫布操作
- 4-安卓自定義View進階-圖片文字
- 5-安卓自定義View進階-Path基本操作
- 6-安卓自定義View進階-貝塞爾曲線
- 7-安卓自定義View進階-Path完結篇偽
- 8-安卓自定義View進階-Path玩出花樣PathMeasure
- 9-安卓自定義View進階-Matrix原理
- 通用類介紹
- Application
- 2-Android-2-使用
- 2-Android-02控件
- ViewGroup
- ConstraintLayout
- CoordinatorLayout
- 2-Android-03三方使用
- Dagger2
- Dagger2圖文完全教程
- Dagger2最清晰的使用教程
- Dagger2讓你愛不釋手-終結篇
- Dagger2讓你愛不釋手-重點概念講解、融合篇
- dagger2讓你愛不釋手:基礎依賴注入框架篇
- 閱讀筆記
- Glide
- Google推薦的圖片加載庫Glide:最新版使用指南(含新特性)
- rxjava
- 這可能是最好的RxJava2.x入門教程完結版
- 這可能是最好的RxJava2.x入門教程(一)
- 這可能是最好的RxJava2.x入門教程(三)
- 這可能是最好的RxJava2.x入門教程(二)
- 這可能是最好的RxJava2.x入門教程(五)
- 這可能是最好的RxJava2.x入門教程(四)
- 2-Android-3-優化
- 優化概況
- 各種優化
- Android端秒開優化
- apk大小優化
- 內存分析
- 混淆
- 2-Android-4-工具
- adb命令
- 一鍵分析Android的BugReport
- 版本控制
- git
- git章節簡述
- 2-Android-5-源碼
- HandlerThread 源碼分析
- IntentService的使用和源碼分析
- 2-Android-9-辨析
- LRU算法
- 什么是Bitmap
- 常見圖片壓縮方式
- 3-Kotlin
- Kotlin使用筆記1-草稿
- Kotlin使用筆記2
- kotlin特性草稿
- Kotlin草稿-Delegation
- Kotlin草稿-Field
- Kotlin草稿-object
- 4-JavaScript
- 5-Python
- 6-Other
- Git
- Gradle
- Android中ProGuard配置和總結
- gradle使用筆記
- Nexus私服搭建
- 編譯提速最佳實踐
- 7-設計模式與架構
- 組件化
- 組件化探索(OKR)
- 1-參考列表
- 2-1-組件化概述
- 2-2-gradle配置
- 2-3-代碼編寫
- 2-4-常見問題
- 2-9-值得一讀
- 8-數據結構與算法
- 0臨時文件
- 漢諾塔
- 8-數據-1數據結構
- HashMap
- HashMap、Hashtable、HashSet 和 ConcurrentHashMap 的比較
- 遲到一年HashMap解讀
- 8-數據-2算法
- 1個就夠了
- Java常用排序算法(必須掌握的8大排序算法)
- 常用排序算法總結(性能+代碼)
- 必須知道的八大種排序算法(java實現)
- 9-職業
- 閱讀
- 書單
- 面試
- 面試-01-java
- Java面試題全集駱昊(上)
- Java面試題全集駱昊(下)
- Java面試題全集駱昊(中)
- 面試-02-android
- 40道Android面試題
- 面試-03-開源源碼
- Android圖片加載框架最全解析(二),從源碼的角度理解Glide的執行流程
- 面試-07-設計模式
- 面試-08-算法
- 面試-09-其他
- SUMMARY
- 版權說明
- temp111