# Canvas之繪制基本形狀
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### [【本系列相關文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md)
在上一篇[自定義View分類與流程](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B01%5DCustomViewProcess.md)中我們了解自定義View相關的基本知識,不過,這些東西依舊還是理論,并不能**拿來(zhuang)用(B)**, 這一次我們就了解一些**能(zhaung)用(B)**的東西。
在本篇文章中,我們先了解Canvas的基本用法,最后用一個小示例來結束本次教程。
## 一.Canvas簡介
Canvas我們可以稱之為畫布,能夠在上面繪制各種東西,是安卓平臺2D圖形繪制的基礎,非常強大。
**一般來說,比較基礎的東西有兩大特點:<br/>
1.可操作性強:由于這些是構成上層的基礎,所以可操作性必然十分強大。<br/>
2.比較難用:各種方法太過基礎,想要完美的將這些操作組合起來有一定難度。**
不過不必擔心,本系列文章不僅會介紹到Canvas的操作方法,還會簡單介紹一些設計思路和技巧。
## 二.Canvas的常用操作速查表
| 操作類型 | 相關API | 備注 |
| ---------- | ---------------------------------------- | ---------------------------------------- |
| 繪制顏色 | drawColor, drawRGB, drawARGB | 使用單一顏色填充整個畫布 |
| 繪制基本形狀 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次為 點、線、矩形、圓角矩形、橢圓、圓、圓弧 |
| 繪制圖片 | drawBitmap, drawPicture | 繪制位圖和圖片 |
| 繪制文本 | drawText, drawPosText, drawTextOnPath | 依次為 繪制文字、繪制文字時指定每個文字位置、根據路徑繪制文字 |
| 繪制路徑 | drawPath | 繪制路徑,繪制貝塞爾曲線時也需要用到該函數 |
| 頂點操作 | drawVertices, drawBitmapMesh | 通過對頂點操作可以使圖像形變,drawVertices直接對畫布作用、 drawBitmapMesh只對繪制的Bitmap作用 |
| 畫布剪裁 | clipPath, clipRect | 設置畫布的顯示區域 |
| 畫布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次為 保存當前狀態、 回滾到上一次保存的狀態、 保存圖層狀態、 回滾到指定狀態、 獲取保存次數 |
| 畫布變換 | translate, scale, rotate, skew | 依次為 位移、縮放、 旋轉、錯切 |
| Matrix(矩陣) | getMatrix, setMatrix, concat | 實際畫布的位移,縮放等操作的都是圖像矩陣Matrix,只不過Matrix比較難以理解和使用,故封裝了一些常用的方法。 |
> PS: Canvas常用方法在上面表格中已經全部列出了,當然還存在一些其他的方法未列出,具體可以參考官方文檔 [Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)
******
## 三.Canvas詳解
本篇內容主要講解如何利用Canvas繪制基本圖形。
### 繪制顏色:
繪制顏色是填充整個畫布,常用于繪制底色。
``` java
canvas.drawColor(Color.BLUE); //繪制藍色
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2742437w3j30u01hcjrq.jpg" width = "300" />
> 關于顏色的更多資料請參考[基礎篇_顏色](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B03%5DColor.md)
******
### 創建畫筆:
要想繪制內容,首先需要先創建一個畫筆,如下:
``` java
// 1.創建一個畫筆
private Paint mPaint = new Paint();
// 2.初始化畫筆
private void initPaint() {
mPaint.setColor(Color.BLACK); //設置畫筆顏色
mPaint.setStyle(Paint.Style.FILL); //設置畫筆模式為填充
mPaint.setStrokeWidth(10f); //設置畫筆寬度為10px
}
// 3.在構造函數中初始化
public SloopView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
```
在創建完畫筆之后,就可以在Canvas中繪制各種內容了。
******
### 繪制點:
可以繪制一個點,也可以繪制一組點,如下:
``` java
canvas.drawPoint(200, 200, mPaint); //在坐標(200,200)位置繪制一個點
canvas.drawPoints(new float[]{ //繪制一組點,坐標位置由float數組指定
500,500,
500,600,
500,700
},mPaint);
```
關于坐標原點默認在左上角,水平向右為x軸增大方向,豎直向下為y軸增大方向。
> 更多參考這里 [基礎篇_坐標系](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B01%5DCoordinateSystem.md)
<img src="http://ww1.sinaimg.cn/large/005Xtdi2jw1f2743rkifnj30u01hc74n.jpg" width = "300" />
******
### 繪制直線:
繪制直線需要兩個點,初始點和結束點,同樣繪制直線也可以繪制一條或者繪制一組:
``` java
canvas.drawLine(300,300,500,600,mPaint); // 在坐標(300,300)(500,600)之間繪制一條直線
canvas.drawLines(new float[]{ // 繪制一組線 每四數字(兩個點的坐標)確定一條線
100,200,200,200,
100,300,200,300
},mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f2745k83ybj30u01hcq3d.jpg" width = "300" />
******
### 繪制矩形:
確定一個矩形最少需要四個數據,就是**對角線的兩個點**的坐標值,這里一般采用**左上角和右下角**的兩個點的坐標。
關于繪制矩形,Canvas提供了三種重載方法,第一種就是提供**四個數值(矩形左上角和右下角兩個點的坐標)來確定一個矩形**進行繪制。
其余兩種是先將矩形封裝為**Rect或RectF**(實際上仍然是用兩個坐標點來確定的矩形),然后傳遞給Canvas繪制,如下:
``` java
// 第一種
canvas.drawRect(100,100,800,400,mPaint);
// 第二種
Rect rect = new Rect(100,100,800,400);
canvas.drawRect(rect,mPaint);
// 第三種
RectF rectF = new RectF(100,100,800,400);
canvas.drawRect(rectF,mPaint);
```
以上三種方法所繪制出來的結果是完全一樣的。
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f27478692dj30u01hc3yy.jpg" width = "300" />
看到這里,相信很多觀眾會產生一個疑問,<b>為什么會有Rect和RectF兩種?兩者有什么區別嗎?</b>
答案當然是存在區別的,**兩者最大的區別就是精度不同,Rect是int(整形)的,而RectF是float(單精度浮點型)的**。除了精度不同,兩種提供的方法也稍微存在差別,在這里我們暫時無需關注,想了解更多參見官方文檔 [Rect](http://developer.android.com/reference/android/graphics/Rect.html) 和 [RectF](http://developer.android.com/reference/android/graphics/RectF.html)
******
### 繪制圓角矩形:
繪制圓角矩形也提供了兩種重載方式,如下:
``` java
// 第一種
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);
// 第二種
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);
```
上面兩種方法繪制效果也是一樣的,但鑒于第二種方法在API21的時候才添加上,所以我們一般使用的都是第一種。
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2747s3c5zj30u01hcq3e.jpg" width = "300" />
下面簡單解析一下圓角矩形的幾個必要的參數的意思。
很明顯可以看出,第二種方法前四個參數和第一種方法的RectF作用是一樣的,都是為了確定一個矩形,最后一個參數Paint是畫筆,無需多說,**與矩形相比,圓角矩形多出來了兩個參數rx 和 ry**,這兩個參數是干什么的呢?
稍微分析一下,既然是圓角矩形,他的角肯定是圓弧(圓形的一部分),**我們一般用什么確定一個圓形呢?**
答案是**圓心 和 半徑,其中圓心用于確定位置,而半徑用于確定大小**。<br/>
由于矩形位置已經確定,所以其邊角位置也是確定的,那么確定位置的參數就可以省略,只需要用半徑就能描述一個圓弧了。<br/>
但是,**半徑只需要一個參數,但這里怎么會有兩個呢?**<br/>
好吧,讓你發現了,**這里圓角矩形的角實際上不是一個正圓的圓弧,而是橢圓的圓弧,這里的兩個參數實際上是橢圓的兩個半徑**,他們看起來個如下圖:<br/>

**紅線標注的 rx 與 ry 就是兩個半徑,也就是相比繪制矩形多出來的那兩個參數。**
我們了解到原理后,就可以為所欲為了,通過計算可知我們上次繪制的矩形寬度為700,高度為300,當你讓 rx大于350(寬度的一半), ry大于150(高度的一半) 時奇跡就出現了, 你會發現圓角矩形變成了一個橢圓, 他們畫出來是這樣的 ( 為了方便確認我更改了畫筆顏色, 同時繪制出了矩形和圓角矩形 ):
``` java
// 矩形
RectF rectF = new RectF(100,100,800,400);
// 繪制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
// 繪制圓角矩形
mPaint.setColor(Color.BLUE);
canvas.drawRoundRect(rectF,700,400,mPaint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2748ugy2pj30u01hcwf1.jpg" width = "300" />
其中灰色部分是我們所選定的矩形,而里面的圓角矩形則變成了一個橢圓,<b>實際上在rx為寬度的一半,ry為高度的一半時,剛好是一個橢圓,通過上面我們分析的原理推算一下就能得到,而當rx大于寬度的一半,ry大于高度的一半時,實際上是無法計算出圓弧的,所以drawRoundRect對大于該數值的參數進行了限制(修正),凡是大于一半的參數均按照一半來處理。</b>
******
### 繪制橢圓:
相對于繪制圓角矩形,繪制橢圓就簡單的多了,因為他只需要一個矩形矩形作為參數:
``` java
// 第一種
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);
// 第二種
canvas.drawOval(100,100,800,400,mPaint);
```
同樣,以上兩種方法效果完全一樣,但一般使用第一種。
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f274afxbiyj30u01hczks.jpg" width = "300" />
繪制橢圓實際上就是繪制一個矩形的內切圖形,原理如下,就不多說了:

PS: 如果你傳遞進來的是一個長寬相等的矩形(即正方形),那么繪制出來的實際上就是一個圓。
******
### 繪制圓:
繪制圓形也比較簡單, 如下:
```
canvas.drawCircle(500,500,400,mPaint); // 繪制一個圓心坐標在(500,500),半徑為400 的圓。
```
繪制圓形有四個參數,前兩個是圓心坐標,第三個是半徑,最后一個是畫筆。
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f274c41kknj30u01hcdgf.jpg" width = "300" />
******
### 繪制圓弧:
繪制圓弧就比較神奇一點了,為了理解這個比較神奇的東西,我們先看一下它需要的幾個參數:
``` java
// 第一種
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}
// 第二種
public void drawArc(float left, float top, float right, float bottom, float startAngle,
float sweepAngle, boolean useCenter, @NonNull Paint paint) {}
```
從上面可以看出,相比于繪制橢圓,繪制圓弧還多了三個參數:
``` java
startAngle // 開始角度
sweepAngle // 掃過角度
useCenter // 是否使用中心
```
通過字面意思我們基本能猜測出來前兩個參數(startAngle, sweepAngel)的作用,就是確定角度的起始位置和掃過角度, 不過第三個參數是干嘛的?試一下就知道了,上代碼:
```
RectF rectF = new RectF(100,100,800,400);
// 繪制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
// 繪制圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);
//-------------------------------------
RectF rectF2 = new RectF(100,600,800,900);
// 繪制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);
// 繪制圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);
```
上述代碼實際上是繪制了一個起始角度為0度,掃過90度的圓弧,兩者的區別就是是否使用了中心點,結果如下:
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f274d1smwej30u01hc3z4.jpg" width = "300" />
可以發現使用了中心點之后繪制出來類似于一個扇形,而不使用中心點則是圓弧起始點和結束點之間的連線加上圓弧圍成的圖形。這樣中心點這個參數的作用就很明顯了,不必多說想必大家試一下就明白了。 另外可以關于角度可以參考一下這篇文章: [角度與弧度](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B02%5DAngleAndRadian.md)
相比于使用橢圓,我們還是使用正圓比較多的,使用正圓展示一下效果:
```
RectF rectF = new RectF(100,100,600,600);
// 繪制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
// 繪制圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);
//-------------------------------------
RectF rectF2 = new RectF(100,700,600,1200);
// 繪制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);
// 繪制圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f274e3surgj30u01hc3z4.jpg" width = "300" />
******
### 簡要介紹Paint
看了上面這么多,相信有一部分人會產生一個疑問,如果我想繪制一個圓,只要邊不要里面的顏色怎么辦?
很簡單,繪制的**基本形狀由Canvas確定**,但繪制出來的**顏色,具體效果則由Paint確定**。
如果你注意到了的話,在一開始我們設置畫筆樣式的時候是這樣的:
``` java
mPaint.setStyle(Paint.Style.FILL); //設置畫筆模式為填充
```
為了展示方便,容易看出效果,之前使用的模式一直為填充模式,實際上畫筆有三種模式,如下:
``` java
STROKE //描邊
FILL //填充
FILL_AND_STROKE //描邊加填充
```
為了區分三者效果我們做如下實驗:
```
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40); //為了實驗效果明顯,特地設置描邊寬度非常大
// 描邊
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);
// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);
// 描邊加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f274g6lxbpj30u01hcq3n.jpg" width = "300" />
一圖勝千言,通過以上實驗我們可以比較明顯的看出三種模式的區別,如果只需要邊緣不需要填充內容的話只需要設置模式為描邊(STROKE)即可。
其實關于Paint的內容也是有不少的,這些只是冰山一角,在后續內容中會詳細的講解Paint。
******
## 小示例
### 簡要介紹畫布的操作:
> 畫布操作詳細內容會在下一篇文章中講解, 不是本文重點,但以下示例中可能會用到,所以此處簡要介紹一下。
| 相關操作 | 簡要介紹 |
| --------- | ----------- |
| save | 保存當前畫布狀態 |
| restore | 回滾到上一次保存的狀態 |
| translate | 相對于當前位置位移 |
| rotate | 旋轉 |
### 制作一個餅狀圖
在展示百分比數據的時候經常會用到餅狀圖,像這樣:

### 簡單分析
其實根據我們上面的知識已經能自己制作一個餅狀圖了。不過制作東西最重要的不是制作結果,而是制作思路。
相信我貼上代碼大家一看就立刻明白了,非常簡單的東西。不過嘛,咱們還是想了解一下制作思路:
先分析餅狀圖的構成,非常明顯,餅狀圖就是一個又一個的扇形構成的,每個扇形都有不同的顏色,對應的有名字,數據和百分比。
經以上信息可以得出餅狀圖的最基本數據應包括:<b>名字 數據值 百分比 對應的角度 顏色</b>。
<b>
用戶關心的數據 : 名字 數據值 百分比<br/>
需要程序計算的數據: 百分比 對應的角度<br/>
其中顏色這一項可以用戶指定也可以用程序指定(我們這里采用程序指定)。<br/>
</b>
### 封裝數據:
``` java
public class PieData {
// 用戶關心數據
private String name; // 名字
private float value; // 數值
private float percentage; // 百分比
// 非用戶關心數據
private int color = 0; // 顏色
private float angle = 0; // 角度
public PieData(@NonNull String name, @NonNull float value) {
this.name = name;
this.value = value;
}
}
```
PS: 以上省略了get set方法
### 自定義View:
先按照自定義View流程梳理一遍(確定各個步驟應該做的事情):
| 步驟 | 關鍵字 | 作用 |
| :--: | ------------- | --------------------- |
| 1 | 構造函數 | 初始化(初始化畫筆Paint) |
| 2 | onMeasure | 測量View的大小(暫時不用關心) |
| 3 | onSizeChanged | 確定View大小(記錄當前View的寬高) |
| 4 | onLayout | 確定子View布局(無子View,不關心) |
| 5 | onDraw | 實際繪制內容(繪制餅狀圖) |
| 6 | 提供接口 | 提供接口(提供設置數據的接口) |
代碼如下:
``` java
public class PieView extends View {
// 顏色表(注意: 此處定義顏色使用的是ARGB,帶Alpha通道的)
private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
0xFFE6B800, 0xFF7CFC00};
// 餅狀圖初始繪制角度
private float mStartAngle = 0;
// 數據
private ArrayList<PieData> mData;
// 寬高
private int mWidth, mHeight;
// 畫筆
private Paint mPaint = new Paint();
public PieView(Context context) {
this(context, null);
}
public PieView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mData)
return;
float currentStartAngle = mStartAngle; // 當前起始角度
canvas.translate(mWidth / 2, mHeight / 2); // 將畫布坐標原點移動到中心位置
float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8); // 餅狀圖半徑
RectF rect = new RectF(-r, -r, r, r); // 餅狀圖繪制區域
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
mPaint.setColor(pie.getColor());
canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint);
currentStartAngle += pie.getAngle();
}
}
// 設置起始角度
public void setStartAngle(int mStartAngle) {
this.mStartAngle = mStartAngle;
invalidate(); // 刷新
}
// 設置數據
public void setData(ArrayList<PieData> mData) {
this.mData = mData;
initData(mData);
invalidate(); // 刷新
}
// 初始化數據
private void initData(ArrayList<PieData> mData) {
if (null == mData || mData.size() == 0) // 數據有問題 直接返回
return;
float sumValue = 0;
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
sumValue += pie.getValue(); //計算數值和
int j = i % mColors.length; //設置顏色
pie.setColor(mColors[j]);
}
float sumAngle = 0;
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
float percentage = pie.getValue() / sumValue; // 百分比
float angle = percentage * 360; // 對應的角度
pie.setPercentage(percentage); // 記錄百分比
pie.setAngle(angle); // 記錄角度大小
sumAngle += angle;
Log.i("angle", "" + pie.getAngle());
}
}
}
```
**PS: 在更改了數據需要重繪界面時要調用invalidate()這個函數重新繪制。**
### 效果圖
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f274gz06voj30u01hc3za.jpg" width = "300" />
> **PS: 這個餅狀圖并沒有添加百分比等數據,僅作為示例使用。**
## 總結:
其實自定義View只要按照流程一步步的走,也是比較容易的。不過里面也有不少坑,這些坑還是自己踩過印象比較深,建議大家不要直接copy源碼,自己手打體驗一下。
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300/> </a>
## 參考資料:
[View](http://developer.android.com/reference/android/view/View.html)<br/>
[Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)<br/>
[Android Canvas繪圖詳解](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html)<br/>
<br/><br/><br/><br/><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