Android 自定義View之繪圖

【Android 自定義View之繪圖】

基礎(chǔ)圖形的繪制

一、Paint與Canvas

繪圖需要兩個(gè)工具俭正,筆和紙驯遇。這里的 Paint相當(dāng)于筆,而 Canvas相當(dāng)于紙捎琐,不過需要注意的是 Canvas(畫布)無限大会涎,沒有邊界,切記理解成只有屏幕大小瑞凑。我這里打個(gè)比方末秃, Canvas是整個(gè)天空,而屏幕是通過窗戶看到的景色籽御。

那么我需要改變畫筆大小练慕,粗細(xì),顏色篱蝇,透明度贺待,字體樣式等都需要在 Paint里面設(shè)置徽曲;
同樣要畫出圓形零截,矩形,不規(guī)則形狀都是在 Canvas里面操作的秃臣。

Paint

Paint的基本設(shè)置函數(shù)

  1. mPaint.setAntiAlias(true) //設(shè)置是否抗鋸齒;
  2. mPaint.setStyle(Paint.Style.FILL_AND_STROKE); //設(shè)置填充樣式
  3. mPaint.setColor(Color.GREEN);//設(shè)置畫筆顏色
  4. mPaint.setStrokeWidth(2);//設(shè)置畫筆寬度
  5. mPaint.setShadowLayer(10,15,15,Color.RED);//設(shè)置陰影

1 涧衙、setAntiAlias(true) 設(shè)置是否抗鋸齒

設(shè)置抗鋸齒會(huì)使圖像邊緣更清晰一些哪工,鋸齒痕跡不會(huì)那么明顯。

2弧哎、setStyle (Paint.Style style) 設(shè)置填充樣式

Paint.Style 類型:

Paint.Style.FILL_AND_STROKE 填充且描邊
Paint.Style.STROKE 描邊
Paint.Style.FILL 填充

看下上面三種類型雁比,這里以矩形為例:

3、setColor(@ColorInt int color) 設(shè)置畫筆顏色

4撤嫩、setStrokeWidth(float width) 設(shè)置畫筆寬度

5偎捎、setShadowLayer(float radius, float dx, float dy, int shadowColor) 設(shè)置陰影

先來看看參數(shù)代表的含義:

radius : 表示陰影的傾斜度
dx : 水平位移
dy : 垂直位移
shadowColor : 陰影顏色

看一個(gè)簡單的例子:

paint.setShadowLayer(5,10,10,Color.parseColor("#abc133"));

效果圖:

這里你可能有疑問,為啥我自己演示了一篇卻看不到矩形序攘,圓形等圖形的陰影茴她,只能看到文本的陰影呢?那么我們需要注意的是:這個(gè)方法不支持硬件加速程奠,所以我們要測試時(shí)必須先關(guān)閉硬件加速丈牢。

那么請(qǐng)加上setLayerType(LAYER_TYPE_SOFTWARE, null); 并且確保你的最小api8以上。

Canvas

下文【Canvas詳細(xì)講解】有Canvas進(jìn)一步說明

畫布背景設(shè)置:

canvas.drawColor(Color.BLUE);
canvas.drawRGB(255, 255, 0);  

這兩個(gè)功能一樣瞄沙,都是用來設(shè)置背景顏色的己沛。

我們只需要重寫onDraw(Canvas canvas)方法,就可以繪制你想要的圖形了距境。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
     //繪制圖形
}

二申尼、基本幾何圖形繪制

1、畫直線drawLine

方法預(yù)覽:

drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)

參數(shù):

startX : 開始點(diǎn)X坐標(biāo)
startY : 開始點(diǎn)Y坐標(biāo)
stopX : 結(jié)束點(diǎn)X坐標(biāo)
stopY : 結(jié)束點(diǎn)Y坐標(biāo)

paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(5);    
paint.setColor(Color.parseColor("#FF0000"));
canvas.drawLine(100,100,600,600,paint);

2垫桂、多條直線drawLines

方法預(yù)覽:

drawLines(@Size(min=4,multiple=2) @NonNull float[] pts, int offset, int count, Paint paint)
drawLines(@Size(min=4,multiple=2) @NonNull float[] pts, @NonNull Paint paint)

參數(shù):

pts : 是點(diǎn)的集合且大小最小為4而且是2的倍數(shù)晶姊。表示每2個(gè)點(diǎn)連接形成一條直線,pts 的組織方式為{x1,y1,x2,y2….}
offset : 集合中跳過的數(shù)值個(gè)數(shù)伪货,注意不是點(diǎn)的個(gè)數(shù)们衙!一個(gè)點(diǎn)是兩個(gè)數(shù)值
count : 參與繪制的數(shù)值的個(gè)數(shù),指pts[]里數(shù)值個(gè)數(shù)碱呼,而不是點(diǎn)的個(gè)數(shù)蒙挑,因?yàn)橐粋€(gè)點(diǎn)是兩個(gè)數(shù)值

還是來看個(gè)例子:

@Override
protected void onDraw(Canvas canvas) {
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setStyle(Paint.Style.FILL);
    paint.setStrokeWidth(5);    
    
    float [] pts={50,100,100,200,200,300,300,400};
    paint.setColor(Color.RED);
    canvas.drawLines(pts,paint);

    paint.setColor(Color.BLUE);
    canvas.drawLines(pts,1,4,paint);//去掉第一個(gè)數(shù)50,取之后的4個(gè)數(shù)即100,100,200,200
}

紅線:點(diǎn)(50,100)和點(diǎn)(100,200)連接成一條直線愚臀;點(diǎn)(200,300)和點(diǎn)(300,400)連接成直線忆蚀。
藍(lán)線:點(diǎn)(100,100)和點(diǎn)(200,200)連接成一條直線;


3姑裂、點(diǎn)及多個(gè)點(diǎn)drawPoint馋袜、drawPoints

方法預(yù)覽:

drawPoint(float x, float y, @NonNull Paint paint)

drawPoints(@Size(multiple=2) @NonNull float[] pts, @NonNull Paint paint)
drawPoints(@Size(multiple=2) @NonNull float[] pts, int offset, int count, @NonNull Paint paint)

點(diǎn)的繪制和上面直線的繪制一樣,我這里就不再累訴了舶斧。

4欣鳖、矩形drawRect、drawRoundRect

方法預(yù)覽:

drawRect(@NonNull RectF rect, @NonNull Paint paint)

drawRect(@NonNull Rect r, @NonNull Paint paint)

drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)

區(qū)別RectFRect 茴厉,RectF坐標(biāo)系是浮點(diǎn)型泽台;Rect坐標(biāo)系是整形什荣。

圓角矩形方法預(yù)覽:

drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)

drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint)

參數(shù):
RectF : 繪制的矩形
rx : 生成圓角的橢圓X軸半徑
ry : 生成圓角的橢圓Y軸的半徑

RectF rect = new RectF(100, 10, 500, 300);
canvas.drawRoundRect(rect, 60, 20, paint);

5、圓形drawCircle

方法預(yù)覽:

drawCircle(float cx, float cy, float radius, @NonNull Paint paint)

參數(shù):

cx : 圓心X坐標(biāo)
cy : 圓心Y坐標(biāo)
radius : 半徑

canvas.drawCircle(400,400,300,paint);

6怀酷、橢圓drawOval

方法預(yù)覽:

drawOval(@NonNull RectF oval, @NonNull Paint paint)

drawOval(float left, float top, float right, float bottom, @NonNull Paint paint)

7稻爬、圓弧drawArc

方法預(yù)覽:

drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)

drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle,
            boolean useCenter, @NonNull Paint paint)

參數(shù):
oval : 生成橢圓的矩形
startAngle : 弧開始的角度 (X軸正方向?yàn)?度,順時(shí)針弧度增大
sweepAngle : 繪制多少弧度 (注意不是結(jié)束弧度)
useCenter : 是否有弧的兩邊 true有兩邊 false無兩邊

畫筆設(shè)置填充:

RectF rect=new RectF(0,0,300,400);

paint.setStyle(Paint.Style.FILL);

paint.setColor(Color.RED);
canvas.drawArc(rect,30,30,false,paint);

paint.setColor(Color.BLUE);
canvas.drawArc(rect,120,30,true,paint);

paint.setStyle(Paint.Style.STROKE);

paint.setColor(Color.GREEN);
canvas.drawArc(rect,0,360,true,paint);

paint.setColor(Color.RED);
canvas.drawArc(rect,-30,30,false,paint);

paint.setColor(Color.BLUE);
canvas.drawArc(rect,-120,30,true,paint);

說明:


引用:

自定義View之繪圖

路徑(Path)

Path常用方法

方法 作用 備注
moveTo 移動(dòng)起點(diǎn) 移動(dòng)下一次操作的起點(diǎn)位置
lineTo 連接直線 連接上一個(gè)點(diǎn)到當(dāng)前點(diǎn)之間的直線
setLastPoint 設(shè)置終點(diǎn) 重置最后一個(gè)點(diǎn)的位置
close 閉合路勁 從最后一個(gè)點(diǎn)連接最初的一個(gè)點(diǎn)蜕依,形成一個(gè)閉合區(qū)域
addRect 添加矩形 添加矩形到當(dāng)前Path
addRoundRect 添加圓角矩形 添加圓角矩形到當(dāng)前Path
addOval 添加橢圓 添加橢圓到當(dāng)前Path
addCircle 添加圓 添加圓到當(dāng)前Path
addPah 添加路勁 添加路勁到當(dāng)前Path
addArc 添加圓弧 添加圓弧到當(dāng)前Path
arcTo 圓弧 繪制圓弧桅锄,注意和addArc的區(qū)別
isEmpty 是否為空 判定Path是否為空
isRect 是否為矩形 判定Path是否是一個(gè)矩形
set 替換路勁 用新的路勁替換當(dāng)前路勁的所有內(nèi)容
offset 偏移路勁 對(duì)當(dāng)前的路勁進(jìn)行偏移
quadTo 貝塞爾曲線 二次貝塞爾曲線的方法
cubicTo 貝塞爾曲線 三次貝塞爾曲線的方法
rMoveTo
rlineTo
rQuadTo
rCubicTo
rXxx方法 不帶r的方法是基于原點(diǎn)坐標(biāo)系(偏移量),帶r的基于當(dāng)前點(diǎn)坐標(biāo)系(偏移量)
op 布爾操作 對(duì)兩個(gè)Path進(jìn)行布爾運(yùn)算(交集样眠,并集)等操作
setFillType 填充模式 設(shè)置Path的填充模式
getFillType 填充模式 獲取Path的填充
isInverseFillType 是否逆填充 判斷是否是逆填充模式
toggleInverseFillType 相反模式 切換相反的填充模式
getFillType 填充模式 獲取Path的填充
incReserve 提示方法 提示Path還有多少個(gè)點(diǎn)等待加入
computeBounds 計(jì)算邊界 計(jì)算Path的路勁
reset竞滓,rewind 重置路勁 清除Path中的內(nèi)容(reset相當(dāng)于new Path , rewind 會(huì)保留Path的數(shù)據(jù)結(jié)構(gòu))
transform 矩陣操作 矩陣變換

Path方法使用詳解

使用Path不僅可以繪制簡單的圖形(如圓形,矩形吹缔,直線等)商佑,也可以繪制復(fù)雜一些的圖形(如正多邊形,五角星等)厢塘,還有繪制裁剪和繪制文本都會(huì)用到Path茶没。由于方法比較多,我這里分組來講下晚碾。

moveTo , lineTo , setLastPoint , close

先創(chuàng)建畫筆:

paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
paint.setColor(Color.parseColor("#FF0000"));

注意paint.setStyle(Paint.Style.FILL);抓半,設(shè)置畫筆為實(shí)心。一些線條將在畫布上看不見格嘁。

1笛求、lineTo

首先我們來看看lineTo,如果你直接moveTo 將看不出效果。

Path path = new Path();

path.lineTo(200,200);
path.lineTo(400,0);

canvas.drawPath(path,paint);

為了方便大家好觀察坐標(biāo)的變化糕簿,我在屏幕上畫出了網(wǎng)格探入,每塊網(wǎng)格的寬高都是100。由于第一次之前沒有過操作懂诗,所以默認(rèn)點(diǎn)就是原點(diǎn)(屏幕左上角)蜂嗽,第一次lineTo就是坐標(biāo)原點(diǎn)到(200,200)之間的直線。第二次lineTo就是上一次結(jié)束點(diǎn)位置(200,200)到(400,0)點(diǎn)之間的直線殃恒。

