# Android 手勢檢測(GestureDetector)
Android 手勢檢測,主要是 GestureDetector 相關內容的用法和注意事項,本文依舊屬于事件處理這一體系,部分內容會涉及到之前文章提及過的知識點,如果你沒看過之前的文章,可以到 [自定義 View 系列](http://www.gcssloop.com/customview/CustomViewIndex) 來查看這些內容。
在開發 Android 手機應用過程中,可能需要對一些手勢作出響應,如:單擊、雙擊、長按、滑動、縮放等。這些都是很常用的手勢。就拿最簡單的雙擊來說吧,假如我們需要判斷一個控件是否被雙擊(即在較短的時間內快速的點擊兩次),似乎是一個很容易的任務,但仔細考慮起來,要處理的細節問題也有不少,例如:
1. **記錄點擊次數**,為了判斷是否被點擊超過 1 次,所以必須記錄點擊次數。
2. **記錄點擊時間**,由于雙擊事件是較快速的點擊兩次,像點擊一次后,過來幾分鐘再點擊一次肯定不能算是雙擊事件,所以在記錄點擊次數的同時也要記錄上一次的點擊時間,我們可以設置本次點擊距離上一次時間超過一定時間(例如:超過100ms)就不識別為雙擊事件。
3. **點擊狀態重置**,在響應雙擊事件,或者判斷不是雙擊事件的時候要重置計數器和上一次點擊時間。重置既可以在點擊的時候判斷并進行重新設置,也可以使用定時器等超過一定時間后重置狀態。
這樣看起來,判斷一個雙擊事件就有這么多麻煩事情,更別其他的手勢了,雖然這些看起來都很簡單,但設計起來需要考慮的細節情況實在是太多了。
那么有沒有一種更好的方法來方便的檢測手勢呢?當然有啦,因為這些手勢很常用,系統早就封裝了一些方法給我們用,接下來我們就看看它們是如何使用的。
## GestureDetector
> GestureDetector 可以使用 MotionEvents 檢測各種手勢和事件。GestureDetector.OnGestureListener 是一個回調方法,在發生特定的事件時會調用 Listener 中對應的方法回調。這個類只能用于檢測觸摸事件的 MotionEvent,不能用于軌跡球事件。
> (話說軌跡球已經消失多長時間了,估計很多人都沒見過軌跡球這種東西)。
>
> 如何使用:
>
> - 創建一個 GestureDetector 實例。
> - 在onTouchEvent(MotionEvent)方法中,確保調用 GestureDetector 實例的 onTouchEvent(MotionEvent)。回調中定義的方法將在事件發生時執行。
> - 如果偵聽 onContextClick(MotionEvent),則必須在 View 的 onGenericMotionEvent(MotionEvent)中調用 GestureDetector OnGenericMotionEvent(MotionEvent)。
GestureDetector 本身的方法比較少,使用起來也非常簡單,下面讓我們先看一下它的簡單使用示例,分解開來大概需要三個步驟。
```java
// 1.創建一個監聽回調
SimpleOnGestureListener listener = new SimpleOnGestureListener() {
@Override public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this, "雙擊666", Toast.LENGTH_SHORT).show();
return super.onDoubleTap(e);
}
};
// 2.創建一個檢測器
final GestureDetector detector = new GestureDetector(this, listener);
// 3.給監聽器設置數據源
view.setOnTouchListener(new View.OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) {
return detector.onTouchEvent(event);
}
});
```
接下來我們先了解一下 GestureDetector 里面都有哪些內容。
### 1. 構造函數
GestureDetector 一共有 5 種構造函數,但有 2 種被廢棄了,1 種是重復的,所以我們只需要關注其中的 2 種構造函數即可,如下:
| 構造函數 |
| ---------------------------------------- |
| GestureDetector(Context context, GestureDetector.OnGestureListener listener) |
| GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) |
第 1 種構造函數里面需要傳遞兩個參數,上下文(Context) 和 手勢監聽器(OnGestureListener),這個很容易理解,就不再過多敘述,上面的例子中使用的就是這一種。
第 2 種構造函數則需要多傳遞一個 Handler 作為參數,這個有什么作用呢?其實作用也非常簡單,這個 Handler 主要是為了給 GestureDetector 提供一個 Looper。
在通常情況下是不需這個 Handler 的,因為它會在內部自動創建一個 Handler 用于處理數據,如果你在主線程中創建 GestureDetector,那么它內部創建的 Handler 會自動獲得主線程的 Looper,然而如果你在一個沒有創建 Looper 的子線程中創建 GestureDetector 則需要傳遞一個帶有 Looper 的 Handler 給它,否則就會因為無法獲取到 Looper 導致創建失敗。
第 2 種構造函數使用方式如下(下面是兩種在子線程中創建 GestureDetector 的方法):
```java
// 方式一、在主線程創建 Handler
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override public void run() {
final GestureDetector detector = new GestureDetector(MainActivity.this, new
GestureDetector.SimpleOnGestureListener() , handler);
// ... 省略其它代碼 ...
}
}).start();
// 方式二、在子線程創建 Handler,并且指定 Looper
new Thread(new Runnable() {
@Override public void run() {
final Handler handler = new Handler(Looper.getMainLooper());
final GestureDetector detector = new GestureDetector(MainActivity.this, new
GestureDetector.SimpleOnGestureListener() , handler);
// ... 省略其它代碼 ...
}
}).start();
```
當然了,使用其它創建 Handler 的方式也是可以的,重點傳遞的 Handler 一定要有 Looper,敲黑板,重點是 Handler 中的 Looper。假如子線程準備了 Looper 那么可以直接使用第 1 種構造函數進行創建,如下:
```java
new Thread(new Runnable() {
@Override public void run() {
Looper.prepare(); // <- 重點在這里
final GestureDetector detector = new GestureDetector(MainActivity.this, new
GestureDetector.SimpleOnGestureListener());
// ... 省略其它代碼 ...
}
}).start();
```
### 2.手勢監聽器
既然是手勢檢測,自然要在對應的手勢出現的時候通知調用者,最合適的自然是事件監聽器模式。目前 GestureDetecotr 有四種監聽器。
| 監聽器 | 簡介 |
| ---------------------------------------- | ---------------------------------------- |
| [OnContextClickListener](https://developer.android.com/reference/android/view/GestureDetector.OnContextClickListener.html) | 這個很容易讓人聯想到ContextMenu,然而它和ContextMenu并沒有什么關系,它是在Android6.0(API 23)才添加的一個選項,是用于檢測外部設備上的按鈕是否按下的,例如藍牙觸控筆上的按鈕,一般情況下,忽略即可。 |
| [OnDoubleTapListener](https://developer.android.com/reference/android/view/GestureDetector.OnDoubleTapListener.html) | 雙擊事件,有三個回調類型:雙擊(DoubleTap)、單擊確認(SingleTapConfirmed) 和 雙擊事件回調(DoubleTapEvent) |
| [OnGestureListener](https://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html) | 手勢檢測,主要有以下類型事件:按下(Down)、 一扔(Fling)、長按(LongPress)、滾動(Scroll)、觸摸反饋(ShowPress) 和 單擊抬起(SingleTapUp) |
| [SimpleOnGestureListener](https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html) | 這個是上述三個接口的空實現,一般情況下使用這個比較多,也比較方便。 |
#### 2.1 OnContextClickListener
由于 OnContextClickListener 主要是用于檢測外部設備按鈕的,關于它需要注意一點,如果偵聽 onContextClick(MotionEvent),則必須在 View 的 onGenericMotionEvent(MotionEvent)中調用 GestureDetector 的 OnGenericMotionEvent(MotionEvent)。
由于目前我們用到這個監聽器的場景并不多,所以也就不展開介紹了,重點關注后面幾個監聽器。
#### 2.2 OnDoubleTapListener
這個很明顯就是用于檢測雙擊事件的,它有三個回調接口,分別是 onDoubleTap、onDoubleTapEvent 和 onSingleTapConfirmed。
##### **2.2.1 onDoubleTap 與 onSingleTapConfirmed**
**如果你只想監聽雙擊事件,那么只用關注 onDoubleTap 就行了,如果你同時要監聽單擊事件則需要關注 onSingleTapConfirmed 這個回調函數**。
有人可能會有疑問,監聽單擊事件為什么要使用 onSingleTapConfirmed,使用 OnClickListener 不行嗎?從理論上是可行的,但是我并不推薦這樣使用,主要有兩個原因:
1.它們兩個是存在一定沖突的,如果你看過 [事件分發機制詳解](http://www.gcssloop.com/customview/dispatch-touchevent-source) 就會知道,如果想要兩者同時被觸發,則 setOnTouchListener 不能消費事件,如果 onTouchListener 消費了事件,就可能導致 OnClick 無法正常觸發。
2.需要同時監聽單擊和雙擊,則說明單擊和雙擊后響應邏輯不同,然而使用 OnClickListener 會在雙擊事件發生時觸發兩次,這顯然不是我們想要的結果。而使用 onSingleTapConfirmed 就不用考慮那么多了,你完全可以把它當成單擊事件來看待,而且在雙擊事件發生時,onSingleTapConfirmed 不會被調用,這樣就不會引發沖突。
如果你需要同時監聽兩種點擊事件可以這樣寫:
```java
GestureDetector detector = new GestureDetector(this, new GestureDetector
.SimpleOnGestureListener() {
@Override public boolean onSingleTapConfirmed(MotionEvent e) {
Toast.makeText(MainActivity.this, "單擊", Toast.LENGTH_SHORT).show();
return false;
}
@Override public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this, "雙擊", Toast.LENGTH_SHORT).show();
return false;
}
});
```
關于 onSingleTapConfirmed 原理也非常簡單,這一個回調函數在單擊事件發生后300ms后觸發(注意,不是立即觸發的),只有在確定不會有后續的事件后,既當前事件肯定是單擊事件才觸發 onSingleTapConfirmed,所以在進行點擊操作時,onDoubleTap 和 onSingleTapConfirmed 只會有一個被觸發,也就不存在沖突了。
當然,如果你對事件分發機制非常了解的話,隨便怎么用都行,條條大路通羅馬,我這里只是推薦一種最簡單而且不容易出錯的實現方案。
##### **2.2.2 onDoubleTapEvent**
**有些細心的小伙伴可能注意到還有一個 onDoubleTapEvent 回調函數,它是干什么的呢?它在雙擊事件確定發生時會對第二次按下產生的 MotionEvent 信息進行回調。**
至于為什么要存在這樣的回調,就要涉及到另一個比較細致的問題了,那就是 onDoubleTap 的觸發時間,如果你在這些函數被調用時打印一條日志,那么你會看到這樣的信息:
```
GCS-LOG: onDoubleTap
GCS-LOG: onDoubleTapEvent - down
GCS-LOG: onDoubleTapEvent - move
GCS-LOG: onDoubleTapEvent - move
GCS-LOG: onDoubleTapEvent - up
```
通過觀察這些信息你會發現它們的調用順序非常有趣,首先是 onDoubleTap 被觸發,之后依次觸發 onDoubleTapEvent 的 down、move、up 等信息,為什么說它們有趣呢?是因為這樣的調用順序會引發兩種猜想,第一種猜想是 onDoubleTap 是在第二次手指抬起(up)后觸發的,而 onDoubleTapEvent 是一種延時回調。第二種猜想則是 onDoubleTap 在第二次手指按下(dowm)時觸發,onDoubleTapEvent 是一種實時回調。
通過測試和觀察源碼發現第二種猜想是正確的,因為第二次按下手指時,即便不抬起也會觸發 onDoubleTap 和 onDoubleTapEvent 的 down,而且源碼中邏輯也表明 onDoubleTapEvent 是一種實時回調。
這就引發了另一個問題,雙擊的觸發時間,雖然這是一個細微到很難讓人注意到的問題,假如說我們想要在第二次按下抬起后才判定這是一個雙擊操作,觸發后續的內容,則不能使用 onDoubleTap 了,需要使用 onDoubleTapEvent 來進行更細微的控制,如下:
```java
final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener() {
@Override public boolean onDoubleTap(MotionEvent e) {
Logger.e("第二次按下時觸發");
return super.onDoubleTap(e);
}
@Override public boolean onDoubleTapEvent(MotionEvent e) {
switch (e.getActionMasked()) {
case MotionEvent.ACTION_UP:
Logger.e("第二次抬起時觸發");
break;
}
return super.onDoubleTapEvent(e);
}
});
```
如果你不需要控制這么細微的話,忽略即可(Logger 是我自己封裝的日志庫,忽略即可)。
#### 2.3 OnGestureListener
這個是手勢檢測中較為核心的一個部分了,主要檢測以下類型事件:按下(Down)、 一扔(Fling)、長按(LongPress)、滾動(Scroll)、觸摸反饋(ShowPress) 和 單擊抬起(SingleTapUp)。
##### 2.3.1 onDown
```java
@Override public boolean onDown(MotionEvent e) {
return true;
}
```
看過前面的文章應該知道,down 在事件分發體系中是一個較為特殊的事件,為了保證事件被唯一的 View 消費,哪個 View 消費了 down 事件,后續的內容就會傳遞給該 View。如果我們想讓一個 View 能夠接收到事件,有兩種做法:
1、讓該 View 可以點擊,因為可點擊狀態會默認消費 down 事件。
2、手動消費掉 down 事件。
由于圖片、文本等一些控件默認是不可點擊的,所以我們要么聲明它們的 clickable 為 true,要么在發生 down 事件是返回 true。所以 onDown 在這里的作用就很明顯了,就是為了保證讓該控件能擁有消費事件的能力,以接受后續的事件。
##### 2.3.2 onFling
Failing 中文直接翻譯過來就是一扔、拋、甩,最常見的場景就是在 ListView 或者 RecyclerView 上快速滑動時手指抬起后它還會滾動一段時間才會停止。onFling 就是檢測這種手勢的。
```java
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float
velocityY) {
return super.onFling(e1, e2, velocityX, velocityY);
}
```
在 onFling 的回調中共有四個參數,分別是:
| 參數 | 簡介 |
| --------- | ------------------ |
| e1 | 手指按下時的 Event。 |
| e2 | 手指抬起時的 Event。 |
| velocityX | 在 X 軸上的運動速度(像素/秒)。 |
| velocityY | 在 Y 軸上的運動速度(像素/秒)。 |
我們可以通過 e1 和 e2 獲取到手指按下和抬起時的坐標、時間等相關信息,通過 velocityX 和 velocityY 獲取到在這段時間內的運動速度,單位是像素/秒(即 1 秒內滑動的像素距離)。
這個我們自己用到的地方比較少,但是也可以幫助我們簡單的做出一些有趣的效果,例如下面的這種彈球效果,會根據滑動的力度和方向產生不同的彈跳效果。

其實這種原理非常簡單,簡化之后如下:
1. 記錄 velocityX 和 velocityY 作為初始速度,之后不斷讓速度衰減,直至為零。
2. 根據速度和當前小球的位置計算一段時間后的位置,并在該位置重新繪制小球。
3. 判斷小球邊緣是否碰觸控件邊界,如果碰觸了邊界則讓速度反向。
根據這三條基本的邏輯就可以做出比較像的彈球效果,[具體的Demo可以看這里](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Demo/FailingBall.zip)。
##### 2.3.3 onLongPress
這個是檢測長按事件的,即手指按下后不抬起,在一段時間后會觸發該事件。
```java
@Override
public void onLongPress(MotionEvent e) {
}
```
##### 2.3.4 onScroll
onScroll 就是監聽滾動事件的,它看起來和 onFaling 比較像,不同的是,onSrcoll 后兩個參數不是速度,而是滾動的距離。
```java
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float
distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
}
```
| 參數 | |
| --------- | ----------- |
| e1 | 手指按下時的Event |
| e2 | 手指抬起時的Event |
| distanceX | 在 X 軸上劃過的距離 |
| distanceY | 在 Y 軸上劃過的距離 |
##### 2.3.5 onShowPress
它是用戶按下時的一種回調,主要作用是給用戶提供一種視覺反饋,可以在監聽到這種事件時可以讓控件換一種顏色,或者產生一些變化,告訴用戶他的動作已經被識別。
不過這個消息和 onSingleTapConfirmed 類似,也是一種延時回調,延遲時間是 180 ms,假如用戶手指按下后立即抬起或者事件立即被攔截,時間沒有超過 180 ms的話,這條消息會被 remove 掉,也就不會觸發這個回調。
```java
@Override
public void onShowPress(MotionEvent e) {
}
```
##### 2.3.6 onSingleTapUp
```java
@Override
public boolean onSingleTapUp(MotionEvent e) {
return super.onSingleTapUp(e);
}
```
這個也很容易理解,就是用戶單擊抬起時的回調,但是它和上面的 `onSingleTapConfirmed` 之間有何不同呢?和 `onClick` 又有何不同呢?
單擊事件觸發:
```java
GCS: onSingleTapUp
GCS: onClick
GCS: onSingleTapConfirmed
```
| 類型 | 觸發次數 | 摘要 |
| -------------------- | ---- | ---- |
| onSingleTapUp | 1 | 單擊抬起 |
| onSingleTapConfirmed | 1 | 單擊確認 |
| onClick | 1 | 單擊事件 |
雙擊事件觸發:
```java
GCS: onSingleTapUp
GCS: onClick
GCS: onDoubleTap // <- 雙擊
GCS: onClick
```
| 類型 | 觸發次數 | 摘要 |
| -------------------- | ---- | ------------ |
| onSingleTapUp | 1 | 在雙擊的第一次抬起時觸發 |
| onSingleTapConfirmed | 0 | 雙擊發生時不會觸發。 |
| onClick | 2 | 在雙擊事件時觸發兩次。 |
可以看出來這三個事件還是有所不同的,根據自己實際需要進行使用即可
#### 2.4 SimpleOnGestureListener
這個里面并沒有什么內容,只是對上面三種 Listener 的空實現,在上面的例子中使用的基本都是這監聽器。因為它用起來更方便一點。
這主要是 GestureDetector 構造函數的設計問題,以只監聽 OnDoubleTapListener 為例,如果想要使用 OnDoubleTapListener 接口則需要這樣進行設置:
```java
GestureDetector detector = new GestureDetector(this, new GestureDetector
.SimpleOnGestureListener());
detector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
@Override public boolean onSingleTapConfirmed(MotionEvent e) {
Toast.makeText(MainActivity.this, "單擊確認", Toast.LENGTH_SHORT).show();
return false;
}
@Override public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this, "雙擊", Toast.LENGTH_SHORT).show();
return false;
}
@Override public boolean onDoubleTapEvent(MotionEvent e) {
// Toast.makeText(MainActivity.this,"",Toast.LENGTH_SHORT).show();
return false;
}
});
```
既然都已經創建 SimpleOnGestureListener 了,再創建一個 OnDoubleTapListener 顯然十分浪費,如果構造函數不使用 SimpleOnGestureListener,而是使用 OnGestureListener 的話,會多出幾個無用的空實現,顯然很浪費,所以在一般情況下,老老實實的使用 SimpleOnGestureListener 就好了。
### 3. 相關方法
除了各類監聽器之外,與 GestureDetector 相關的方法其實并不多,只有幾個,下面來簡單介紹一下。
| 方法 | 摘要 |
| ----------------------- | ---------------------------------------- |
| setIsLongpressEnabled | 通過布爾值設置是否允許觸發長按事件,true 表示允許,false 表示不允許。 |
| isLongpressEnabled | 判斷當前是否允許觸發長按事件,true 表示允許,false 表示不允許。 |
| onTouchEvent | 這個是其中一個重要的方法,在最開始已經演示過使用方式了。 |
| onGenericMotionEvent | 這個是在 API 23 之后才添加的內容,主要是為 OnContextClickListener 服務的,暫時不用關注。 |
| setContextClickListener | 設置 ContextClickListener 。 |
| setOnDoubleTapListener | 設置 OnDoubleTapListener 。 |
### 結語
關于手勢檢測部分的 GestureDetector 相關內容基本就這么多了,其實手勢檢測還有一個 ScaleGestureDetector 也是為手勢檢測服務的,限于篇幅,本次就講這么多吧。
其實手勢檢測輔助類 GestureDetector 本身并不是很復雜,帶上注釋等內容才不到1000行,感興趣的可以自己研究一下實現方式。
## 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>
## 參考資料
[文檔 · GestureDetector ](https://developer.android.com/reference/android/view/GestureDetector.html)
[源碼 · GestureDetector](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/GestureDetector.java)
- 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