本文內容偏向理論,和 [畫布操作](http://www.gcssloop.com/customview/Canvas_Convert/) 有重疊的部分,本文會讓你更加深入的了解其中的原理。
本篇的主角Matrix,是一個一直在后臺默默工作的勞動模范,雖然我們所有看到View背后都有著Matrix的功勞,但我們卻很少見到它,本篇我們就看看它是何方神圣吧。
> 由于Google已經對這一部分已經做了很好的封裝,所以跳過本部分對實際開發影響并不會太大,不想深究的粗略瀏覽即可,下一篇中將會詳細講解Matrix的具體用法和技巧。
>
> ## ?? 警告:測試本文章示例之前請關閉硬件加速。
## Matrix簡介
**Matrix是一個矩陣,主要功能是坐標映射,數值轉換。**
它看起來大概是下面這樣:

**Matrix作用就是坐標映射,那么為什么需要Matrix呢? 舉一個簡單的例子:**
我的的手機屏幕作為物理設備,其物理坐標系是從左上角開始的,但我們在開發的時候通常不會使用這一坐標系,而是使用內容區的坐標系。
以下圖為例,我們的內容區和屏幕坐標系還相差一個通知欄加一個標題欄的距離,所以兩者是不重合的,我們在內容區的坐標系中的內容最終繪制的時候肯定要轉換為實際的物理坐標系來繪制,Matrix在此處的作用就是轉換這些數值。
> 假設通知欄高度為20像素,導航欄高度為40像素,那么我們在內容區的(0,0)位置繪制一個點,最終就要轉化為在實際坐標系中的(0,60)位置繪制一個點。

以上是僅作為一個簡單的示例,實際上不論2D還是3D,我們要將圖形顯示在屏幕上,都離不開Matrix,所以說Matrix是一個在背后辛勤工作的勞模。
### Matrix特點
- 作用范圍更廣,Matrix在View,圖片,動畫效果等各個方面均有運用,相比與之前講解等畫布操作應用范圍更廣。
- 更加靈活,畫布操作是對Matrix的封裝,Matrix作為更接近底層的東西,必然要比畫布操作更加靈活。
- 封裝很好,Matrix本身對各個方法就做了很好的封裝,讓開發者可以很方便的操作Matrix。
- 難以深入理解,很難理解中各個數值的意義,以及操作規律,如果不了解矩陣,也很難理解前乘,后乘。
### 常見誤解
**1.認為Matrix最下面的一行的三個參數(MPERSP_0、MPERSP_1、MPERSP_2)沒有什么太大的作用,在這里只是為了湊數。**
實際上最后一行參數在3D變換中有著至關重要的作用,這一點會在后面中Camera一文中詳細介紹。
**2.最后一個參數MPERSP_2被解釋為scale**
的確,更改MPERSP_2的值能夠達到類似縮放的效果,但這是因為齊次坐標的緣故,并非這個參數的實際功能。
## Matrix基本原理
Matrix 是一個矩陣,最根本的作用就是坐標轉換,下面我們就看看幾種常見變換的原理:
> 我們所用到的變換均屬于仿射變換,仿射變換是 線性變換(縮放,旋轉,錯切) 和 平移變換(平移) 的復合,由于這些概念對于我們作用并不大,此處不過多介紹,有興趣可自行了解。
基本變換有4種: 平移(translate)、縮放(scale)、旋轉(rotate) 和 錯切(skew)。
下面我們看一下四種變換都是由哪些參數控制的。


**從上圖可以看到最后三個參數是控制透視的,這三個參數主要在3D效果中運用,通常為(0, 0, 1),不在本篇討論范圍內,暫不過多敘述,會在之后對文章中詳述其作用。**
由于我們以下大部分的計算都是基于矩陣乘法規則,如果你已經把線性代數還給了老師,請參考一下這里:
**[維基百科-矩陣乘法](https://zh.wikipedia.org/wiki/%E7%9F%A9%E9%99%A3%E4%B9%98%E6%B3%95)**
### 1.縮放(Scale)

用矩陣表示:

> 你可能注意到了,我們坐標多了一個1,這是使用了齊次坐標系的緣故,在數學中我們的點和向量都是這樣表示的(x, y),兩者看起來一樣,計算機無法區分,為此讓計算機也可以區分它們,增加了一個標志位,增加之后看起來是這樣: <br/>
>
> (x, y, 1) - 點<br/>
> (x, y, 0) - 向量<br/>
>
> 另外,齊次坐標具有等比的性質,(2,3,1)、(4,6,2)...(2N,3N,N)表示的均是(2,3)這一個點。(**將MPERSP_2解釋為scale這一誤解就源于此**)。
圖例:

### 2.錯切(Skew)
錯切存在兩種特殊錯切,水平錯切(平行X軸)和垂直錯切(平行Y軸)。
#### 水平錯切

用矩陣表示:

圖例:

#### 垂直錯切

用矩陣表示:

圖例:

#### 復合錯切
> 水平錯切和垂直錯切的復合。

用矩陣表示:

圖例:

### 3.旋轉(Rotate)
假定一個點 A(x<sub>0</sub>, y<sub>0</sub>) ,距離原點距離為 r, 與水平軸夾角為 α 度, 繞原點旋轉 θ 度, 旋轉后為點 B(x, y) 如下:

用矩陣表示:

圖例:

### 4.平移(Translate)
> 此處也是使用齊次坐標的優點體現之一,實際上前面的三個操作使用 2x2 的矩陣也能滿足需求,但是使用 2x2 的矩陣,無法將平移操作加入其中,而將坐標擴展為齊次坐標后,將矩陣擴展為 3x3 就可以將算法統一,四種算法均可以使用矩陣乘法完成。

用矩陣表示:

圖例:

## Matrix復合原理
其實Matrix的多種復合操作都是使用矩陣乘法實現的,從原理上理解很簡單,但是,使用矩陣乘法也有其弱點,后面的操作可能會影響到前面到操作,所以在構造Matrix時順序很重要。
我們常用的四大變換操作,每一種操作在Matrix均有三類,前乘(pre),后乘(post)和設置(set),可以參見文末對[Matrix方法表](#fangfa),由于矩陣乘法不滿足交換律,所以前乘(pre),后乘(post)和設置(set)的區別還是很大的。
### 前乘(pre)
前乘相當于矩陣的右乘:

> 這表示一個矩陣與一個特殊矩陣前乘后構造出結果矩陣。
### 后乘(post)
前乘相當于矩陣的左乘:

> 這表示一個矩陣與一個特殊矩陣后乘后構造出結果矩陣。
### 設置(set)
設置使用的不是矩陣乘法,而是直接覆蓋掉原來的數值,所以,**使用設置可能會導致之前的操作失效**。
## 組合
**關于 Matrix 的文章終有一個問題,就是 pre 和 post 這一部分的理論非常別扭,國內大多數文章都是這樣的,看起來貌似是對的但很難理解,部分內容違背直覺。**
**我由于也受到了這些文章的影響,自然而然的繼承了這一理論,直到在評論區有一位小伙伴提出了一個問題,才讓我重新審視了這一部分的內容,并進行了一定反思。**
經過良久的思考之后,我決定拋棄國內大部分文章的那套理論和結論,只用嚴謹的數學邏輯和程序邏輯來闡述這一部分的理論,也許仍有疏漏,如有發現請指正。
**首先澄清兩個錯誤結論,記住,是錯誤結論,錯誤結論,錯誤結論。**
### ~~錯誤結論一:pre 是順序執行,post 是逆序執行。~~
這個結論很具有迷惑性,因為這個結論并非是完全錯誤的,你很容易就能證明這個結論,例如下面這樣:
```java
// 第一段 pre 順序執行,先平移(T)后旋轉(R)
Matrix matrix = new Matrix();
matrix.preTranslate(pivotX,pivotY);
matrix.preRotate(angle);
Log.e("Matrix", matrix.toShortString());
// 第二段 post 逆序執行,先平移(T)后旋轉(R)
Matrix matrix = new Matrix();
matrix.postRotate(angle);
matrix.postTranslate(pivotX,pivotY)
Log.e("Matrix", matrix.toShortString());
```
**這兩段代碼最終結果是等價的,于是輕松證得這個結論的正確性,但事實真是這樣么?**
首先,從數學角度分析,pre 和 post 就是右乘或者左乘的區別,其次,它們不可能實際影響運算順序(程序執行順序)。以上這兩段代碼等價也僅僅是因為最終化簡公式一樣而已。
> 設原始矩陣為 M,平移為 T ,旋轉為 R ,單位矩陣為 I ,最終結果為 M'
>
> - 矩陣乘法不滿足交換律,即 A\\*B ≠ B\\*A
> - 矩陣乘法滿足結合律,即 (A\\*B)\\*C = A\\*(B\\*C)
> - 矩陣與單位矩陣相乘結果不變,即 A * I = A
```
由于上面例子中原始矩陣(M)是一個單位矩陣(I),所以可得:
// 第一段 pre
M' = (M*T)*R = I*T*R = T*R
// 第二段 post
M' = T*(R*M) = T*R*I = T*R
```
由于兩者最終的化簡公式是相同的,所以兩者是等價的,但是,這結論不具備普適性。
**即原始矩陣不為單位矩陣的時候,兩者無法化簡為相同的公式,結果自然也會不同。另外,執行順序就是程序書寫順序,不存在所謂的正序逆序。**
### ~~錯誤結論二:pre 是先執行,而 post 是后執行。~~
這一條結論比上一條更離譜。
之所以產生這個錯誤完全是因為寫文章的人懂英語。
```
pre :先,和 before 相似。
post :后,和 after 相似。
```
所以就得出了 pre 先執行,而 post 后執行這一說法,但從嚴謹的數學和程序角度來分析,完全是不可能的,還是上面所說的,**pre 和 post 不能影響程序執行順序,而程序每執行一條語句都會得出一個確定的結果,所以,它根本不能控制先后執行,屬于完全扯淡型。**
**如果非要用這套理論強行解釋的話,反而看起來像是 post 先執行,例如:**
```java
matrix.preRotate(angle);
matrix.postTranslate(pivotX,pivotY);
```
同樣化簡公式:
```
// 矩陣乘法滿足結合律
M‘ = T*(M*R) = T*M*R = (T*M)*R
```
**從實際上來說,由于矩陣乘法滿足結合律,所以不論你說是靠右先執行還是靠左先執行,從結果上來說都沒有錯。**
**之前基于這條錯誤的結論我進行了一次錯誤的證明:**
> **(這段內容注定要成為我寫作歷程中不可抹滅的恥辱,既然是公開文章,就應該對讀者負責,雖然我在發表每一篇文章之前都竭力的求證其中的問題,各種細節,避免出現這種錯誤,但終究還是留下了這樣一段內容,在此我誠摯的向我所有的讀者道歉。)**
>
> 關注我的讀者請盡量看我在 [個人博客](http://www.gcssloop.com/#blog) 和 [GitHub](https://github.com/GcsSloop/AndroidNote/blob/master/README.md) 發布的版本,這兩個平臺都在博文修復計劃之內,有任何錯誤或者紕漏,都會首先修復這兩個平臺的文章。另外,所有進行修復過的文章都會在我的微博 [@GcsSloop](http://weibo.com/GcsSloop) 重新發布說明,關注我的微博可以第一時間得到博文更新或者修復的消息。
>
> ------
>
> ## 以下是錯誤證明:
>
> ~~在實際操作中,我們每一步操作都會得出準確的計算結果,但是為什么還會用存在先后的說法? 難道真的能夠用pre和post影響計算順序? 實則不然,下面我們用一個例子說明:~~
>
> ```java
> Matrix matrix = new Matrix();
> matrix.postScale(0.5f, 0.8f);
> matrix.preTranslate(1000, 1000);
> Log.e(TAG, "MatrixTest" + matrix.toShortString());
> ```
>
> ~~在上面的操作中,如果按照正常的思路,先縮放,后平移,縮放操作執行在前,不會影響到后續的平移操作,但是執行結果卻發現平移距離變成了(500, 800)。~~
>
> ~~在上面例子中,計算順序是沒有問題的,先計算的縮放,然后計算的平移,而縮放影響到平移則是因為前一步縮放后的結果矩陣右乘了平移矩陣,這是符合矩陣乘法的運算規律的,也就是說縮放操作雖然在前卻影響到了平移操作,**相當于先執行了平移操作,然后執行的縮放操作,因此才有pre操作會先執行,而post操作會后執行這一說法**。~~
>
> ------
上面的論證是完全錯誤的,因為可以輕松舉出反例:
```java
Matrix matrix = new Matrix();
matrix.preScale(0.5f, 0.8f);
matrix.preTranslate(1000, 1000);
Log.e(TAG, "MatrixTest" + matrix.toShortString());
```
反例中,雖然將 `postScale` 改為了 `preScale` ,但兩者結果是完全相同的,所以先后論根本就是錯誤的。
他們結果相同是因為最終化簡公式是相同的,都是 S*T
之所以平移距離是 MTRANS\_X = 500,MTRANS\_Y = 800,那是因為執行 Translate 之前 Matrix 已經具有了一個縮放比例。在右乘的時候影響到了具體的數值計算,可以用矩陣乘法計算一下。

最終結果為:

當 T*S 的時候,縮放比例則不會影響到 MTRANS\\_X 和 MTRANS\\_Y ,具體可以使用矩陣乘法自己計算一遍。
## 如何理解和使用 pre 和 post ?
不要去管什么先后論,順序論,就按照最基本的矩陣乘法理解。
```
pre : 右乘, M‘ = M*A
post : 左乘, M’ = A*M
```
**那么如何使用?**
正確使用方式就是先構造正常的 Matrix 乘法順序,之后根據情況使用 pre 和 post 來把這個順序實現。
還是用一個最簡單的例子理解,假設需要圍繞某一點旋轉。
可以用這個方法 `xxxRotate(angle, pivotX, pivotY)` ,由于我們這里需要組合構造一個 Matrix,所以不直接使用這個方法。
首先,有兩條基本定理:
- 所有的操作(旋轉、平移、縮放、錯切)默認都是以坐標原點為基準點的。
- 之前操作的坐標系狀態會保留,并且影響到后續狀態。
基于這兩條基本定理,我們可以推算出要基于某一個點進行旋轉需要如下步驟:
```
1. 先將坐標系原點移動到指定位置,使用平移 T
2. 對坐標系進行旋轉,使用旋轉 S (圍繞原點旋轉)
3. 再將坐標系平移回原來位置,使用平移 -T
```
具體公式如下:
> M 為原始矩陣,是一個單位矩陣, M‘ 為結果矩陣, T 為平移, R為旋轉
```
M' = M*T*R*-T = T*R*-T
```
按照公式寫出來的偽代碼如下:
```java
Matrix matrix = new Matrix();
matrix.preTranslate(pivotX,pivotY);
matrix.preRotate(angle);
matrix.preTranslate(-pivotX, -pivotY);
```
圍繞某一點操作可以拓展為通用情況,即:
```java
Matrix matrix = new Matrix();
matrix.preTranslate(pivotX,pivotY);
// 各種操作,旋轉,縮放,錯切等,可以執行多次。
matrix.preTranslate(-pivotX, -pivotY);
```
公式為:
```
M' = M*T* ... *-T = T* ... *-T
```
但是這種方式,兩個調整中心的平移函數就拉的太開了,所以通常采用這種寫法:
```java
Matrix matrix = new Matrix();
// 各種操作,旋轉,縮放,錯切等,可以執行多次。
matrix.postTranslate(pivotX,pivotY);
matrix.preTranslate(-pivotX, -pivotY);
```
這樣公式為:
```
M' = T*M* ... *-T = T* ... *-T
```
可以看到最終化簡結果是相同的。
所以說,pre 和 post 就是用來調整乘法順序的,正常情況下應當正向進行構建出乘法順序公式,之后根據實際情況調整書寫即可。
**在構造 Matrix 時,個人建議盡量使用一種乘法,前乘或者后乘,這樣操作順序容易確定,出現問題也比較容易排查。當然,由于矩陣乘法不滿足交換律,前乘和后乘的結果是不同的,使用時應結合具體情景分析使用。**
### 下面我們用不同對方式來構造一個相同的矩陣:
注意:
- 1.由于矩陣乘法不滿足交換律,請保證使用初始矩陣(Initial Matrix),否則可能導致運算結果不同。
- 2.注意構造順序,順序是會影響結果的。
- 3.Initial Matrix是指new出來的新矩陣,或者reset后的矩陣,是一個單位矩陣。
#### 1.僅用pre:
```java
// 使用pre, M' = M*T*S = T*S
Matrix m = new Matrix();
m.reset();
m.preTranslate(tx, ty);
m.preScale(sx, sy);
```
用矩陣表示:

#### 2.僅用post:
```java
// 使用post, M‘ = T*S*M = T*S
Matrix m = new Matrix();
m.reset();
m.postScale(sx, sy); //,越靠前越先執行。
m.postTranslate(tx, ty);
```
用矩陣表示:

#### 3.混合:
```java
// 混合 M‘ = T*M*S = T*S
Matrix m = new Matrix();
m.reset();
m.preScale(sx, sy);
m.postTranslate(tx, ty);
```
或:
```java
// 混合 M‘ = T*M*S = T*S
Matrix m = new Matrix();
m.reset();
m.postTranslate(tx, ty);
m.preScale(sx, sy);
```
> 由于此處只有兩步操作,且指定了先后,所以代碼上交換并不會影響結果。
用矩陣表示:

**注意: 由于矩陣乘法不滿足交換律,請保證初始矩陣為單位矩陣,如果初始矩陣不為單位矩陣,則導致運算結果不同。**
上面雖然用了很多不同的寫法,但最終的化簡公式是一樣的,這些不同的寫法,都是根據同一個公式反向推算出來的。
## Matrix方法表
這個方法表,暫時放到這里讓大家看看,方法的使用講解放在下一篇文章中。
| 方法類別 | 相關API | 摘要 |
| -------- | ---------------------------------------- | -------------------------- |
| 基本方法 | equals hashCode toString toShortString | 比較、 獲取哈希值、 轉換為字符串 |
| 數值操作 | set reset setValues getValues | 設置、 重置、 設置數值、 獲取數值 |
| 數值計算 | mapPoints mapRadius mapRect mapVectors | 計算變換后的數值 |
| 設置(set) | setConcat setRotate setScale setSkew setTranslate | 設置變換 |
| 前乘(pre) | preConcat preRotate preScale preSkew preTranslate | 前乘變換 |
| 后乘(post) | postConcat postRotate postScale postSkew postTranslate | 后乘變換 |
| 特殊方法 | setPolyToPoly setRectToRect rectStaysRect setSinCos | 一些特殊操作 |
| 矩陣相關 | invert isAffine isIdentity | 求逆矩陣、 是否為仿射矩陣、 是否為單位矩陣 ... |
## 總結
對于Matrix重在理解,理解了其中的原理之后用起來將會更加得心應手。
學完了本篇之后,推薦配合鴻洋大大的視頻課程 [
打造個性的圖片預覽與多點觸控](http://www.imooc.com/learn/239) 食用,定然能夠讓你對Matrix對理解更上一層樓。
由于個人水平有限,文章中可能會出現錯誤,如果你覺得哪一部分有錯誤,或者發現了錯別字等內容,歡迎在評論區告訴我,另外,據說關注 [作者微博](http://weibo.com/GcsSloop) 不僅能第一時間收到新文章消息,還能變帥哦。
## About
[本系列相關文章](http://www.gcssloop.com/customview/CustomViewIndex/)
作者微博: [GcsSloop](http://weibo.com/GcsSloop)
## 參考資料
[Matrix](https://developer.android.com/reference/android/graphics/Matrix.html)<br/>
[Android中圖像變換Matrix的原理、代碼驗證和應用](http://biandroid.iteye.com/blog/1399462)<br/>
[Android中關于矩陣(Matrix)前乘后乘的一些認識](http://blog.csdn.net/linmiansheng/article/details/18820599)<br/>
[維基百科-仿射變換](https://zh.wikipedia.org/wiki/%E4%BB%BF%E5%B0%84%E5%8F%98%E6%8D%A2)<br/>
[維基百科-齊次坐標](https://zh.wikipedia.org/wiki/%E9%BD%90%E6%AC%A1%E5%9D%90%E6%A0%87)<br/>
[維基百科-線性映射](https://zh.wikipedia.org/wiki/%E7%BA%BF%E6%80%A7%E6%98%A0%E5%B0%84)<br/>
[齊次坐標系入門級思考](https://oncemore2020.github.io/blog/homogeneous/)<br/>
[仿射變換與齊次坐標](https://guangchun.wordpress.com/2011/10/12/affineandhomogeneous/)<br/>
- 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