2植旧、moveTo 和setLastPoint

方法預(yù)覽:

moveTo(float x, float y) 

setLastPoint(float dx, float dy)

這兩個(gè)方法在作用上有相似之處,卻是兩個(gè)不同的東西离唐,具體參考下表:

方法名 作用 是否影響之前的操作 是否影響之后的操作
moveTo 移動(dòng)下一次操作的起點(diǎn)位置
setLastPoint 改變上一次操作點(diǎn)的位置

來看看下面的例子:

Path path = new Path();

path.lineTo(200, 200);
path.moveTo(300,300);//moveTo
path.lineTo(400, 0);

canvas.drawPath(path, paint);

效果圖:


Path path = new Path();

path.lineTo(200, 200);
path.setLastPoint(300,100);//setLastPoint
path.lineTo(400, 0);

canvas.drawPath(path, paint);

效果圖:


當(dāng)我們繪制線條之前病附,調(diào)用moveTosetLastPoint效果是一樣的,都是對(duì)坐標(biāo)原點(diǎn)(0,0)進(jìn)行操作亥鬓。
setLastPoint重置上一次操作的最后一點(diǎn)完沪,在執(zhí)行完第一次lineTo的時(shí)候,最后一個(gè)點(diǎn)就是(200,200),setLastPoint更改(200,200)為(300,100)贮竟,所以在執(zhí)行的時(shí)候就是(300,100)到(400, 0)之間的連線了丽焊。

3较剃、close

方法預(yù)覽

public void close()

close方法連接最后一個(gè)點(diǎn)和最初一個(gè)點(diǎn)(如果兩個(gè)點(diǎn)不重合)形成一個(gè)閉合圖形咕别。

path.moveTo(100,100);
path.lineTo(500,100);
path.lineTo(300,400);
path.close();
canvas.drawPath(path, paint);

效果圖:


上圖中可以看到lineTo(500,100)直線和lineTo(300,400)直線技健,而close方法就是連接(300,400),(100,100)兩點(diǎn)惰拱,形成一個(gè)閉合的區(qū)域雌贱。

注意:close的作用的封閉路徑,如果連接最后一個(gè)點(diǎn)和最初一個(gè)點(diǎn)任然無法形成閉合的區(qū)域偿短,那么close什么也不做欣孤。

quadTo,cubicTo

二次貝塞爾曲線以及三次貝塞爾曲線。

1昔逗、quadTo

方法預(yù)覽

public void quadTo(float x1, float y1, float x2, float y2)

quadTo方法其中 (x1,y1) 為控制點(diǎn)降传,(x2,y2)為結(jié)束點(diǎn)。

path.moveTo(100,400);
path.quadTo(300, 100, 400, 400);

canvas.drawPath(path, paint);

效果圖:


2勾怒、cubicTo

方法預(yù)覽:

public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)

cubicTo方法比quadTo方法多了一個(gè)點(diǎn)坐標(biāo)婆排,那么其中(x1,y1) 為控制點(diǎn),(x2,y2)為控制點(diǎn)笔链,(x3,y3) 為結(jié)束點(diǎn)段只。

path.moveTo(100, 400);
path.cubicTo(100, 400, 300, 100, 400, 400);

canvas.drawPath(path, paint);

繪制的圖形和上面的quadTo繪制的圖形是一樣的。我們?nèi)サ?code>moveTo來看看運(yùn)行的效果圖:

如果你想了解貝塞爾曲線公式鉴扫,請(qǐng)鏈接這里

addXxx和arcTo

主要是向Path中添加基本圖形以及區(qū)分addArcarcTo

1赞枕、添加基本圖形

方法預(yù)覽:

//圓形
addCircle(float x, float y, float radius, Path.Direction dir)
//橢圓
addOval(RectF oval, Path.Direction dir)
addOval(float left, float top, float right, float bottom, Path.Direction dir)
//矩形
addRect(RectF rect, Path.Direction dir)
addRect(float left, float top, float right, float bottom, Path.Direction dir)
//圓角矩形
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir) 
addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir)
addRoundRect(RectF rect, float[] radii, Path.Direction dir)
addRoundRect(float left, float top, float right, float bottom, float[] radii, Path.Direction dir)

我們仔細(xì)觀察上面的方法,在最后都有一個(gè)Path.Direction,這是個(gè)什么東東呢定嗓?
Direction的意思是方向蝎毡,指導(dǎo),趨勢(shì)古话。點(diǎn)進(jìn)去跟一下你會(huì)發(fā)現(xiàn)Direction是一個(gè)枚舉類型(Enum)分別有CW(順時(shí)針)CCW(逆時(shí)針)兩個(gè)常量锁施。那么它的作用主要有以下兩點(diǎn):

序號(hào) 作用
1 在添加圖形時(shí)確定閉合順序(各個(gè)點(diǎn)的記錄順序)
2 對(duì)自相交圖形的渲染結(jié)果有影響

我們先來看看閉合順序的問題陪踩,添加一個(gè)矩形看看:

path.addRect(100, 200, 500, 400, Path.Direction.CW);

canvas.drawPath(path, paint);

我將上面的代碼CW改成CCW再運(yùn)行一次,結(jié)果一模一樣悉抵。
想看到區(qū)別就要用到setLastPoint(重置最后一個(gè)點(diǎn)的坐標(biāo))肩狂。我們來這樣變變代碼:

path.addRect(100, 200, 500, 400, Path.Direction.CW);
path.setLastPoint(200,400);

canvas.drawPath(path, paint);

效果立馬現(xiàn)行:

為什么圖形會(huì)發(fā)生奇怪的變化呢。我們先來分析一下姥饰,繪制一個(gè)矩形至少需要對(duì)角線的兩個(gè)點(diǎn)傻谁,根據(jù)這兩個(gè)點(diǎn)計(jì)算出四條邊然后把四條邊按照順序連接起來。上圖的起始坐標(biāo)是(100,200)按著順時(shí)針的方向連接(500,200)列粪,(500,400)审磁,(100,400)最后連接(100,200)形成一個(gè)矩形谈飒。setLastPoint是重置上一個(gè)操作點(diǎn)坐標(biāo)及改變(100,400)為(200,400),所以出現(xiàn)了上圖的效果态蒂。

接下來我們看看逆時(shí)針的情況:

path.addRect(100, 200, 500, 400, Path.Direction.CCW);
path.setLastPoint(400,300);

canvas.drawPath(path, paint);

效果圖:

我們理清楚了閉合的問題杭措,相交問題與設(shè)置填充模式有關(guān)。

我以addCircle方法來講解添加圖形

path.addCircle(300,300,200, Path.Direction.CW);//(300,300)點(diǎn)表示圓心坐標(biāo)钾恢,200 表示半徑長度

canvas.drawPath(path, paint);
path.addCircle(300, 300, 200, Path.Direction.CCW);//(300,300)點(diǎn)表示圓心坐標(biāo)手素,200 表示半徑長度
path.setLastPoint(300,400);
canvas.drawPath(path, paint);

2、addPath

方法預(yù)覽:

public void addPath(Path src)
public void addPath(Path src, float dx, float dy)//`dx,dy`指的是偏移量
public void addPath(Path src, Matrix matrix)//添加到當(dāng)前path之前先使用Matrix進(jìn)行變換

addPath方法就是將兩個(gè)路徑合并到一起瘩蚪。

Path path = new Path();
path.addRect(100,100,400,300, Path.Direction.CW);

Path src=new Path();
src.addCircle(300,300,100, Path.Direction.CW);
path.addPath(src,0,100);

canvas.drawPath(path, paint);

效果圖:


3泉懦、addArc與arcTo

方法預(yù)覽:

addArc(RectF oval, float startAngle, float sweepAngle)
addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)

arcTo(RectF oval, float startAngle, float sweepAngle)
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)

從方法名字上面看,這兩個(gè)方法都是與圓弧有關(guān)疹瘦,那么他們之間肯定是有區(qū)別的:

名稱 作用 區(qū)別
addArc 添加一個(gè)圓弧到Path 直接添加一個(gè)圓弧到path中崩哩,和上一次操作點(diǎn)無關(guān)
arcTo 添加一個(gè)圓弧到Path 添加一個(gè)圓弧到path中,如果圓弧的起點(diǎn)和上次操作點(diǎn)坐標(biāo)不同就連接兩個(gè)點(diǎn)

startAngle表示開始圓弧度數(shù)(0度與X軸方向?qū)R言沐,順時(shí)針移動(dòng)邓嘹,弧度增大)。
sweepAngle表示運(yùn)動(dòng)了多少弧度呢灶,并不是結(jié)束弧度吴超。

forceMoveTo表示“是否強(qiáng)制使用moveTo”,也就是說是否使用moveTo將上一次操作點(diǎn)移動(dòng)到圓弧的起點(diǎn)坐標(biāo)鸯乃。默認(rèn)是false鲸阻。

forceMoveTo 含義
true 將最后一個(gè)點(diǎn)移動(dòng)到圓弧起點(diǎn),即不連接最后一個(gè)點(diǎn)與圓弧起點(diǎn)
false 不移動(dòng)缨睡,而是連接最后一個(gè)點(diǎn)與圓弧起點(diǎn)(注意之前沒有操作的話鸟悴,不會(huì)連接原點(diǎn))

示例:

path.lineTo(200, 200);
RectF rectF = new RectF(100, 100, 400, 400);
path.arcTo(rectF, 0, 270, true);
// path.addArc(rectF,0,270);和上面一句等價(jià)

canvas.drawPath(path, paint);

效果圖:


我們把 path.arcTo(rectF, 0, 270, true);改成 path.arcTo(rectF, 0, 270, false);,來看看效果圖:

從上面兩張圖可以看出明顯的變化奖年。

isEmpty细诸、 isRect、 set 和 offset

isEmpty

判斷path中是否包含內(nèi)容陋守。

Path path = new Path();
Log.e("-----","----"+path.isEmpty());//-----: ----true
path.lineTo(100,100);
Log.e("-----","----"+path.isEmpty());//-----: ----false

canvas.drawPath(path, paint);

isRect

方法預(yù)覽:

isRect(RectF rect)

判斷path是否是一個(gè)矩形震贵,如果是一個(gè)矩形的話,會(huì)將矩形的信息存放進(jìn)參數(shù)rect中水评。

Path path = new Path();
RectF rectF = new RectF();
rectF.left = 100;
rectF.top = 100;
rectF.right = 400;
rectF.bottom = 300;
path.addRect(rectF, Path.Direction.CW);
boolean isRect = path.isRect(rectF);
Log.e("-----","------"+isRect);//-----: ------true

set

方法預(yù)覽:

public void set(Path src)

將新的path賦值到現(xiàn)有path猩系。相當(dāng)于運(yùn)算符中的“=”,如a=b,把b賦值給a

還是一起來看個(gè)例子:

Path path = new Path();
path.addRect(100,100,400,300, Path.Direction.CW);
Path src=new Path();
src.addCircle(300,200,100, Path.Direction.CW);
path.set(src);
canvas.drawPath(path, paint);

效果圖:


offset

方法預(yù)覽:

public void offset(float dx, float dy)
public void offset(float dx, float dy, Path dst)

這個(gè)方法就是對(duì)Path進(jìn)行一段平移中燥,正方向和X軸寇甸,Y軸方向一致(如果dx為正數(shù)則向右平移,反之向左平移;如果dy為正則向下平移拿霉,反之向上平移)吟秩。
我們看到第二個(gè)方法多了一個(gè)dst,這個(gè)又是一個(gè)什么玩意呢绽淘,其實(shí)參數(shù)dst存儲(chǔ)平移后的path的涵防。

用例子來說明一下:

Path path = new Path();
path.addCircle(300, 200, 100, Path.Direction.CW);

Path dst = new Path();
dst.addCircle(500, 200, 200, Path.Direction.CW);

path.offset(-100, 100, dst);

paint.setColor(Color.RED);
canvas.drawPath(path, paint);

paint.setColor(Color.BLUE);
canvas.drawPath(dst, paint);

效果圖:


從運(yùn)行效果圖可以看出,雖然我們?cè)赿st中添加了一個(gè)圓形收恢,但是并沒有表現(xiàn)出來武学,所以祭往,當(dāng)dst中存在內(nèi)容時(shí)伦意,dst中原有的內(nèi)容會(huì)被清空,而存放平移后的path硼补。
原來的path并沒有變化驮肉。

