上篇的學習,Paint
中的基礎知識基本結束,筆學完了,學習開始學習畫布。本篇記錄學習Canvas
中的一些知識。在drawBitmap()
方法中會引出計劃下篇要學習的內容Matrix
,drawPath()
方法會引出計劃在下下篇學習的貝塞爾曲線,學習的重點也是這兩個方法
學習資料:
本人很菜,有錯誤,請指出
雖然稱Canvas
為畫布,但并不是直接在Canvas
畫,Canvas
內部默認會創建一個Biatmap
,也可以通過構造方法或者setBitmap()
方法傳入一個,像素所有的信息是畫在了這個Bitmap
上,然后Bitmap
被保存在了Canvas
之內
這是我看愛哥的博客后,自己做的一個總結,若有錯誤,請指出。針對
Canvas
的兩種構造方法,愛哥針對源碼有做分析,可以看看了解一下
在Canvas
的方法中,clip
和draw
方法占據了一大半,在Android 自定義View學習(二)——開始了解Canvas和Paint了解過了幾個draw
方法,本篇進行補充學習
1.draw方法補充學習
在draw
一系列方法中,有一個特殊的牛B的存在,drawBitmapMesh()
,這個方法牛B在可以幾乎對Bitmap
做任何操作。雖然這個方法很強大,但使用的頻率并不算高,也有點雞肋。一些比較簡單的Bitmap
處理可能優先考慮Matrix
,而過于復雜的處理,會耗時比較久,效率可能并不高。一般優先不考慮這個方法,遇到Matrix
實現不了的需求,記得有這么一個方法可以學習,然后使用
1.1 drawBitmap方法
感覺這個方法使用頻率很高。一共有6個重載方法,其中兩個參數最多的方法已經廢棄,也就是需要學習4個
1.1.1 第1個方法
drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
-
bitmap
要畫的目標Bitmap
-
left
左上角的X
軸坐標 -
top
左上角的Y
軸坐標 -
paint
畫筆
簡單使用
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
final float x = getWidth() / 2 - bitmap.getWidth() / 2;//水平居中
final float y = 0;
canvas.drawBitmap(bitmap, x, y, mPaint);
}
在實際開發中,還會考慮圖片寬高的壓縮,顯示位置,以及padding
的等等
1.1.2 第2個和第3個方法
drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,@Nullable Paint paint)
drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,@Nullable Paint paint)
兩個方法的差別在于第三個參數Rect
和RectF
-
src
用來截取Bitmap
局部所想要顯示的像素塊區域,通過構造方法中的四個坐標系點確定范圍。這個參數可以為null
,為null
就是整個Bitmap
都作為目標資源顯示 -
dst
用來顯示的區域,在控件中繪制Bitmap
的區域,可以實現拉伸或者縮放
簡單使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test);
rect = new Rect(0,0,1080,600);
rectF = new RectF(0f,0f,1080f,600f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bitmap != null)
canvas.drawBitmap(bitmap,null,rectF,mPaint);
}
1080
是測試手機的屏幕寬度
第2個參數rect = new Rect(x,y,x1,y1)
,構造方法中四個參數分別為:
-
x
, X軸開始的坐標點,默認為0 -
y
, Y軸開始的坐標點 -
x1
, X軸結束的坐標點,若x1-x
大于了Bitmap
的寬度,截取的有效區域就是Bitmap
的寬度 -
y1
, Y軸結束的坐標點
第3個參數rectF = new RectF(x,y,x1,y1)
,構造方法中四個參數分別為:
-
x
,開始繪制的X軸的坐標點,默認為0 -
y
,開始繪制的Y軸的坐標點 -
x1
,結束繪制的X軸的坐標點,若x1-x
大于src
中x1-x
,就是拉伸;小于就是縮放 -
y1
,結束繪制的Y軸的坐標點
簡單修改代碼:
rect = new Rect(0,0,400,600);
rectF = new RectF(100f,0f,700f,600f);
此時在控件X
軸100f
位置開始繪制資源Bitmap
的(0,0)到(400,600)
局部區域,最終的顯示效果就成了拉伸局部的效果。Y
軸同理
這兩個參數的作用,用幾個數簡單測試一下,比較直觀。
1.1.3 第4個方法
drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint)
簡單使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
matrix.setTranslate(100f,100f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap,matrix,mPaint);
}
Matrix
是一個3 * 3
的矩陣,下篇進行記錄學習
1.2 畫Line方法
畫一條線的方法
drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)
畫多條線的方法
drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint)
drawLines(@Size(multiple=4) @NonNull float[] pts, int offset, int count,@NonNull Paint paint)
1.2.1 drawLine 畫一條線
drawLine()
方法就一個,沒有重載方法。畫規則曲線可以考慮使用Path
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStrokeWidth(10f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(100f,100f,600f,500f,mPaint);
}
方法參數也很簡單,四個參數確定兩個點的坐標,然后兩點一線
1.2.2 drawLines畫多條直線
兩個參數的drawsLines(float[] pts, Paint paint)
方法簡單使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStrokeWidth(10f);
floats = new float[]{100f,100f,300f,300f,400f,200f,600f,200f};//每4個數一組,確定兩個點的坐標
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLines(floats,mPaint);
}
方法源碼中有這樣一個@Size(multiple=4)
注解,要求floats
中每4個值看做一組,每組來確定一條直線的兩個端點,不足4個的部分是無效的
兩個參數的方法內部還是調用了四個參數的方法,在源碼中
public void drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint) {
drawLines(pts, 0, pts.length, paint);
}
在drawLines(float[] pts, int offset, int count,Paint paint)
中,
-
offset
表示跳過floats
中幾個值 -
count
表示跳過offset
后,數組的長度
簡單修改代碼
canvas.drawLines(floats,4,floats.length-4,mPaint);
就只會畫出(400f,200f),(600f,200f)
確定的那條水平的短線
1.3 畫Potion方法
同1.2畫Line
方法一樣,畫點的方法也是有兩種,畫一個點和畫多個點
畫一個點很簡單,直接看畫多個點
drawPoints(@Size(multiple=2) float[] pts, int offset, int count,@NonNull Paint paint)
根據方法內的注解得知,pts
的大小要大于2,并且每兩個一組,多余的無效
簡單使用:
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStrokeWidth(10f);
floats = new float[]{100f,100f,300f,200f,200f};
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPoints(floats,mPaint);
}
在floats
中,有5個值,但實際有效的也就是前4個
1.3 drawPath 方法
在Android 自定義View學習(三)——Paint繪制文字屬性中學習了解了setPathEffect(PathEffect effect)
方法在繪制路徑時的效果,drawPath()
方法往往都會配合PahtEffect
來使用
drawPath(@NonNull Path path, @NonNull Paint paint)
方法只有一個,簡單使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//設置風格為空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(540f,50f);
path.lineTo(740f,300f);
path.lineTo(340f,300f);
path.close();//形成閉合 將(340,300)和(540,50)連接起來
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
}
繪制出一個等腰三角形
主要就是用到Path
這個類,這個類有很多方法除了可以繪制直線,還可以繪制曲線
1.3.1 Path類
The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves.
It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint's Style), or it can be used for clipping or to draw text on a path.
這個類封裝了一些可以借助一元直線方程,二遠方程曲線,立方曲線的方法來繪制一些較為復雜的組合形式的集合圖形,繪制出來的風格則是根據畫筆設置的style
來決定,也可以剪切或者繪制一段文字在路徑上
除了已經用到的moveTo()
和lineTo()
方法,Path
中還有很多add
開頭的方法。有兩個重點方法是quadTo()
和cubicTo()
方法
1.3.1 quadTo和cubicTo方法
quadTo()
可以用來繪制二階貝塞爾曲線,也就是3個點確定一個曲線
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//設置風格為空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
floats = new float[]{200f,200f,900f,100f};
path.quadTo(200f,200f,900f,100f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
canvas.drawPoints(floats,mPaint);
}
圖中的曲線,是由(100,100),(900,100),(200,200)
三個點來確定。
cubicTo
方法使用和quadTo
用法類似,只是多了一組參數,多了一個點而已
path.cubicTo(200f,200f,400f,100f,900f,200f);
測試效果和quadTo
很容易就分區
依稀記得高三的數學考試最后一道大題往往就是要求繪制會一個點的運動軌跡,大部分都是一段曲線或者一個橢圓之類的,和這里有些類似。
這兩方法重要的是理解其中的原理,貝爾塞爾曲線等到Matrix
學習結束后再進行學習
1.3.2 addTo方法
先來看簡單用法
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//設置風格為空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
rectF = new RectF(100, 100, 400, 400);
path.arcTo(rectF,0,90);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
}
這個方法就是畫出一段弧線后,將弧線的起始點和moveTo
確定的path
的起始點進行連接起來?;【€截取圓的一部分。圓的直徑為300,經過很簡單分析,圓心在(250,250)
的點
1.3.3 rLineTo方法
r
就是relative
,相對的縮寫
簡單使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//設置風格為空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
path.rLineTo(300,200f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
canvas.drawPoint(300,200,mPaint);
}
rLineTo
方法確定的坐標是相對于moveTo
來說的,實際最終的畫出的直線的結束點的坐標為(400,300)
1.3.4 addArc方法
簡單使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//設置風格為空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
path.lineTo(300,200f);
//添加弧形
rectF = new RectF(100, 100, 400, 400);
path.addArc(rectF,0,90);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
canvas.drawPoint(300,200,mPaint);
}
addArc
方法和addTo
方法卻別是,addArc
是添加一段弧形,并不將繪制的圖形連接起來
1.4 canvas.drawTextOnPath 方法
在繪制的路徑上,繪制文字
private void init() {
//路徑畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//設置風格為空心
mPaint.setStrokeWidth(10f);
//文字畫筆
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(60f);
//路徑
path = new Path();
rectF = new RectF(100, 100, 300, 400);
path.addOval(rectF, Path.Direction.CW);
chars = new char[]{'a','b','c','d','e'};
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制路徑
canvas.drawPath(path, mPaint);
//繪制路徑上的文字
canvas.drawTextOnPath(chars,0,chars.length,path,0f,chars.length,textPaint);
}
在addOval
方法中,可以改變路徑閉合的方向,簡單修改代碼
path.addOval(rectF, Path.Direction.CCW);
文字在路徑內側繪制,并且逆時針
draw
系列方法中,有一個看起來很有意思的方法,drawTextRun
方法,但這個方法最低要求的23
canvas.drawTextRun(chars,0,chars.length,0,chars.length,100f,100f,false,textPaint);
沒有23的真機,就用了虛擬機,看方法名字,以為會按照一定的方法,文字進行滾動,可并沒有,設置為false
為abcde
,設置為true
為edcba
,不清楚在真機上啥效果
加上在開始了解Canvas
中的方法,draw
大致就學習到這里
2.Clip方法學習
Clip
開頭的方法主要有兩個:
-
clipPath()
利用Path
的方法,可以裁切出一塊不規則區域畫布 -
clipRect()
可以裁切出一塊矩形畫布
還有一個已經廢棄的clipRegion()
,廢棄就不學了,直接學替代的方法
2.1 clipRect裁切規則區域畫布
簡單使用:
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.FILL);
//矩形
rectF = new RectF(0, 0, 400, 400);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制底色 黃
canvas.drawColor(Color.YELLOW);
//截取畫布
canvas.clipRect(rectF);
//截取后的畫布底色
canvas.drawColor(Color.CYAN);
//驗證有效區域
canvas.drawRect(300,300,600,600,mPaint);
}
clipRect(rectF)
,就是在在畫布裁出以(0,0),(0,400),(400,0),(400,400)
四個點確定的矩形。之后Canvas
有效的區域便就是裁出的矩形區域,再次進行繪制時,超出這個區域便無法繪制,但裁切并不會影響c裁切前已經繪制好的區域,clip
裁切針對的是Canvas
cliprRect
有這樣一個cliprRect((@NonNull RectF rect, @NonNull Region.Op op)
重載方法
2.1.1 Region.Op
Op
是Region
類中的一個枚舉,有6個值
直接用代碼演示效果
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
//裁切區域1
rectF1 = new RectF(100, 100, 300, 300);
//裁切區域2
rectF2 = new RectF(200, 200, 400, 400);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制底色 藍
canvas.drawColor(Color.BLUE);
canvas.save();
//截取畫布1
canvas.clipRect(rectF1);
//截取畫布2
canvas.clipRect(rectF2,Region.Op.DIFFERENCE);
//截取后的有效區域畫布底色
canvas.drawColor(Color.RED);
canvas.restore();
//繪制輔助區域
canvas.drawRect(rectF1, mPaint);
canvas.drawRect(rectF2, mPaint);
}
紅色區域就代表兩次裁切后的有效區域
- Region.Op.DIFFERENCE
取第一次裁切的非交集部分
- Region.Op.INTERSECT
取兩次的交集
- Region.Op.REPLACE
第二次替代第一次裁切
- Region.Op.UNION
兩次裁切的和
- Region.Op.REVERSE_DIFFERENCE
與DIFFERENCE
相反,取的第2次裁切的非交集區域
- Region.Op.XOR
異或,取兩次交集外的區域
有點類似PorterDuffXfermode
圖像處理的效果
2.2 CilpPath 裁切不規則畫布
裁切出一個圓形區域的畫布
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
path = new Path();
path.addCircle(300,300,100, Path.Direction.CCW);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制底色 藍
canvas.drawColor(Color.BLUE);
//裁切畫布
canvas.clipPath(path);
//繪制裁切后的區域底色
canvas.drawColor(Color.parseColor("#FF4081"));
//繪制輔助圓形
canvas.drawCircle(300,300,100,mPaint);
}
clip
的方法基本就學到這里
3.其他方法
畫布除了裁切外,還有可以進行旋轉
3.1 rotate 旋轉方法
private void init() {
rectP1 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP1.setColor(Color.BLUE);
rectP2 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP2.setColor(Color.parseColor("#FF4081"));
rectF1 = new RectF(100,100,400,400);
rectF2 = new RectF(200,200,300,300);
}
/**
* 旋轉畫布
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//旋轉30°
canvas.rotate(30);
canvas.drawRect(rectF1,rectP1);
canvas.drawRect(rectF2,rectP2);
}
整個畫布進行了順時針旋轉30°
,參數為正時,是順時針旋轉,負數為逆時針旋轉
如果只想讓小的紅色的矩形進行旋轉,而藍色的大矩形不旋轉,需要了解畫布中的層
3.2 save和restore方法
簡單修改代碼,加入save
和restore
兩個方法
private void init() {
rectP1 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP1.setColor(Color.BLUE);
rectP2 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP2.setColor(Color.parseColor("#FF4081"));
rectF1 = new RectF(100,100,400,400);
rectF2 = new RectF(200,200,300,300);
}
/**
* 旋轉畫布
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(rectF1,rectP1);
canvas.save();
//旋轉30°
canvas.rotate(30);
canvas.drawRect(rectF2,rectP2);
canvas.restore();
}
save()
就是保存當前圖層
restore()
就是把圖層恢復到最近一次save()
方法前的狀態
關于保存圖層,還有一個更加強大的saveLayer()
,這個方法就等用到時,再進行學習
3.3 translate 平移畫布
這個方法使用很簡單
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//平移
canvas.translate(800,200);
//繪制矩形
canvas.drawRect(rectF1,rectP1);
canvas.drawRect(rectF2,rectP2);
//在(100,100)處繪制一個小圓,用來輔助觀察坐標系的改變
canvas.drawCircle(100,100,30,rectP2);
}
這個方法需要注意的是,Canvas
的坐標系就進行了改變,觀察小圓的位置
3.4 scale 縮放方法
縮放的使用也非常簡單
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.scale(0.5F, 1.0F);
canvas.drawBitmap(bitmap,0,0,null);
}
縮放有效值為0~1f
,1表示不進行縮放,原始大小
縮放方法有個重載方法scale(float sx, float sy, float px, float py)
簡單修改代碼
canvas.scale(0.5F, 1.0F,540,0);
px,py
確定縮放中心,canvas.scale(0.5F, 1.0F)
默認為(0,0)
為縮放中心,指定(540,0)
為縮放中心時,屏幕寬度為1080
,Canvas
就在水平居中縮放
3.5 skew 錯切
簡單使用
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.skew(0.5f,0);
canvas.drawBitmap(bitmap,0,0,null);
}
這幾個方法都只是簡單的調用,看了看效果,以后用到就再深入了解學習
4. 最后
Canvas
的基本知識也就學習這些,遺漏的遇到再學習
篇幅有點長,但并不難理解,基本都是調用一下就可以比較直觀看出效果的方法
下篇學習Matrix
月餅節到了,中秋快樂 : )