FillType

方法預(yù)覽:

public void setFillType(Path.FillType ft)
public Path.FillType getFillType()

setFillType方法中的參數(shù)Path.FillType為枚舉類型:

FillType值 含義
FillType.WINDING 取path所有所在區(qū)域 默認(rèn)值
FillType.EVEN_ODD 取path所在并不相交區(qū)域
FillType.INVERSE_WINDING 取path所有未占區(qū)域
FillType.INVERSE_EVEN_ODD 取path未占或相交區(qū)域

setFillType

WINDING
Path path = new Path();
path.addCircle(300,200,100, Path.Direction.CW);
path.addCircle(200,200,100, Path.Direction.CW);
path.setFillType(Path.FillType.WINDING);
canvas.drawPath(path, paint);

效果圖:


EVEN_ODD
INVERSE_WINDING
INVERSE_EVEN_ODD

isInverseFillType

是否是逆填充模式:
WINDING 和 EVEN_ODD 返回false;
INVERSE_WINDING 和 INVERSE_EVEN_ODD 返回true已骇;

toggleInverseFillType

切換相反的填充模式离钝,如果填充模式為WINDING則填充模式為INVERSE_WINDING,反之為WINDING模式褪储;如果填充模式為EVEN_ODD則填充模式為INVERSE_EVEN_ODD卵渴,反之為EVEN_ODD模式。

舉個(gè)例子:

Path path = new Path();
path.addCircle(300,200,100, Path.Direction.CW);
path.addCircle(200,200,100, Path.Direction.CW);
path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
path.toggleInverseFillType();
canvas.drawPath(path, paint);

效果圖和上面EVEN_ODD模式一模一樣鲤竹。

引用:

自定義View之繪圖篇(二):路徑(Path)

文字(Text)

一浪读、文字

相關(guān)方法預(yù)覽:

//普通設(shè)置
paint.setAntiAlias(true); //指定是否使用抗鋸齒功能  如果使用會(huì)使繪圖速度變慢 默認(rèn)false
setStyle(Paint.Style.FILL);//繪圖樣式  對(duì)于設(shè)文字和幾何圖形都有效  
setTextAlign(Align.LEFT);//設(shè)置文字對(duì)齊方式  取值:align.CENTER、align.LEFT或align.RIGHT 默認(rèn)align.LEFT
paint.setTextSize(12);//設(shè)置文字大小

//樣式設(shè)置  
paint.setFakeBoldText(true);//設(shè)置是否為粗體文字  
paint.setUnderlineText(true);//設(shè)置下劃線  
paint.setTextSkewX((float) -0.25);//設(shè)置字體水平傾斜度  普通斜體字是-0.25  
paint.setStrikeThruText(true);//設(shè)置帶有刪除線效果 

//其它設(shè)置  
paint.setTextScaleX(2);//只會(huì)將水平方向拉伸  高度不會(huì)變  

1辛藻、文本繪圖樣式

先來看看下面這個(gè)例子:

mPaint.setStrokeWidth(5);
mPaint.setTextSize(80);
//設(shè)置繪圖樣式 為填充
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("我是一顆小小的石頭", 100,100, mPaint);

//設(shè)置繪圖樣式 為描邊
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawText("我是一顆小小的石頭", 100,300, mPaint);

//設(shè)置繪圖樣式 為填充且描邊
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText("我是一顆小小的石頭", 100,500, mPaint);

效果圖:


2碘橘、setTextAlign(Paint.Align align) 文字的對(duì)齊方式

mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(80);
//設(shè)置對(duì)齊方式  左對(duì)齊
mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("小小的石頭", 500,100, mPaint);//點(diǎn)(500,100)在文本的左邊

//設(shè)置對(duì)齊方式  中間對(duì)齊
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("小小的石頭", 500,200, mPaint);//點(diǎn)(500,100)在文本的中間

//設(shè)置對(duì)齊方式  右對(duì)齊
mPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText("小小的石頭", 500,300, mPaint);//點(diǎn)(500,100)在文本的右邊

3、文字樣式設(shè)置

canvas.drawText("小小的石頭", 200, 100, mPaint); //不帶任何效果

mPaint.setFakeBoldText(true);//是否粗體文字
mPaint.setUnderlineText(true);//設(shè)置下劃線
mPaint.setStrikeThruText(true);//設(shè)置刪除線效果
canvas.drawText("小小的石頭", 200, 200, mPaint);

4吱肌、文字傾斜度設(shè)置

mPaint.setTextSkewX(-0.25f);
canvas.drawText("小小的石頭", 100, 100, mPaint);

mPaint.setTextSkewX(0.25f);
canvas.drawText("小小的石頭", 100, 200, mPaint);

mPaint.setTextSkewX(-0.5f);
canvas.drawText("小小的石頭", 100, 300, mPaint);

可見普通斜體字是-0.25f痘拆,大于-0.25f 向左傾斜,小于 -0.25f 向右傾斜氮墨。

5纺蛆、水平拉伸設(shè)置

mPaint.setTextScaleX(1);//不拉伸
canvas.drawText("小小的石頭", 100, 100, mPaint);

mPaint.setTextScaleX(2);//水平方向拉伸2倍
canvas.drawText("小小的石頭", 100, 200, mPaint);

mPaint.setTextScaleX(3);//水平方向拉伸3倍
canvas.drawText("小小的石頭", 100, 300, mPaint);

由上可以發(fā)現(xiàn),僅是水平方向拉伸规揪,高度并未改變桥氏。

canvas繪制文字

1、drawText

方法預(yù)覽:

drawText(String text, float x, float y, Paint paint)

drawText(char[] text, int index, int count, float x, float y, Paint paint)
        //text 字節(jié)數(shù)組粒褒;index 表示第一個(gè)要繪制的文字索引识颊;count 需要繪制的文字個(gè)數(shù)

drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
//text 表示字符;start 開始截取字符的索引號(hào);end 結(jié)束截取字符的索引號(hào)祥款。[start , end ) 包含 start 但不包含 end

//`drawTextRun`方法是在 **skd23** 才引入的方法
drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, float x, float y, boolean isRtl, Paint paint)//isRtl 表示排列順序清笨,true 表示正序,false 表示倒序

drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)

第一個(gè)構(gòu)造函數(shù) : 是最普通的刃跛。
第二個(gè)構(gòu)造函數(shù) : text 字節(jié)數(shù)組抠艾;index 表示第一個(gè)要繪制的文字索引;count 需要繪制的文字個(gè)數(shù)桨昙。
第三個(gè)構(gòu)造函數(shù) : text 表示字符 (注意與上面比較)检号;start 開始截取字符的索引號(hào);end 結(jié)束截取字符的索引號(hào)蛙酪。(注意和上面的區(qū)別) [start , end ) 包含 start 但不包含 end

第四個(gè)構(gòu)造函數(shù)和第五個(gè)構(gòu)造函數(shù) : contextIndex 和 index 相同 齐苛; contextCount 大于等于 count ; isRtl 表示排列順序桂塞,true 表示正序凹蜂,false 表示倒序(這里的倒是指第一個(gè)字符變到最后一個(gè)字符,最后一個(gè)字符變到第一個(gè)字符)阁危。 注意了drawTextRun方法是在 skd23 才引入的方法玛痊。

canvas.drawText("我是一顆小小的石頭".toCharArray(), 1, 4, 100, 100, mPaint);

canvas.drawText("我是一顆小小的石頭", 1, 4, 100, 200, mPaint);

//最小sdk23
canvas.drawTextRun("我是一顆小小的石頭".toCharArray(), 1, 4, 1, 4, 100, 300, true, mPaint);

canvas.drawTextRun("我是一顆小小的石頭".toCharArray(), 1, 4, 1, 4, 100, 400, false, mPaint);

2、drawPosText

方法預(yù)覽:

drawPosText(String text, float[] pos, Paint paint)

drawPosText(char[] text, int index, int count, float[] pos, Paint paint)

這里的參數(shù)含義和 drawText 方法的參數(shù)一樣狂打。我們來看個(gè)簡單的例子:

float[] pos = {100, 100, 200, 200, 300, 300, 400, 400, 500, 500, 600, 600};
canvas.drawPosText("我是一顆小小", pos, mPaint);

3擂煞、drawTextOnPath

方法預(yù)覽:

drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)

參數(shù)含義:

  • index,count : 和上面截取參數(shù)含義一樣趴乡,這里不再累訴对省。

  • hOffset : 與路徑起點(diǎn)水平偏移量
    正數(shù)向 X 軸正方形移動(dòng)(右移)浙宜;負(fù)數(shù)向 X 軸負(fù)方向移動(dòng)(左移)官辽;
    如果是圓弧:正數(shù)是順時(shí)針的偏移量粟瞬;反之是逆時(shí)針的偏移量

  • vOffset : 與路徑中心垂直偏移量同仆。
    正數(shù)向 Y 軸正方形移動(dòng)(下移);負(fù)數(shù)向 Y 軸負(fù)方向移動(dòng)(上移)
    如果是圓蝗蛊贰:正數(shù)是向圓心移動(dòng)俗批;反之是遠(yuǎn)離圓心

mPath.moveTo(100,100);
mPath.lineTo(800,100);
canvas.drawTextOnPath("我是一顆小小的石頭",mPath,10,-10,mPaint);

路徑為圓弧的例子:

mPath.addCircle(500,500,200, Path.Direction.CW);
canvas.drawTextOnPath("我是一顆小小的石頭",mPath,40,-20,mPaint);
11

4、Typeface(字體樣式設(shè)置)

方法預(yù)覽:

setTypeface(Typeface typeface)

Typeface是用來設(shè)置字體樣式的市怎,通過paint.setTypeface()來指定岁忘。可以指定系統(tǒng)中的字體樣式区匠,也可以指定自定義的樣式文件中獲取干像。
要構(gòu)建Typeface時(shí)帅腌,可以指定所用樣式的正常體、斜體麻汰、粗體等速客,如果指定樣式中,沒有相關(guān)文字的樣式就會(huì)用系統(tǒng)默認(rèn)的樣式來顯示五鲫,一般默認(rèn)是宋體溺职。

參數(shù)類型是枚舉類型,枚舉值如下:

  1. Typeface.NORMAL //正常體
  2. Typeface.BOLD //粗體
  3. Typeface.ITALIC //斜體
  4. Typeface.BOLD_ITALIC //粗斜體

a位喂、系統(tǒng)字體

方法預(yù)覽:

create(String familyName, int style) //字體名

create(Typeface family, int style)  //類型

defaultFromStyle(int style)       //默認(rèn)類型

我們來看一個(gè)簡單的例子:

typeface = Typeface.create("宋體", Typeface.NORMAL);
mPaint.setTypeface(typeface);
canvas.drawText("我是一顆小小的石頭", 100, 100, mPaint);

typeface = Typeface.create("楷體", Typeface.NORMAL);
mPaint.setTypeface(typeface);
canvas.drawText("我是一顆小小的石頭", 100, 200, mPaint);
12

從上圖可以看出來浪耘,設(shè)置楷體根本沒起作用,在系統(tǒng)的字體當(dāng)中沒有找到楷體塑崖。

b七冲、自定義字體

方法預(yù)覽:

createFromAsset(AssetManager mgr, String path) //Asset中獲取

createFromFile(File path) //文件路徑獲取

createFromFile(String path) //外部路徑獲取

由于后面兩個(gè)方法比較簡單,主要來看一下第一個(gè)方法弃舒。

首先在main下創(chuàng)建assets文件夾癞埠,然后在assets文件夾創(chuàng)建fonts文件夾状原,最后在fonts文件夾下放入font1.ttf聋呢,如圖:

13
typeface = Typeface.createFromAsset(mContext.getAssets(), "fonts/font1.ttf");
//Typeface.createFromFile(mContext.getFilesDir()+"/font1.ttf")
mPaint.setTypeface(typeface);
canvas.drawText("我是一顆小小的石頭", 100, 100, mPaint);

typeface = Typeface.createFromAsset(mContext.getAssets(), "fonts/font2.ttf");
mPaint.setTypeface(typeface);
canvas.drawText("我是一顆小小的石頭", 100, 200, mPaint);

效果圖:


14

引用:

自定義View之繪圖篇(三):文字(Text)

baseLine和FontMetrics

了解baseLine和FontMetrics有助于我們理解drawText()繪制文字的原理

一、baseLine 基線

記得小時(shí)候練習(xí)字母用的是四線格本颠区,把字母寫在四線格內(nèi)削锰,如下:

那么在canvasdrawText繪制文字時(shí)候,也是有規(guī)則的毕莱,這個(gè)規(guī)則就是baseLine(基線)器贩。什么又是基線了,說白了就是一條直線朋截,我們這里理解的是確定它的位置蛹稍。我們先來看一下基線:

9

從上圖看出:基線等同四線格的第三條線,在Android中基線的位置定了部服,那么文字的位置也就定了唆姐。

1、canvas.drawText()

方法預(yù)覽:

drawText(String text, float x, float y, Paint paint)

參數(shù):
text 需要繪制的文字
x 繪制文字原點(diǎn)X坐標(biāo)
y 繪制文字原點(diǎn)Y坐標(biāo)
paint 畫筆

我們先來看一張圖:

需要注意的是x,y并不是文字左上角的坐標(biāo)點(diǎn)廓八,它比較特殊奉芦,y所代表的是基線坐標(biāo)y的坐標(biāo)

我們具體來看看drawText()方法剧蹂,這里以一個(gè)例子的形式來理解:

      mPaint.setAntiAlias(true);
      mPaint.setColor(Color.RED);
      mPaint.setStyle(Paint.Style.FILL);
      mPaint.setTextSize(120);

      canvas.drawText("abcdefghijk",200,200,mPaint);

      mPaint.setColor(Color.parseColor("#23AC3B"));
      canvas.drawLine(200,200,getWidth(),200,mPaint);

效果圖:


證實(shí)了y是基線y的坐標(biāo)點(diǎn)声功。

結(jié)論:
1、canvas.drawText()中參數(shù)y是基線y的坐標(biāo)
2宠叼、x坐標(biāo)先巴、基線位置、文字大小確定,文字的位置就是確定的了伸蚯。

2醋闭、mPaint.setTextAlign(Paint.Align.XXX);

我們可以從上面的例子看出,x代表的是文字開始繪制的地方朝卒。我第一次使用的時(shí)候也是這么認(rèn)為的证逻,可是我寫了幾個(gè)例子,發(fā)現(xiàn)我理解錯(cuò)了抗斤。那么正確的理解又是什么呢?

x代表所要繪制文字所在矩形的相對(duì)位置囚企。相對(duì)位置就是指定點(diǎn)(x,y)在所要繪制矩形的位置。我們知道所繪制矩形的縱坐標(biāo)是由Y值來確定的瑞眼,而相對(duì)x坐標(biāo)的位置龙宏,只有左、中伤疙、右三個(gè)位置了银酗。也就是所繪制矩形可能是在x坐標(biāo)相對(duì)于文字的左側(cè),中間或者右邊繪制徒像,而定義在x坐標(biāo)在所繪制矩形相對(duì)位置的函數(shù)是:

setTextAlign(Paint.Align align)

Paint.Align是枚舉類型躏升,值分別為 : Paint.Align.LEFT,Paint.Align.CENTER和Paint.Align.RIGHT候醒。

我們來分別看一看設(shè)置不同值時(shí)馅精,繪制的結(jié)果是怎么樣的。

(1)旁涤、Paint.Align.LEFT

mPaint.setTextAlign(Paint.Align.LEFT);//主要是這里的取值不一樣
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(120);

canvas.drawText("abcdefghijk", 200, 200, mPaint);

mPaint.setColor(Color.parseColor("#23AC3B"));
canvas.drawLine(0, 200, getWidth(), 200, mPaint);
canvas.drawLine(200, 0, 200, getHeight(), mPaint);

效果圖如下:

6

可以看出(x,y)文字矩形下邊的左邊翔曲。

(2)、Paint.Align.CENTER

mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(120);

canvas.drawText("abcdefghijk", 200, 200, mPaint);

mPaint.setColor(Color.parseColor("#23AC3B"));
canvas.drawLine(0, 200, getWidth(), 200, mPaint);
canvas.drawLine(200, 0, 200, getHeight(), mPaint);

效果圖:

7

可以看出(x,y)位于文字矩形下邊的中間劈愚,換句話說瞳遍,系統(tǒng)會(huì)根據(jù)(x,y)的位置和文字矩形大小,會(huì)計(jì)算出當(dāng)前開始繪制的點(diǎn)菌羽。以使原點(diǎn)(x,y)正好在所要繪制的矩形下邊的中間掠械。

(3)、Paint.Align.RIGHT

mPaint.setTextAlign(Paint.Align.RIGHT);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(120);

canvas.drawText("abcdefghijk", 200, 200, mPaint);

mPaint.setColor(Color.parseColor("#23AC3B"));
canvas.drawLine(0, 200, getWidth(), 200, mPaint);
canvas.drawLine(200, 0, 200, getHeight(), mPaint);

效果圖:


可以看出(x,y)文字矩形下邊的右邊算凿。

二份蝴、FontMetrics

10

從圖中可以知道,除了基線氓轰,還有另外的四條線婚夫,它們分別是 topascent署鸡,descentbottom案糙,它們的含義分別為:

  1. top:可繪制的最高高度所在線
  2. bottom:可繪制的最低高度所在線
  3. ascent :系統(tǒng)建議的限嫌,繪制單個(gè)字符時(shí),字符應(yīng)當(dāng)?shù)淖罡吒叨人诰€
  4. descent:系統(tǒng)建議的时捌,繪制單個(gè)字符時(shí)怒医,字符應(yīng)當(dāng)?shù)淖畹透叨人诰€

1、獲取實(shí)例

Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();

Paint.FontMetricsInt fm=  mPaint.getFontMetricsInt();

兩個(gè)構(gòu)造方法的區(qū)別是奢讨,得到對(duì)象的成員變量的值一個(gè)為float類型稚叹,一個(gè)為int類型。

2拿诸、成員變量

FontMetrics扒袖,它里面有如下五個(gè)成員變量:

float ascent = fontMetrics.ascent;
float descent = fontMetrics.descent;
float top = fontMetrics.top;
float bottom = fontMetrics.bottom;
float leading = fontMetrics.leading;

ascent,descent,top,bottom,leading 這些線的位置要怎么計(jì)算出來呢?我們先來看個(gè)圖:

11

那么它們的計(jì)算方法如下:

ascent = ascent線的y坐標(biāo) - baseline線的y坐標(biāo)亩码;//負(fù)數(shù)
descent = descent線的y坐標(biāo) - baseline線的y坐標(biāo)季率;//正數(shù)
top = top線的y坐標(biāo) - baseline線的y坐標(biāo);//負(fù)數(shù)
bottom = bottom線的y坐標(biāo) - baseline線的y坐標(biāo)描沟;//正數(shù)

leading = top線的y坐標(biāo) - ascent線的y坐標(biāo)飒泻;//負(fù)數(shù)

FontMetrics的這幾個(gè)變量的值都是以baseLine為基準(zhǔn)的。
對(duì)于ascent來說吏廉,baseline線在ascent線之下泞遗,所以必然baseline的y值要大于ascent線的y值,所以ascent變量的值是負(fù)的迟蜜。其他幾個(gè)同理刹孔。

同樣我們可以推算出:

ascent線Y坐標(biāo) = baseline線的y坐標(biāo) + fontMetric.ascent;
descent線Y坐標(biāo) = baseline線的y坐標(biāo) + fontMetric.descent娜睛;
top線Y坐標(biāo) = baseline線的y坐標(biāo) + fontMetric.top
bottom線Y坐標(biāo) = baseline線的y坐標(biāo) + fontMetric.bottom卦睹;

3畦戒、繪制ascent,descent结序,top障斋,bottom線

直接貼代碼:

int baseLineY = 200;
mPaint.setTextSize(120);
canvas.drawText("abcdefghijkl's", 200, baseLineY, mPaint);

Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();

float top = fontMetrics.top + baseLineY;
float ascent = fontMetrics.ascent + baseLineY;
float descent = fontMetrics.descent + baseLineY;
float bottom = fontMetrics.bottom + baseLineY;

//繪制基線
mPaint.setColor(Color.parseColor("#FF1493"));
canvas.drawLine(0, baseLineY, getWidth(), baseLineY, mPaint);

//繪制top直線
mPaint.setColor(Color.parseColor("#FFB90F"));
canvas.drawLine(0, top, getWidth(), top, mPaint);

//繪制ascent直線
mPaint.setColor(Color.parseColor("#b03060"));
canvas.drawLine(0, ascent, getWidth(), ascent, mPaint);

//繪制descent直線
mPaint.setColor(Color.parseColor("#912cee"));
canvas.drawLine(0, descent, getWidth(), descent, mPaint);

//繪制bottom直線
mPaint.setColor(Color.parseColor("#1E90FF"));
canvas.drawLine(0, bottom, getWidth(), bottom, mPaint);

在這段代碼中,我們需要注意的是:
canvas.drawText()中參數(shù)y是基線y的位置徐鹤;
mPaint.setTextAlign(Paint.Align.LEFT);指點(diǎn)(200垃环,200)在文字矩形的左邊。然后計(jì)算各條直線的y坐標(biāo):

float top = fontMetrics.top + baseLineY;
float ascent = fontMetrics.ascent + baseLineY;
float descent = fontMetrics.descent + baseLineY;
float bottom = fontMetrics.bottom + baseLineY;

效果圖:


4返敬、繪制文字最小矩形遂庄、文字寬度、文字高度

(1)繪制文字最小矩形

drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)需要繪制矩形就需要知道矩形左上角坐標(biāo)點(diǎn)劲赠,矩形長和寬涛目。以上面例子為例:

left = 200
top = ascent 秸谢;
right= 200+矩形寬度;
bottom = descent霹肝;

這樣我們就可以繪制出最小矩形:

(2)文字高度

float top = fontMetrics.top + baseLineY;
float bottom = fontMetrics.bottom + baseLineY;
//文字高度
float height= bottom - top; //注意top為負(fù)數(shù)
//文字中點(diǎn)y坐標(biāo)
float center = (bottom - top) / 2;

當(dāng)然也可以: float height=Math.abs(top-bottom);

(3)文字寬度

 String text="abcdefghijkl's";
 //文字寬度
 float width = mPaint.measureText(text);

已知中線估蹄,獲取baseline

你可能會(huì)說這個(gè)還不簡單:

Paint mPaint = new Paint();
mPaint.setTextSize(80);
mPaint.setColor(Color.WHITE);
mPaint.setAntiAlias(true);
String text = "FontMetrics的那些猜想";
Paint.FontMetrics fm = mPaint.getFontMetrics();
//獲取文字高度
float fontHeight = fm.bottom - fm.top;
//獲取文字寬度
float fontWidth = mPaint.measureText(text);
//繪制中線
canvas.drawLine(0, centerY, getWidth(), centerY, mPaint);
//繪制文本
canvas.drawText(text, centerX - fontWidth / 2, centerY + fontHeight / 2, mPaint);

效果圖:

font

怎么會(huì)這樣呢?

我們一起來分析下原因沫换,先來看一張分析圖:

font

那么我們就可以得出:

baseline=centerY+A-fm.bottom;

如果以:

baseline=centerY + fontHeight / 2;

那么就會(huì)以bottom線作為文字的基線臭蚁,這樣就會(huì)造成文字位于中線之下。

結(jié)論

我們最終可知讯赏,當(dāng)給定中間線center位置以后刊棕,那么baseline的位置為:

baseline = center + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom;

FontMetrics.bottom注意這里為正數(shù)。

效果一覽:

font

我們還可以這樣獲取文字高度:

public float getFontHeight(Paint paint, String str) {
    Rect rect = new Rect();
    paint.getTextBounds(str, 0, str.length(), rect);
    return rect.height();
}

經(jīng)測試得出:

Paint.FontMetrics fm = mPaint.getFontMetrics();

注意:fm 值和手機(jī)密度沒有關(guān)系待逞,并且fm.bottom/fm.top=4(約等于)甥角。

引用:

自定義View之繪圖篇(四):baseLine和FontMetrics

Canvas

一、什么是Canvas识樱?

什么是Canvas嗤无?官方文檔是這么介紹的:

The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect,Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

Canvas 類是用于繪圖的,繪制圖形怜庸,你需要4個(gè)基本元素

  • 畫在哪当犯。畫在Bitmap上。(相當(dāng)于紙張割疾,我們把圖畫在紙張上面)

  • 怎么畫嚎卫。(調(diào)用canvas執(zhí)行繪圖操作。比如canvas.drawCircle()宏榕,canvas.drawLine()拓诸,canvas.drawPath()將我們需要的圖像畫出來。)

  • 畫的內(nèi)容麻昼。(比如我想在紙張畫一朵花奠支,根據(jù)自己需求畫圓,畫直線抚芦,畫路徑等)

  • 用什么畫倍谜。(在紙張上畫一朵花,肯定是用筆來畫的叉抡,這里的筆指的是 Paint)

Canvas 畫布無限大尔崔,它并沒有邊界。怎么來理解這句話呢褥民?打個(gè)比方:畫布就是窗外的景色季春,而手機(jī)屏幕就是窗口,你在窗口看到窗外的景色是有限的轴捎。同樣我也可以把圖形畫到屏幕之外鹤盒,通過對(duì) Canvas 的變換與操作蚕脏,讓屏幕之外的圖形顯示到屏幕里面。

二侦锯、Canvas 繪圖

Canvas 繪制一些常見的圖形:

mPaint.setColor(Color.RED);
//繪制直線
canvas.drawLine(100,100,600,100,mPaint);

//繪制矩形
canvas.drawRect(100,200,600,400,mPaint);

//繪制文字
mPaint.setTextSize(60);
mPaint.setStrokeWidth(2);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("我是一顆石頭",100,500,mPaint);

三驼鞭、Canvas 的變換與操作

有時(shí)候我們還需要對(duì) Canvas 做一些操作,比如旋轉(zhuǎn)尺碰,裁剪挣棕,平移等等。

  • canvas.translate 平移

  • canvas.rotate 旋轉(zhuǎn)

  • canvas.scale 縮放

  • canvas.skew 錯(cuò)切

  • canvas.clipRect 裁剪

  • canvas.save和canvas.restore 保存和恢復(fù)

  • PorterDuffXfermode 圖像混合 (paint相關(guān)方法)

1亲桥、平移(translate)

canvas中有一個(gè)函數(shù)translate()是用來實(shí)現(xiàn)畫布平移的洛心,畫布的原狀是以左上角為原點(diǎn),向左是X軸正方向题篷,向下是Y軸正方向词身,如下圖所示

translate函數(shù)其實(shí)實(shí)現(xiàn)的相當(dāng)于平移坐標(biāo)系,即平移坐標(biāo)系的原點(diǎn)的位置番枚。translate()函數(shù)的原型如下:

void translate(float dx, float dy)

參數(shù)說明:
float dx:水平方向平移的距離法严,正數(shù)指向正方向(向右)平移的量,負(fù)數(shù)為向負(fù)方向(向左)平移的量
flaot dy:垂直方向平移的距離葫笼,正數(shù)指向正方向(向下)平移的量深啤,負(fù)數(shù)為向負(fù)方向(向上)平移的量

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);      
    Paint paint = new Paint();  
    paint.setColor(Color.GREEN);  
    paint.setStyle(Style.FILL);  
//translate  平移,即改變坐標(biāo)系原點(diǎn)位置      
//  canvas.translate(100, 100);  
    Rect rect1 = new Rect(0,0,400,220);  
    canvas.drawRect(rect1, paint);  
}  

1、上面這段代碼路星,先把canvas.translate(100, 100);注釋掉溯街,看原來矩形的位置,然后打開注釋洋丐,看平移后的位置呈昔,對(duì)比如下圖:


屏幕顯示與Canvas的關(guān)系

很多童鞋一直以為顯示所畫東西的改屏幕就是Canvas,其實(shí)這是一個(gè)非常錯(cuò)誤的理解垫挨,比如下面我們這段代碼:

這段代碼中韩肝,同一個(gè)矩形,在畫布平移前畫一次九榔,平移后再畫一次,大家會(huì)覺得結(jié)果會(huì)怎樣涡相?

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);  
      
    //構(gòu)造兩個(gè)畫筆哲泊,一個(gè)紅色,一個(gè)綠色  
    Paint paint_green = generatePaint(Color.GREEN, Style.STROKE, 3);  
    Paint paint_red   = generatePaint(Color.RED, Style.STROKE, 3);  
      
    //構(gòu)造一個(gè)矩形  
    Rect rect1 = new Rect(0,0,400,220);  
    //在平移畫布前用綠色畫下邊框  
    canvas.drawRect(rect1, paint_green);  
      
    //平移畫布后,再用紅色邊框重新畫下這個(gè)矩形  
    canvas.translate(100, 100);  
    canvas.drawRect(rect1, paint_red);  
}  
private Paint generatePaint(int color, Paint.Style style, int width) {
    Paint paint = new Paint();
    paint.setColor(color);
    paint.setStyle(style);
    paint.setStrokeWidth(width);
    return paint;
} 

代碼分析:
這段代碼中催蝗,對(duì)于同一個(gè)矩形切威,在平移畫布前利用綠色畫下矩形邊框,在平移后丙号,再用紅色畫下矩形邊框先朦。大家是不是會(huì)覺得這兩個(gè)邊框會(huì)重合缰冤?實(shí)際結(jié)果是這樣的。

為什么綠色框并沒有移動(dòng)喳魏?

這是由于屏幕顯示與Canvas根本不是一個(gè)概念棉浸!
Canvas是一個(gè)很虛幻的概念,相當(dāng)于一個(gè)透明圖層(用過PS的同學(xué)應(yīng)該都知道)刺彩,每次Canvas畫圖時(shí)(即調(diào)用Draw系列函數(shù))迷郑,都會(huì)產(chǎn)生一個(gè)透明圖層,然后在這個(gè)圖層上畫圖创倔,畫完之后覆蓋在屏幕上顯示嗡害。所以上面的兩個(gè)結(jié)果是由下面幾個(gè)步驟形成的:

1、調(diào)用canvas.drawRect(rect1, paint_green);時(shí)畦攘,產(chǎn)生一個(gè)Canvas透明圖層霸妹,由于當(dāng)時(shí)還沒有對(duì)坐標(biāo)系平移,所以坐標(biāo)原點(diǎn)是(0知押,0)叹螟;再在系統(tǒng)在Canvas上畫好之后,覆蓋到屏幕上顯示出來朗徊,過程如下圖:

2首妖、然后再第二次調(diào)用canvas.drawRect(rect1, paint_red);時(shí),又會(huì)重新產(chǎn)生一個(gè)全新的Canvas畫布爷恳,但此時(shí)畫布坐標(biāo)已經(jīng)改變了有缆,即向右和向下分別移動(dòng)了100像素,所以此時(shí)的繪圖方式為:(合成視圖温亲,從上往下看的合成方式)

上圖展示了棚壁,上層的Canvas圖層與底部的屏幕的合成過程,由于Canvas畫布已經(jīng)平移了100像素栈虚,所以在畫圖時(shí)是以新原點(diǎn)來產(chǎn)生視圖的袖外,然后合成到屏幕上,這就是我們上面最終看到的結(jié)果了魂务。我們看到屏幕移動(dòng)之后曼验,有一部分超出了屏幕的范圍,那超出范圍的圖像顯不顯示呢粘姜,當(dāng)然不顯示了鬓照!也就是說,Canvas上雖然能畫上孤紧,但超出了屏幕的范圍豺裆,是不會(huì)顯示的。當(dāng)然号显,我們這里也沒有超出顯示范圍臭猜,兩框框而已躺酒。

總結(jié):

1、每次調(diào)用canvas.drawXXXX系列函數(shù)來繪圖進(jìn)蔑歌,都會(huì)產(chǎn)生一個(gè)全新的Canvas畫布羹应。
2、如果在DrawXXX前丐膝,調(diào)用平移量愧、旋轉(zhuǎn)等函數(shù)來對(duì)Canvas進(jìn)行了操作,那么這個(gè)操作是不可逆的帅矗!每次產(chǎn)生的畫布的最新位置都是這些操作后的位置偎肃。(關(guān)于Save()、Restore()的畫布可逆問題的后面再講)
3浑此、在Canvas與屏幕合成時(shí)累颂,超出屏幕范圍的圖像是不會(huì)顯示出來的。

2凛俱、旋轉(zhuǎn)(Rotate)

畫布的旋轉(zhuǎn)是默認(rèn)是圍繞坐標(biāo)原點(diǎn)來旋轉(zhuǎn)的紊馏,這里容易產(chǎn)生錯(cuò)覺,看起來覺得是圖片旋轉(zhuǎn)了蒲犬,其實(shí)我們旋轉(zhuǎn)的是畫布朱监,以后在此畫布上畫的東西顯示出來的時(shí)候全部看起來都是旋轉(zhuǎn)的。其實(shí)Roate函數(shù)有兩個(gè)構(gòu)造函數(shù):

void rotate(float degrees)
void rotate (float degrees, float px, float py) 

第一個(gè)構(gòu)造函數(shù)直接輸入旋轉(zhuǎn)的度數(shù)原叮,正數(shù)是順時(shí)針旋轉(zhuǎn)赫编,負(fù)數(shù)指逆時(shí)針旋轉(zhuǎn),它的旋轉(zhuǎn)中心點(diǎn)是原點(diǎn)(0奋隶,0)
第二個(gè)構(gòu)造函數(shù)除了度數(shù)以外擂送,還可以指定旋轉(zhuǎn)的中心點(diǎn)坐標(biāo)(px,py)

下面以第一個(gè)構(gòu)造函數(shù)為例,旋轉(zhuǎn)一個(gè)矩形唯欣,先畫出未旋轉(zhuǎn)前的圖形嘹吨,然后再畫出旋轉(zhuǎn)后的圖形;

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);  
    Paint paint_green = generatePaint(Color.GREEN, Style.FILL, 5);  
    Paint paint_red   = generatePaint(Color.RED, Style.STROKE, 5);  
      
    Rect rect1 = new Rect(300,10,500,100);  
    canvas.drawRect(rect1, paint_red); //畫出原輪廓  
      
    canvas.rotate(30);//順時(shí)針旋轉(zhuǎn)畫布  
    canvas.drawRect(rect1, paint_green);//畫出旋轉(zhuǎn)后的矩形  
}   

效果圖是這樣的:

這個(gè)最終屏幕顯示的構(gòu)造過程是這樣的:

下圖顯示的是第一次畫圖合成過程境氢,此時(shí)僅僅調(diào)用canvas.drawRect(rect1, paint_red); 畫出原輪廓

然后是先將Canvas正方向依原點(diǎn)旋轉(zhuǎn)30度蟀拷,然后再與上面的屏幕合成,最后顯示出我們的復(fù)合效果萍聊。

有關(guān)Canvas與屏幕的合成關(guān)系我覺得我已經(jīng)講的夠詳細(xì)了匹厘,后面的幾個(gè)操作Canvas的函數(shù),我就不再一一講它的合成過程了脐区。

3望侈、縮放(scale )

public void scale (float sx, float sy) 
public final void scale (float sx, float sy, float px, float py)
第一個(gè)構(gòu)造函數(shù):

float sx:水平方向伸縮的比例亦渗,假設(shè)原坐標(biāo)軸的比例為n,不變時(shí)為1,在變更的X軸密度為n*sx;所以,sx為小數(shù)為縮小趴梢,sx為整數(shù)為放大
float sy:垂直方向伸縮的比例,同樣斋扰,小數(shù)為縮小谴仙,整數(shù)為放大

注意:這里有X、Y軸的密度的改變默伍,顯示到圖形上就會(huì)正好相同欢嘿,比如X軸縮小,那么顯示的圖形也會(huì)縮小也糊。一樣的炼蹦。

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(8);
        
        Rect rect = new Rect(100, 100, 200, 200);
//原圖
        paint.setColor(Color.RED);
        canvas.drawRect(rect, paint);
//畫布縮放方法1
        canvas.scale(0.5f, 2f);
        paint.setColor(Color.BLUE);
        canvas.drawRect(rect, paint);

因?yàn)槭钦麄€(gè)畫布的伸縮,對(duì)應(yīng)連Stroke線的粗細(xì)也發(fā)生了變化

由圖可知:
原圖:Rect(100, 100, 200, 200)
移動(dòng)后:Rect(50, 200, 100, 400)
公式

原圖:(l, t, r, b)
scale (sx,  sy)
移動(dòng)后:(l*sx, t*sy, r*sx, b*sy)
第二個(gè)構(gòu)造函數(shù):

void scale (float sx, float sy, float px, float py)
Preconcat the current matrix with the specified scale.
Parameters
sx
float: The amount to scale in X
sy
float: The amount to scale in Y
px
float: The x-coord for the pivot point (unchanged by the scale)
py
float: The y-coord for the pivot point (unchanged by the scale)

px 和 py 分別為縮放的中心點(diǎn)狸剃,不設(shè)置的話默認(rèn)為畫布原點(diǎn)(0, 0)

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);

        Rect rect = new Rect(100, 100, 200, 200);
//原圖
        canvas.save();
        paint.setColor(Color.RED);
        canvas.drawRect(rect, paint);
//畫布縮放方法2
        canvas.scale(2f, 2f, 0, 0);
        paint.setColor(Color.BLUE);
        canvas.drawRect(rect, paint);
//畫布縮放方法2(以正方形中心點(diǎn)為縮放中心)
        canvas.restore();
        canvas.scale(2f, 2f, 150, 150);
        paint.setColor(Color.GREEN);
        canvas.drawRect(rect, paint);
原理

源碼如下:

public final void scale(float sx, float sy, float px, float py) {
    translate(px, py);
    scale(sx, sy);
    translate(-px, -py);
}

步驟:
將畫布平移px,py掐隐,然后scale,scale結(jié)束之后再將畫布平移-px,-py钞馁。

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(8);

        Rect rect = new Rect(100, 100, 200, 200);
//原圖
        paint.setColor(Color.RED);
        canvas.drawRect(rect, paint);
//畫布縮放方法2
        canvas.scale(0.5f, 2f, 100, 100);//px虑省、py
        paint.setColor(Color.BLUE);
        canvas.drawRect(rect, paint);

原理演示

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(8);
        
        Rect rect = new Rect(100, 100, 200, 200);

//原圖
        paint.setColor(Color.RED);
        canvas.drawRect(rect, paint);
//以下三步模擬canvas.scale(0.5f, 2f, 100, 100);
//第①步:移動(dòng)
        canvas.translate(100, 100);
        paint.setColor(Color.GREEN);
        canvas.drawRect(rect, paint);
//第②步:縮放
        canvas.scale(0.5f, 2f);
        paint.setColor(Color.YELLOW);
        canvas.drawRect(rect, paint);
//第③步:反向移動(dòng)
        canvas.translate(-100, -100);
        paint.setColor(Color.BLUE);
        canvas.drawRect(rect, paint);


由圖可知:
原圖:Rect(100, 100, 200, 200)
移動(dòng)后:Rect(100, 100, 150, 300)
公式

原圖:(l,  t,  r,  b)

scale (sx,  sy,  px,  py)   
    第①步:translate(px,  py);
        原點(diǎn)(px,  py)
        圖①(l+px, t+py, r+px, b+py)
    第②步:scale(sx,  sy);
        原點(diǎn)(px,  py)
        圖②(l*sx+px, t*sy+py, r*sx+px, b*sy+py)
    第③步:translate(-px,  -py);
        原點(diǎn)(px+(-px)*sx,  py+(-py)*sy)即(px*(1-sx), py*(1-sy))
        圖③(l*sx+px*(1-sx), t*py*(1-sy)+(-py)*sy, r*sx+px*(1-sx), b*sy+py*(1-sy))

移動(dòng)后:(l*sx+px*(1-sx), t*py*(1-sy)+(-py)*sy, r*sx+px*(1-sx), b*sy+py*(1-sy))

Rt總結(jié)
縮放就是相對(duì)于原點(diǎn)距離的縮放,
移動(dòng)就是對(duì)原點(diǎn)進(jìn)行移動(dòng)僧凰;
視覺坐標(biāo)是距離原點(diǎn)的位置加上原點(diǎn)的坐標(biāo)探颈,
canvas繪畫的坐標(biāo)是相較于原點(diǎn)的坐標(biāo)

4训措、錯(cuò)切(skew)

它的構(gòu)造函數(shù):

void skew (float sx, float sy)

參數(shù)說明:
float sx:將畫布在x方向上傾斜相應(yīng)的角度伪节,sx傾斜角度的tan值
float sy:將畫布在y軸方向上傾斜相應(yīng)的角度隙弛,sy為傾斜角度的tan值架馋,

注意,這里全是傾斜角度的tan值全闷,比如我們打算在X軸方向上傾斜30度叉寂,tan30=1/√3 約等于 0.56;tan60=根號(hào)3总珠,小數(shù)對(duì)應(yīng)1.732屏鳍。

舉例(在X軸方向上傾斜45度,tan45=1):

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);
        Rect rect = new Rect(50, 50, 150, 150);
        paint.setColor(Color.RED);
        canvas.drawRect(rect, paint);

        canvas.skew(1, 0);//skew
        paint.setColor(Color.BLUE);
        canvas.drawRect(rect, paint);

可以從效果圖當(dāng)中看出來局服,我們?cè)O(shè)置 x 方向傾斜钓瞭,反而 y 方向傾斜了,這就是為什么要叫做錯(cuò)切了淫奔。你心中一定又會(huì)有疑問山涡?每個(gè)點(diǎn)的坐標(biāo)又是怎么計(jì)算的呢?那下面我們一起來分析下:

分析
設(shè)定在X軸上傾斜45°


設(shè)定在Y軸上傾斜30°

//公式推導(dǎo),以在Y軸傾斜為例skew (0, sy)
A點(diǎn)到舊X軸距離AX = 新點(diǎn)A1點(diǎn)到新X軸距離A1X1
XX1=OX * sy
新點(diǎn)A1縱坐標(biāo) = A1X1 + XX1
            = AX + OX * sy
即:新點(diǎn)A1縱坐標(biāo) = A點(diǎn)的縱坐標(biāo) + A點(diǎn)的橫坐標(biāo)*傾斜值

void skew (float sx, float sy)
Preconcat the current matrix with the specified skew.
Parameters
sx
float: The amount to skew in X
sy
float: The amount to skew in Y

在X軸方向傾斜

A 點(diǎn)橫坐標(biāo)傾斜后的值 = A點(diǎn)的橫坐標(biāo)+A點(diǎn)的縱坐標(biāo)傾斜值*
B 點(diǎn)橫坐標(biāo)傾斜后的值 = B點(diǎn)的橫坐標(biāo)+A點(diǎn)的縱坐標(biāo)*傾斜值

C 點(diǎn)橫坐標(biāo)傾斜后的值 = C點(diǎn)的橫坐標(biāo)+C點(diǎn)的縱坐標(biāo)傾斜值*
D 點(diǎn)橫坐標(biāo)傾斜后的值 = D點(diǎn)的橫坐標(biāo)+C點(diǎn)的縱坐標(biāo)*傾斜值

在Y軸方向傾斜

A 點(diǎn)縱坐標(biāo)傾斜后的值 = A點(diǎn)的縱坐標(biāo)+A點(diǎn)的橫坐標(biāo)傾斜值*
D 點(diǎn)縱坐標(biāo)傾斜后的值 = D點(diǎn)的縱坐標(biāo)+A點(diǎn)的橫坐標(biāo)*傾斜值

C 點(diǎn)縱坐標(biāo)傾斜后的值 = C點(diǎn)的縱坐標(biāo)+C點(diǎn)的橫坐標(biāo)傾斜值*
B 點(diǎn)縱坐標(biāo)傾斜后的值 = B點(diǎn)的縱坐標(biāo)+C點(diǎn)的橫坐標(biāo)*傾斜值

在y方向傾斜30度

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);
        Rect rect = new Rect(50, 100, 150, 200);
        paint.setColor(Color.RED);
        canvas.drawRect(rect, paint);

        canvas.skew(0, 0.56f);
        paint.setColor(Color.BLUE);
        canvas.drawRect(rect, paint);

5鸭丛、裁剪畫布(clip系列函數(shù))

裁剪畫布是利用Clip系列函數(shù)竞穷,通過與Rect、Path鳞溉、Region取交瘾带、并、差等集合運(yùn)算來獲得最新的畫布形狀熟菲。除了調(diào)用Save看政、Restore函數(shù)以外,這個(gè)操作是不可逆的抄罕,一但Canvas畫布被裁剪允蚣,就不能再被恢復(fù)!
Clip系列函數(shù)如下:
boolean clipPath(Path path)
boolean clipPath(Path path, Region.Op op)
boolean clipRect(Rect rect, Region.Op op)
boolean clipRect(RectF rect, Region.Op op)
boolean clipRect(int left, int top, int right, int bottom)
boolean clipRect(float left, float top, float right, float bottom)
boolean clipRect(RectF rect)
boolean clipRect(float left, float top, float right, float bottom, Region.Op op)
boolean clipRect(Rect rect)
boolean clipRegion(Region region)
boolean clipRegion(Region region, Region.Op op)

以上就是根據(jù)Rect贞绵、Path厉萝、Region來取得最新畫布的函數(shù),難度都不大榨崩,就不再一一講述谴垫。利用ClipRect() 來稍微一講。

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);  
      
    canvas.drawColor(Color.RED);  
    canvas.clipRect(new Rect(100, 100, 200, 200));  
    canvas.drawColor(Color.GREEN);  
}   

先把背景色整個(gè)涂成紅色母蛛。顯示在屏幕上
然后裁切畫布翩剪,最后最新的畫布整個(gè)涂成綠色〔式迹可見綠色部分前弯,只有一小塊,而不再是整個(gè)屏幕了秫逝。
關(guān)于兩個(gè)畫布與屏幕合成恕出,我就不再畫圖了,跟上面的合成過程是一樣的违帆。


6浙巫、畫布的保存與恢復(fù)(save()、restore())

前面我們講的所有對(duì)畫布的操作都是不可逆的刷后,這會(huì)造成很多麻煩的畴,比如,我們?yōu)榱藢?shí)現(xiàn)一些效果不得不對(duì)畫布進(jìn)行操作尝胆,但操作完了丧裁,畫布狀態(tài)也改變了,這會(huì)嚴(yán)重影響到后面的畫圖操作含衔。如果我們能對(duì)畫布的大小和狀態(tài)(旋轉(zhuǎn)角度煎娇、扭曲等)進(jìn)行實(shí)時(shí)保存和恢復(fù)就最好了二庵。
這小節(jié)就給大家講講畫布的保存與恢復(fù)相關(guān)的函數(shù)——Save()、Restore()逊桦。

int save ()
void  restore()

這兩個(gè)函數(shù)沒有任何的參數(shù)眨猎,很簡單。
Save():每次調(diào)用Save()函數(shù)强经,都會(huì)把當(dāng)前的畫布的狀態(tài)進(jìn)行保存,然后放入特定的棧中寺渗;
restore():每當(dāng)調(diào)用Restore()函數(shù)匿情,就會(huì)把棧中最頂層的畫布狀態(tài)取出來,并按照這個(gè)狀態(tài)恢復(fù)當(dāng)前的畫布信殊,并在這個(gè)畫布上做畫炬称。
為了更清晰的顯示這兩個(gè)函數(shù)的作用,下面舉個(gè)例子:

    canvas.drawColor(Color.RED);      
    //保存當(dāng)前畫布大小即整屏  
    canvas.save();   
      
    canvas.clipRect(new Rect(100, 100, 800, 800));  
    canvas.drawColor(Color.GREEN);      
    //恢復(fù)整屏畫布  
    canvas.restore();
      
    canvas.drawColor(Color.BLUE);  

他圖像的合成過程為:(最終顯示為全屏幕藍(lán)色)


下面我通過一個(gè)多次利用Save()涡拘、Restore()來講述有關(guān)保存Canvas畫布狀態(tài)的棧的概念:代碼如下:

    canvas.drawColor(Color.RED);  
    //保存的畫布大小為全屏幕大小  
    canvas.save();  
      
    canvas.clipRect(new Rect(100, 100, 800, 800));  
    canvas.drawColor(Color.GREEN);  
    //保存畫布大小為Rect(100, 100, 800, 800)  
    canvas.save();  
      
    canvas.clipRect(new Rect(200, 200, 700, 700));  
    canvas.drawColor(Color.BLUE);  
    //保存畫布大小為Rect(200, 200, 700, 700)  
    canvas.save();  
      
    canvas.clipRect(new Rect(300, 300, 600, 600));  
    canvas.drawColor(Color.BLACK);  
    //保存畫布大小為Rect(300, 300, 600, 600)  
    canvas.save();  
      
    canvas.clipRect(new Rect(400, 400, 500, 500));  
    canvas.drawColor(Color.WHITE);  

顯示效果為:

在這段代碼中玲躯,總共調(diào)用了四次Save操作。上面提到過鳄乏,每調(diào)用一次Save()操作就會(huì)將當(dāng)前的畫布狀態(tài)保存到棧中跷车,所以這四次Save()所保存的狀態(tài)的棧的狀態(tài)如下:

注意在,第四次Save()之后橱野,我們還對(duì)畫布進(jìn)行了canvas.clipRect(new Rect(400, 400, 500, 500));操作朽缴,并將當(dāng)前畫布畫成白色背景。也就是上圖中最小塊的白色部分水援,是最后的當(dāng)前的畫布密强。

如果,現(xiàn)在使用Restor()蜗元,會(huì)怎樣呢或渤,會(huì)把棧頂?shù)漠嫴既〕鰜恚?dāng)做當(dāng)前畫布的畫圖奕扣,試一下:

canvas.drawColor(Color.RED);  
//保存的畫布大小為全屏幕大小  
canvas.save();  
  
canvas.clipRect(new Rect(100, 100, 800, 800));  
canvas.drawColor(Color.GREEN);  
//保存畫布大小為Rect(100, 100, 800, 800)  
canvas.save();  
  
canvas.clipRect(new Rect(200, 200, 700, 700));  
canvas.drawColor(Color.BLUE);  
//保存畫布大小為Rect(200, 200, 700, 700)  
canvas.save();  
  
canvas.clipRect(new Rect(300, 300, 600, 600));  
canvas.drawColor(Color.BLACK);  
//保存畫布大小為Rect(300, 300, 600, 600)  
canvas.save();  
  
canvas.clipRect(new Rect(400, 400, 500, 500));  
canvas.drawColor(Color.WHITE);  
  
//將棧頂?shù)漠嫴紶顟B(tài)取出來薪鹦,作為當(dāng)前畫布,并畫成黃色背景  
canvas.restore();  
canvas.drawColor(Color.YELLOW);  

上段代碼中成畦,把棧頂?shù)漠嫴紶顟B(tài)取出來距芬,作為當(dāng)前畫布,然后把當(dāng)前畫布的背景色填充為黃色


那如果我連續(xù)Restore()三次循帐,會(huì)怎樣呢框仔?
我們先分析一下,然后再看效果:Restore()三次的話拄养,會(huì)連續(xù)出棧三次离斩,然后把第三次出來的Canvas狀態(tài)當(dāng)做當(dāng)前畫布银舱,也就是Rect(100, 100, 800, 800),所以如下代碼:

    canvas.drawColor(Color.RED);  
    //保存的畫布大小為全屏幕大小  
    canvas.save();  
      
    canvas.clipRect(new Rect(100, 100, 800, 800));  
    canvas.drawColor(Color.GREEN);  
    //保存畫布大小為Rect(100, 100, 800, 800)  
    canvas.save();  
      
    canvas.clipRect(new Rect(200, 200, 700, 700));  
    canvas.drawColor(Color.BLUE);  
    //保存畫布大小為Rect(200, 200, 700, 700)  
    canvas.save();  
      
    canvas.clipRect(new Rect(300, 300, 600, 600));  
    canvas.drawColor(Color.BLACK);  
    //保存畫布大小為Rect(300, 300, 600, 600)  
    canvas.save();  
      
    canvas.clipRect(new Rect(400, 400, 500, 500));  
    canvas.drawColor(Color.WHITE);  
      
    //連續(xù)出棧三次跛梗,將最后一次出棧的Canvas狀態(tài)作為當(dāng)前畫布寻馏,并畫成黃色背景  
    canvas.restore();  
    canvas.restore();  
    canvas.restore();  
    canvas.drawColor(Color.YELLOW);  

結(jié)果為:

7、saveLayer

public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
            @Saveflags int saveFlags)
            ...
saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
            @Saveflags int saveFlags)
            ...

public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint)
Canvas 在一般的情況下可以看作是一張畫布核偿,所有的繪圖操作如drawBitmap, drawCircle都發(fā)生在這張畫布上诚欠,這張畫板還定義了一些屬性比如Matrix,顏色等等漾岳。
但是如果需要實(shí)現(xiàn)一些相對(duì)復(fù)雜的繪圖操作轰绵,比如多層動(dòng)畫,地圖(地圖可以有多個(gè)地圖層疊加而成尼荆,比如:政區(qū)層左腔,道路層,興趣點(diǎn)層)捅儒。Canvas提供了圖層(Layer)支持液样,缺省情況可以看作是只有一個(gè)圖層Layer。如果需要按層次來繪圖巧还,Android的Canvas可以使用SaveLayerXXX, Restore 來創(chuàng)建一些中間層鞭莽,對(duì)于這些Layer是按照“棧結(jié)構(gòu)“來管理的:

創(chuàng)建一個(gè)新的Layer到“棧”中狞悲,可以使用saveLayer, savaLayerAlpha撮抓;
從“棧”中推出一個(gè)Layer摇锋,可以使用restore,restoreToCount丹拯。
但Layer入棧時(shí),后續(xù)的DrawXXX操作都發(fā)生在這個(gè)Layer上荸恕,而Layer退棧時(shí)乖酬,就會(huì)把本層繪制的圖像“繪制”到上層或是Canvas上。
在復(fù)制Layer到Canvas上時(shí)融求,可以指定Layer的透明度(Layer)咬像,這是在創(chuàng)建Layer時(shí)指定的:public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)

本例Layers 介紹了圖層的基本用法:Canvas可以看做是由兩個(gè)圖層(Layer)構(gòu)成的,為了更好的說明問題生宛,我們將代碼稍微修改一下县昂,缺省圖層繪制一個(gè)紅色的圓,在新的圖層畫一個(gè)藍(lán)色的圓陷舅,新圖層的透明度為0×88倒彰。
public class Layers extends Activity {

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint mPaint = new Paint();
    mPaint.setAntiAlias(true);

    canvas.drawColor(Color.RED);

    canvas.saveLayerAlpha(0, 0, 300, 300, 0x88, Canvas.ALL_SAVE_FLAG);//圖層1
    canvas.drawColor(Color.BLUE);

    canvas.restore();//下面兩張圖,圖1注釋掉詞句莱睁,圖2沒注釋掉
    canvas.saveLayerAlpha(100, 100, 400, 400, 0xff, Canvas.ALL_SAVE_FLAG);//圖層2
    canvas.drawColor(Color.YELLOW);
}

沒有canvas.restore();待讳,所以圖層2是基于圖層1作畫芒澜,黃色還是半透明



運(yùn)行了一次canvas.restore();,去掉棧頂一個(gè)圖層即圖層1露出畫布创淡,所以圖層2是直接在畫布上作畫痴晦,黃色不透明


saveFlags

上面我們只是粗暴的使用了ALL_SAVE_FLAG來保存的所有的信息,但是實(shí)際使用中琳彩,所有信息都保存必然增加了開銷誊酌,所以,我們應(yīng)該根據(jù)需要的動(dòng)作汁针,盡量的精確的保存少量的信息术辐。這里就需要了解各個(gè)flag的意義。

首先需要知道的是施无,使用flag的方法除了saveFlayer還有save方法,他們都可以使用flag來指定需要保存的信息必孤。那么來看看6中flag所對(duì)應(yīng)的意義:

Flag 意義 適用方法
MATRIX_SAVE_FLAG 只保存圖層的matrix矩陣 save猾骡,saveLayer
CLIP_SAVE_FLAG 只保存大小信息 save,saveLayer
HAS_ALPHA_LAYER_SAVE_FLAG 表明該圖層有透明度敷搪,和下面的標(biāo)識(shí)沖突兴想,都設(shè)置時(shí)以下面的標(biāo)志為準(zhǔn) saveLayer
FULL_COLOR_LAYER_SAVE_FLAG 完全保留該圖層顏色(和上一圖層合并時(shí),清空上一圖層的重疊區(qū)域赡勘,保留該圖層的顏色) saveLayer
CLIP_TO_LAYER_SAVE_ 創(chuàng)建圖層時(shí)嫂便,會(huì)把canvas(所有圖層)裁剪到參數(shù)指定的范圍,如果省略這個(gè)flag將導(dǎo)致圖層開銷巨大(實(shí)際上圖層沒有裁剪闸与,與原圖層一樣大)
ALL_SAVE_FLAG 保存所有信息 save毙替,saveLayer
(1) MATRIX_SAVE_FLAG

只保存圖層的matrix矩陣。
canvas中的哪些方法是利用matrix完成的践樱,這里需要明確厂画,其實(shí)我們知道,canvas的繪制拷邢,最終是發(fā)生在bitmap上的袱院,從canvas的構(gòu)造函數(shù)中也可以看出。

在Bitmap的構(gòu)造函數(shù)中可以看出bitmap的操作也是通過matrix來進(jìn)行的:

Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)

那么我們可以知道canvas的canvas.translate(平移)瞭稼、canvas.rotate(旋轉(zhuǎn))忽洛、canvas.scale(縮放)、canvas.skew(扭曲)其實(shí)都是通過matrix來達(dá)到的环肘,這一點(diǎn)可以在代碼中使用MATRIX_SAVE_FLAG來進(jìn)行驗(yàn)證欲虚。

save方法

這里舉例平移:

paint.setColor(Color.BLUE);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(200, 200);
canvas.drawRect(100, 100, 300, 300, paint);
canvas.restore();

paint.setColor(Color.RED);
canvas.drawRect(100, 100, 300, 300, paint);

可以看到平移效果得到了保存,并且可以恢復(fù)。

saveLayer方法
paint.setColor(Color.BLUE);
int count=canvas.saveLayer(0,0,1000,1000,paint,Canvas.MATRIX_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
canvas.translate(200, 200);
canvas.drawRect(100, 100, 300, 300, paint);
canvas.restoreToCount(count);

paint.setColor(Color.RED);
canvas.drawRect(100, 100, 300, 300, paint);

圖中看出效果相同

如果這里不使用MATRIX_SAVE_FLAG標(biāo)志位廷臼,那么是否會(huì)出現(xiàn)不同的效果呢苍在,使用CLIP_SAVE_FLAG標(biāo)志來試試:

paint.setColor(Color.BLUE);
int count=canvas.saveLayer(0,0,1000,1000,paint,Canvas.CLIP_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
canvas.translate(200, 200);
canvas.drawRect(100, 100, 300, 300, paint);
canvas.restoreToCount(count);

paint.setColor(Color.RED);
canvas.drawRect(100, 100, 300, 300, paint);

代碼和上面基本相同绝页,只是標(biāo)志位改變了,這里可以看到兩個(gè)圖重疊了寂恬,也就是說CLIP_SAVE_FLAG標(biāo)志位并沒有保存相關(guān)的位移信息续誉,導(dǎo)致restore的時(shí)候沒能恢復(fù)。

(2) CLIP_SAVE_FLAG

看了上面的MATRIX_SAVE_FLAG初肉,這里的意義基本知道酷鸦,主要就是保存裁剪相關(guān)的信息
由于和上面的示例基本類似牙咏,這里就不再做講解了臼隔。

(3) FULL_COLOR_LAYER_SAVE_FLAG 和 HAS_ALPHA_LAYER_SAVE_FLAG

這兩個(gè)方法是saveLayer專用的方法,HAS_ALPHA_LAYER_SAVE_FLAG為layer添加一個(gè)透明通道妄壶,這樣一來沒有繪制的地方就是透明的摔握,覆蓋到上一個(gè)layer的時(shí)候,就會(huì)顯示出上一層的圖像丁寄。而FULL_COLOR_LAYER_SAVE_FLAG 則會(huì)完全展示當(dāng)前l(fā)ayer的圖像氨淌,清除掉上一層的重合圖像。

來看看FULL_COLOR_LAYER_SAVE_FLAG 的示例:

canvas.drawColor(Color.RED);

canvas.saveLayer(200,200,700,700,mPaint,Canvas.FULL_COLOR_LAYER_SAVE_FLAG);
mPaint.setColor(Color.GREEN);
canvas.drawRect(300,300,600,600,mPaint);
canvas.restore();

可以看到伊磺,綠色的方塊周圍有白色的一圈盛正,整個(gè)白色加上綠色區(qū)域是這個(gè)layer的區(qū)域,由于這里使用了FULL_COLOR_LAYER_SAVE_FLAG標(biāo)志屑埋,所以這塊區(qū)域的紅色被layer層完全覆蓋(即使是透明)豪筝,由于綠色周圍的顏色是透明的,所以在清除了紅色并覆蓋后摘能,就顯示出了activity的背景顏色续崖,所以顯示了白色。

如果activity背景是黑色徊哑,這一塊自然變?yōu)楹谏?/p>

那么其他代碼不變袜刷,只是將標(biāo)志位替換成HAS_ALPHA_LAYER_SAVE_FLAG會(huì)發(fā)生什么:

可以看到,綠色周圍的白色不見了莺丑,可見著蟹,這就是區(qū)別。使用這個(gè)標(biāo)志位不會(huì)清空上一圖層的內(nèi)容梢莽。

(4) CLIP_TO_LAYER_SAVE_

這個(gè)標(biāo)志比較重要萧豆,官方的建議是,最好不要忽略這個(gè)標(biāo)識(shí)昏名,這個(gè)標(biāo)識(shí)如果不設(shè)置將會(huì)帶來很大的性能問題涮雷。

這個(gè)標(biāo)識(shí)的作用是將canvas裁剪到指定的大小,并且無法回復(fù)轻局『檠迹看下面一個(gè)例子:

canvas.drawColor(Color.RED);
canvas.saveLayer(200,200,700,700,mPaint,Canvas.CLIP_TO_LAYER_SAVE_FLAG);
canvas.drawColor(Color.GREEN);
canvas.restore();
canvas.drawColor(Color.BLACK);

這里看样刷,先將底色繪制為紅色,然后開啟新圖層览爵,再繪制為綠色置鼻,最后將canvas繪制為黑色,為什么最后不是全屏黑色呢蜓竹,這里明明restore了箕母,這是因?yàn)槭褂昧?strong>CLIP_TO_LAYER_SAVE_FLAG標(biāo)志,這樣一來俱济,canvas被裁剪了奥邮,并且無法回復(fù)了饱搏。這樣也就減少了處理的區(qū)域痕囱,增加了性能寻定。

習(xí)題

畫一個(gè)表盤

image

鬧鐘表盤其實(shí)就是在一個(gè)圓周上繪制;

既然是圓周蔚携,最簡單的方式莫過于在鬧鐘的12點(diǎn)鐘處劃線授帕,通過canvas的旋轉(zhuǎn)繪制到對(duì)應(yīng)圓周處,我們一起實(shí)現(xiàn)一下:

整個(gè)圓周是360 度浮梢,每隔 30 度為一個(gè)整時(shí)間刻度,整刻度與刻度之間有四個(gè)短刻度彤路,劃分出5個(gè)小段秕硝,每個(gè)段為6度,有了這些分析洲尊,我們則可以采用如下代碼進(jìn)行繪制:

    /* 繪制刻度 */
    private void drawLines(Canvas canvas) {
        for (int degree = 0; degree <= 360; degree++) {
            if (degree % 30 == 0) {
                //時(shí)針
                mLineBottom = mLineTop + mHourLineHeight;
                mLinePaint.setStrokeWidth(mHourLineWidth);
            } else {
                mLineBottom = mLineTop + mMinuteLineHeight;
                mLinePaint.setStrokeWidth(mMinuteLineWidth);
            }

            if (degree % 6 == 0) {
                canvas.save();
                canvas.rotate(degree, mCenterX, mCenterY);
                canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);
                canvas.restore();
            }
        }
    }

整體代碼如下:

/* 表盤 */
public class Dial extends View {
    private static final int HOUR_LINE_HEIGHT = 35;
    private static final int MINUTE_LINE_HEIGHT = 25;
    private Paint mCirclePaint, mLinePaint;
    private DrawFilter mDrawFilter;
    //圓心(表盤中心)
    private int mCenterX, mCenterY, mCenterRadius;

    // 圓環(huán)線寬度
    private int mCircleLineWidth;
    // 直線刻度線寬度
    private int mHourLineWidth, mMinuteLineWidth;
    // 時(shí)針長度
    private int mHourLineHeight;
    // 分針長度
    private int mMinuteLineHeight;
    // 刻度線的左远豺、上位置
    private int mLineLeft, mLineTop;

    // 刻度線的下邊位置
    private int mLineBottom;
    // 用于控制刻度線位置
    private int mFixLineHeight;

    public Dial(Context context) {
        this(context, null);
    }

    public Dial(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Dial(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG);

        mCircleLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
                getResources().getDisplayMetrics());
        mHourLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,
                getResources().getDisplayMetrics());
        mMinuteLineWidth = mHourLineWidth / 2;

        mFixLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,
                getResources().getDisplayMetrics());

        mHourLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                HOUR_LINE_HEIGHT,
                getResources().getDisplayMetrics());
        mMinuteLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                MINUTE_LINE_HEIGHT,
                getResources().getDisplayMetrics());
        initPaint();
    }

    private void initPaint() {
        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setColor(Color.RED);
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setStrokeWidth(mCircleLineWidth);

        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setColor(Color.RED);
        mLinePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mLinePaint.setStrokeWidth(mHourLineWidth);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.setDrawFilter(mDrawFilter);
        super.onDraw(canvas);
        // 繪制表盤
        drawCircle(canvas);
        // 繪制刻度
        drawLines(canvas);
    }

    /* 繪制刻度 */
    private void drawLines(Canvas canvas) {
        for (int degree = 0; degree <= 360; degree++) {
            if (degree % 30 == 0) {
                //時(shí)針
                mLineBottom = mLineTop + mHourLineHeight;
                mLinePaint.setStrokeWidth(mHourLineWidth);
            } else {
                mLineBottom = mLineTop + mMinuteLineHeight;
                mLinePaint.setStrokeWidth(mMinuteLineWidth);
            }

            if (degree % 6 == 0) {
                canvas.save();
                canvas.rotate(degree, mCenterX, mCenterY);
                canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);
                canvas.restore();
            }
        }
    }

    /* 繪制表盤 */
    private void drawCircle(Canvas canvas) {
        canvas.drawCircle(mCenterX, mCenterY, mCenterRadius, mCirclePaint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w / 2;
        mCenterY = h / 2;
        mCenterRadius = Math.min(mCenterX, mCenterY) - mCircleLineWidth / 2;

        mLineLeft = mCenterX - mMinuteLineWidth / 2;
        mLineTop = mCenterY - mCenterRadius;
    }
}

畫一個(gè)正方形螺旋圖

思路非常的簡單:
1. 繪制一個(gè)和屏幕等寬的正方形;
2. 將畫布以正方形中心為基準(zhǔn)點(diǎn)進(jìn)行縮放坞嘀;
3. 在縮放的過程中繪制原正方形躯护;

注:每次繪制都得使用canvas.save() 和 canvas.restore()進(jìn)行畫布的鎖定和回滾,以免除對(duì)后面繪制的影響丽涩。

先初始化畫筆棺滞,注意此時(shí)畫筆需要設(shè)置成空心:

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(8);
    int l = 10;
    int t = 10;
    int r = 410;
    int b = 410;
    int space = 30;
    Rect squareRect = new Rect(l, t, r, b);
    int squareCount = (r - l) / space;
    float px = l + (r - l) / 2;
    float py = t + (b - t) / 2;
    for (int i = 0; i < squareCount; i++) {
        // 保存畫布
        canvas.save();
        float fraction = (float) i / squareCount;
        // 將畫布以正方形中心進(jìn)行縮放
        canvas.scale(fraction, fraction, px, py);
        canvas.drawRect(squareRect, paint);
        // 畫布回滾
        canvas.restore();
    }
}

一起來看下繪制的效果:


引用:

自定義控件之繪圖篇(四):canvas變換與操作
自定義View之繪圖篇(六):Canvas那些你應(yīng)該知道的變換
Canvas之translate、scale矢渊、rotate继准、skew方法講解!
Android 2D Graphics學(xué)習(xí)(二)矮男、Canvas篇1移必、Canvas基本使用
android canvas layer (圖層)詳解與進(jìn)階

綜合習(xí)題

實(shí)現(xiàn)圖片圓角帶邊框的效果

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Bitmap rawBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.cat);

    Bitmap bitmap = getRoundCornerBitmap(rawBitmap, 50);
    canvas.drawBitmap(bitmap, 0, 0, new Paint());
}

/**
 * @param bitmap 原圖
 * @param pixels 圓角大小
 * @return
 */
public Bitmap getRoundCornerBitmap(Bitmap bitmap, float pixels) {
//獲取bitmap的寬高
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();

    Bitmap cornerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Paint paint = new Paint();
    Canvas canvas = new Canvas(cornerBitmap);
    paint.setAntiAlias(true);

    canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), paint);

//繪制邊框
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(6);
    paint.setColor(Color.GREEN);
    canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

    return cornerBitmap;
}

1、首先通過Bitmap cornerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);生成cornerBitmap 實(shí)例毡鉴,注意了Bitmap 只能通過靜態(tài)方法來獲取它的實(shí)例崔泵,并不能直接 new出來秒赤。

2、繪制圓角矩形canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);憎瘸。

3入篮、為Paint設(shè)置PorterDuffXfermode。參數(shù) PorterDuff.Mode.SRC_IN 取交集含思。

4崎弃、繪制原圖。canvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), paint);

5含潘、繪制邊框圓角饲做。 canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

引用:

自定義View之繪圖篇(六):Canvas那些你應(yīng)該知道的變換

相關(guān)文章:

《Android自定義控件三部曲文章索引》: http://blog.csdn.net/harvic880925/article/details/50995268

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市遏弱,隨后出現(xiàn)的幾起案子盆均,更是在濱河造成了極大的恐慌,老刑警劉巖漱逸,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泪姨,死亡現(xiàn)場離奇詭異,居然都是意外死亡饰抒,警方通過查閱死者的電腦和手機(jī)肮砾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袋坑,“玉大人仗处,你說我怎么就攤上這事≡婀” “怎么了婆誓?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長也颤。 經(jīng)常有香客問我洋幻,道長,這世上最難降的妖魔是什么翅娶? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任文留,我火速辦了婚禮,結(jié)果婚禮上故觅,老公的妹妹穿的比我還像新娘厂庇。我一直安慰自己,他們只是感情好输吏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布权旷。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拄氯。 梳的紋絲不亂的頭發(fā)上躲查,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音译柏,去河邊找鬼镣煮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鄙麦,可吹牛的內(nèi)容都是我干的典唇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼胯府,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼介衔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骂因,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤炎咖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寒波,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乘盼,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年俄烁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绸栅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡页屠,死狀恐怖阴幌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卷中,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布渊抽,位于F島的核電站蟆豫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏懒闷。R本人自食惡果不足惜十减,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愤估。 院中可真熱鬧帮辟,春花似錦、人聲如沸玩焰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昔园。三九已至蔓榄,卻和暖如春并炮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甥郑。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工逃魄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人澜搅。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓伍俘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勉躺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子癌瘾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 一、概述 1. 四線格與基線 小時(shí)候赂蕴,我們?cè)趧傞_始學(xué)習(xí)寫字母時(shí)柳弄,用的本子是四線格的,我們必須把字母按照規(guī)則寫在四線...
    addapp閱讀 7,652評(píng)論 2 17
  • 版權(quán)聲明:本文為博主原創(chuàng)文章概说,未經(jīng)博主允許不得轉(zhuǎn)載 前言 Canvas 本意是畫布的意思,然而將它理解為繪制工具一...
    cc榮宣閱讀 41,558評(píng)論 1 47
  • 導(dǎo)航 Android Paint之顏色過濾器 Paint之shader(圖像渲染) Paint之PathEffec...
    侯蛋蛋_閱讀 4,578評(píng)論 0 5
  • 姓名:張義躍 245期謙虛1組學(xué)員 公司:本一設(shè)計(jì) 【日精進(jìn)打卡第125天】 【知~學(xué)習(xí)】 《六項(xiàng)精進(jìn)》誦讀0遍共...
    小小蛋兒閱讀 130評(píng)論 0 0
  • 文 | 天演星路 本人有貌純爺們碧注,取向正常愛大波美女。 然最近決定和女孩見面 因?yàn)樗掷m(xù)不斷地發(fā)超過300字的信息...
    天演星路閱讀 390評(píng)論 0 0