上一篇我們講了一些基本的圖形繪制
http://www.reibang.com/p/b335377d2dd9
提到了圖形繪制就像我們平時(shí)畫圖一樣,需要兩個(gè)工具鸭丛,紙和筆。Paint就是相當(dāng)于筆,而Canvas就是紙邀摆,這里叫畫布羔挡。比如圓形洁奈,矩形,文字等相關(guān)的都是在Canvas里生成绞灼。
細(xì)心的朋友可能會(huì)發(fā)現(xiàn)上一期亂入了這個(gè)東西canvas.translate(0,100);
這是啥呢利术?其實(shí)這就是對(duì)畫布的操作(平移)。我們知道低矮,android手機(jī)原點(diǎn)是在手機(jī)的左上角印叁,當(dāng)我們想要在手機(jī)屏幕中心繪制圖形的時(shí)候。需要計(jì)算坐標(biāo)比較麻煩军掂。這個(gè)時(shí)候我們可以把整個(gè)坐標(biāo)系(Canvas)平移到屏幕中心轮蜕。這樣比如canvas.drawPoint(0,0,mPaint);
點(diǎn)就出現(xiàn)在屏幕中心了
事實(shí)上,我們昨天繪制圖形也是使用Canvas的drawXxx()方法繪制出來的蝗锥。
Canvas所提供的各種方法根據(jù)功能來看大致可以分為幾類:
第一是以drawXXX為主的繪制方法跃洛;
第二是以clipXXX為主的裁剪方法;
第三是以scale终议、skew汇竭、translate和rotate組成的Canvas變換方法;
第四類則是以saveXXX和restoreXXX構(gòu)成的畫布鎖定和還原穴张;
還有一些其他的就不歸類了细燎。
如果用到的不能硬件加速的方法,請(qǐng)務(wù)必關(guān)閉硬件加速陆馁,否則可能會(huì)產(chǎn)生不正確的效果找颓。(待日后弄清楚再回來補(bǔ)充)
這里要說明的是,除了做了第四類操作叮贩,否則畫布的操作是不可逆的击狮。而且也不能對(duì)已經(jīng)畫上去的圖形造成影響
平移(translate)
public void translate(float dx, float dy)
參數(shù):
float dx:X軸方向上的移動(dòng)距離
float dx:Y軸方向上的移動(dòng)距離
注意:位移是基于當(dāng)前位置移動(dòng)佛析,而不是每次基于屏幕左上角的(0,0)點(diǎn)移動(dòng)
在牢記這一點(diǎn)以后,我們看一段簡單的代碼:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);//將畫布移動(dòng)到屏幕中心
//將要繪制的矩形
Rect rect = new Rect(0,0,200,100);
//坐標(biāo)系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
//變化前
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
canvas.drawRect(rect,mPaint);
canvas.translate(100,100);
//變化后
mPaint.setColor(Color.GREEN);
canvas.drawRect(rect,mPaint);
canvas.drawLines(pts,mPaint);
}
可以看到彪蓬,整個(gè)畫布都向右平移了100px然后先下平移了100px
旋轉(zhuǎn)(rotate)
public void rotate(float degrees)
public final void rotate(float degrees, float px, float py)
參數(shù):
float degrees :旋轉(zhuǎn)角度
float px:旋轉(zhuǎn)中心橫坐標(biāo)
float py:旋轉(zhuǎn)中心縱坐標(biāo)
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//將要繪制的矩形
Rect rect = new Rect(0,0,200,100);
//坐標(biāo)系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
//變化前
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
canvas.drawRect(rect,mPaint);
canvas.rotate(30);//重點(diǎn):順時(shí)針旋轉(zhuǎn)畫布30度
//變化后
mPaint.setColor(Color.GREEN);
canvas.drawRect(rect,mPaint);
canvas.drawLines(pts,mPaint);
}
默認(rèn)旋轉(zhuǎn)中心是原點(diǎn)寸莫,結(jié)果如圖所示:
如果指定旋轉(zhuǎn)中心
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//將要繪制的矩形
Rect rect = new Rect(0,0,200,100);
//坐標(biāo)系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
//變化前
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
canvas.drawRect(rect,mPaint);
canvas.rotate(30,200档冬,0);//重點(diǎn):以(200膘茎,0)為中心順時(shí)針旋轉(zhuǎn)畫布30度
//變化后
mPaint.setColor(Color.GREEN);
canvas.drawRect(rect,mPaint);
canvas.drawLines(pts,mPaint);
}
結(jié)果如下:
縮放(scale)
public void scale(float sx, float sy)
public final void scale(float sx, float sy, float px, float py)
參數(shù):
float sx:水平方向伸縮的比例,假設(shè)原坐標(biāo)軸的比例為n,不變時(shí)為1酷誓,在變更的X軸密度為n乘sx;所以披坏,sx為小數(shù)為縮小,sx為整數(shù)為放大*
float sy:垂直方向伸縮的比例盐数,同樣棒拂,小數(shù)為縮小,整數(shù)為放大
float px:旋轉(zhuǎn)中心橫坐標(biāo)
float py:旋轉(zhuǎn)中心縱坐標(biāo)*
注:當(dāng)縮放比例為負(fù)數(shù)時(shí)玫氢,則要根據(jù)中心軸進(jìn)行翻轉(zhuǎn)
廢話不多說帚屉,反手就是一段代碼,看看縮放到底是怎么回事:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//將要繪制的矩形
Rect rect = new Rect(0,0,200,100);
//坐標(biāo)系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
//變化前
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
canvas.drawRect(rect,mPaint);
canvas.scale(0.5f,0.5f);//避免重復(fù)代碼漾峡,在旋轉(zhuǎn)這一小節(jié)攻旦,下面只替換這一句代碼
//變化后
mPaint.setColor(Color.GREEN);
canvas.drawRect(rect,mPaint);
canvas.drawLines(pts,mPaint);
}
默認(rèn)縮放中心是原點(diǎn),結(jié)果顯而易見:
我們?cè)侔芽s放這一句換成
canvas.scale(0.5f,0.5f,200,100);
指定縮放中心(200生逸,100)會(huì)怎么樣呢牢屋?我們先點(diǎn)開android源碼看看public final void scale(float sx, float sy, float px, float py)
這個(gè)方法里面到底是個(gè)什么鬼
public final void scale(float sx, float sy, float px, float py) {
translate(px, py);
scale(sx, sy);
translate(-px, -py);
}
代碼很清晰,先平移到縮放中心點(diǎn)牺陶,縮放完成后再平移回去伟阔。但這里需要注意一點(diǎn):縮放以后,坐標(biāo)軸比例發(fā)生變化掰伸。所以不能平移回到原點(diǎn)皱炉。結(jié)果如下所示
我們可以看到,畫布以(200狮鸭,100)為中心在橫縱軸方向分別縮小了一倍
上面提到合搅,當(dāng)縮放比例為負(fù)數(shù)時(shí)需要翻轉(zhuǎn),翻轉(zhuǎn)是什么意思呢歧蕉?我們把旋轉(zhuǎn)的這一句代碼換成:
canvas.scale(-0.5f,0.5f,200,100);
看看結(jié)果如何
沒錯(cuò)灾部,當(dāng)我們把sx系數(shù)變成負(fù)數(shù)以后,變化后的圖形與原圖形在x軸方向上基于原點(diǎn)相互對(duì)稱惯退。
最后我們總結(jié)一下:
public final void scale(float sx, float sy, float px, float py)
這個(gè)方法的變換是:先以點(diǎn)(px,py)為中心縮放對(duì)應(yīng)的比例系數(shù)赌髓,如果比例系數(shù)是負(fù)數(shù),然后在對(duì)應(yīng)的方向上翻轉(zhuǎn)(對(duì)稱)。
扭曲(skew)
public void skew(float sx, float sy)
參數(shù):
float sx:將畫布在x方向上傾斜相應(yīng)的角度锁蠕,sx為傾斜角度的tan值夷野,
float sy:將畫布在y軸方向上傾斜相應(yīng)的角度冲秽,sy為傾斜角度的tan值.
這個(gè)方法的效果有點(diǎn)類似于斜拉痛黎,想了很久也沒有組織好語言來描述它的效果溪掀。給個(gè)公式好了:
X = x + sx * y
Y = sy * x + y
還是直接看代碼吧:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//將要繪制的矩形
Rect rect = new Rect(100,100,300,300);
//坐標(biāo)系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
//變化前
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
canvas.drawRect(rect,mPaint);
//sekw
canvas.skew((float) Math.tan(Math.PI/6),0);//sx參數(shù)為tan30度
//變化后
mPaint.setStrokeWidth(10);//便于看清喻喳,增加畫筆寬度
mPaint.setColor(Color.RED);
canvas.drawRect(rect,mPaint);
canvas.drawLines(pts,mPaint);
}
當(dāng)sx的值為Math.tan(Math.PI/6),sy為0(即Math.tan(Math.PI/2))時(shí)着绊。因?yàn)閠an90度為0钞瀑。所以y軸方向上的坐標(biāo)沒有改變熄守。而X軸方向上的坐標(biāo)都扭曲了30度铸豁。
裁剪畫布(clipXxx)
裁剪畫布是利用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)
我們看到Region這個(gè)東西多次出現(xiàn)厂捞,Region的意思是“區(qū)域”,在Android里呢它同樣表示的是一塊封閉的區(qū)域队丝,Region中的方法都非常的簡單靡馁,我們重點(diǎn)來瞧瞧Region.Op,Op是Region的一個(gè)枚舉類机久,里面呢有六個(gè)枚舉常量:
所以Region.Op其實(shí)就是個(gè)組合模式臭墨。這個(gè)暫時(shí)不深入,后面再講”旄牵現(xiàn)在只要知道Region在這里的用法和Rect是一樣的
Tip:Region表示的是一個(gè)區(qū)域胧弛,而Rect表示的是一個(gè)矩形,這是最根本的區(qū)別之一侠畔。其次结缚,Region有個(gè)很特別的地方是它不受Canvas的變換影響。
我們先把畫布剪切成一個(gè)矩形試試:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//將要繪制的矩形
Rect rect = new Rect(0,0,200,100);
canvas.drawColor(Color.RED);
canvas.clipRect(rect);
canvas.drawColor(Color.GREEN);
}
畫布剪切后软棺,就只剩下綠色這一小塊了红竭。以后任何操作都只能在這一區(qū)域中生效。
保存(save)和回滾(restore)
前面提到除了使用save()和restore()方法,畫布的操作是不可逆的茵宪,言下之意就是使用了這兩個(gè)方法最冰,可以讓畫布的操作可逆(已經(jīng)繪制上去的圖形不會(huì)發(fā)生改變)。
public int save()
public void restore()
public int getSaveCount()
public void restoreToCount(int saveCount)
Save():每次調(diào)用Save()函數(shù)眉厨,都會(huì)把當(dāng)前的畫布的狀態(tài)進(jìn)行保存锌奴,然后放入特定的棧中,該方法會(huì)返回一個(gè)int型id憾股,代表了在畫布棧中的位置鹿蜀,便于restoreToCount()方法回復(fù)的指定畫布狀態(tài);
restore():每當(dāng)調(diào)用Restore()函數(shù)服球,就會(huì)把棧中最頂層的畫布狀態(tài)取出來茴恰,并按照這個(gè)狀態(tài)恢復(fù)當(dāng)前的畫布,并在這個(gè)畫布上做畫斩熊。
getSaveCount():返回棧中儲(chǔ)存的畫布個(gè)數(shù)
restoreToCount(int saveCount):彈出指定位置及其以上所有的狀態(tài)往枣,并按照指定位置的狀態(tài)進(jìn)行恢復(fù)
saveLayerXxx():新建一個(gè)圖層,并放入特定的棧中(性能不佳)
畫布棧這個(gè)概念我們一圖以蔽之:
先來看一下最簡單的save()和restore():
canvas.save()
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//坐標(biāo)系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
RectF rect = new RectF(50, 50, 300, 200);
mPaint.setColor(Color.RED);
canvas.drawRect(rect, mPaint);
// 保存畫布----(接下來的代碼只替換這一句)
canvas.save();
// 向下平移畫布
canvas.translate(0,250);
mPaint.setColor(Color.BLUE);
canvas.drawRect(rect, mPaint);
// 還原畫布
canvas.restore();
mPaint.setColor(Color.GREEN);
//繪制橢圓
canvas.drawOval(rect, mPaint);
}
我們先繪制了一個(gè)紅色矩形粉渠,在save()后添加向下平移分冈,然后繪制藍(lán)色矩形,繪制后釋放了畫布霸株,最后繪制綠色橢圓雕沉。你會(huì)發(fā)現(xiàn)平移的只有藍(lán)色的矩形,之后繪制的綠色橢圓沒有平移去件。這就說明藍(lán)色的矩形被我們放在了一個(gè)新的層上坡椒。一旦你回滾(restore)了畫布,就又回到原來的畫布狀態(tài)尤溜。所以我們的綠色橢圓沒有發(fā)生任何變化倔叼。
canvas.saveLayer()
接下來我們把
canvas.save();
替換成
canvas.saveLayer(0, 0, 400, 400, null, Canvas.ALL_SAVE_FLAG);
前面4個(gè)參數(shù)是控制保存范圍的,因?yàn)閟aveLayerXXX方法會(huì)將操作保存到一個(gè)新的Bitmap中宫莱,而這個(gè)Bitmap的大小取決于我們傳入的參數(shù)大小丈攒,Bitmap是個(gè)相當(dāng)危險(xiǎn)的對(duì)象,很多朋友在操作Bitmap時(shí)不太理解其原理經(jīng)常導(dǎo)致OOM授霸,在saveLayer時(shí)我們會(huì)依據(jù)傳入的參數(shù)獲取一個(gè)相同大小的Bitmap肥印,雖然這個(gè)Bitmap是空的但是其會(huì)占用一定的內(nèi)存空間,我們希望盡可能小地保存該保存的區(qū)域绝葡,而saveLayer則提供了這樣的功能深碱。
替換后得到的結(jié)果如下:
由于我們只保存了(0,0藏畅,400敷硅,400)這一個(gè)區(qū)域畫布功咒,所以超出的部分自然無法繪制。
canvas.saveLayerAlpha()
繼續(xù)把
canvas.save();
替換成
canvas.saveLayerAlpha(0, 0, 400, 400, 50, Canvas.ALL_SAVE_FLAG);
很清楚的可以看到绞蹦,該方法可以在我們保存畫布時(shí)設(shè)置畫布的透明度:
flag
我們?cè)谟胹aveLayerAlpha和saveLayer方法時(shí)都用到了一個(gè)flag值Canvas.ALL_SAVE_FLAG力奋,這是什么鬼?看一下官網(wǎng)怎么說:
整理一下
數(shù)據(jù)類型 | 名稱 | 簡介 |
---|---|---|
int | ALL_SAVE_FLAG | 默認(rèn),保存全部狀態(tài) |
int | CLIP_SAVE_FLAG | 保存剪輯區(qū) |
int | CLIP_TO_LAYER_SAVE_FLAG | 剪裁區(qū)作為圖層保存 |
int | FULL_COLOR_LAYER_SAVE_FLAG | 保存圖層的全部色彩通道 |
int | HAS_ALPHA_LAYER_SAVE_FLAG | 保存圖層的alpha(不透明度)通道 |
int | MATRIX_SAVE_FLAG | 保存Matrix信息(translate, rotate, scale, skew) |
這六個(gè)常量值分別標(biāo)識(shí)了我們保存什么東西幽七。
六個(gè)標(biāo)識(shí)位除了
CLIP_SAVE_FLAG MATRIX_SAVE_FLAG ALL_SAVE_FLAG
是save和saveLayerXXX方法都通用外景殷,其余三個(gè)只能使saveLayerXXX方法有效。也就是說
CLIP_SAVE_FLAG MATRIX_SAVE_FLAG ALL_SAVE_FLAG
可以被save(int flag)和saveLayerXXX(…澡屡,int flag)使用猿挚,其余的僅僅適用于saveLayerXXX方法。平時(shí)使用大家可以直接ALL_SAVE_FLAG就行驶鹉,感興趣的可以了解下別的標(biāo)志位的作用绩蜻。
在此簡單舉個(gè)例子:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
RectF rect = new RectF(0,0,200,100);
mPaint.setColor(Color.RED);
canvas.drawRect(rect,mPaint);//先繪制一個(gè)紅色矩形
canvas.save(Canvas.CLIP_SAVE_FLAG);//只保存clip操作
canvas.translate(100,100);//平移(MATRIX操作)
mPaint.setColor(Color.GREEN);
canvas.drawRect(rect,mPaint);//繪制一個(gè)綠色矩形
canvas.restore();//回滾
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(10);
canvas.drawOval(rect,mPaint);//繪制一個(gè)黑色橢圓
}
這里我們先繪制了一個(gè)紅色矩形,在save()的時(shí)候傳入了一個(gè)參數(shù):Canvas.CLIP_SAVE_FLAG
室埋。即只保存剪輯區(qū)(clip操作)办绝。save后我們將畫布平移,繪制了一個(gè)綠色矩形姚淆,然后回滾孕蝉,最后繪制了一個(gè)黑色橢圓
看看結(jié)果如何:
可以看到,雖然我們回滾后才繪制的黑色橢圓腌逢,但是橢圓依然"巋然不動(dòng)"昔驱。很明顯,這是因?yàn)槲覀冎槐4媪薱lip操作(剪輯區(qū))上忍。所以MATRIX操作的部分不會(huì)回滾。
canvas.restoreToCount()
這東西事實(shí)上沒什么好講的纳本,和restore()的功能一樣窍蓝,區(qū)別是彈出指定位置及其以上所有的狀態(tài),并按照指定位置的狀態(tài)進(jìn)行恢復(fù)繁成。
canvas.getSaveCount()
該方法返回畫布棧中保存畫布的次數(shù)吓笙,但是有時(shí)系統(tǒng)會(huì)調(diào)用,所以即使沒有調(diào)用save()巾腕。getSaveCount()返回的值也不一定是1(最小值為1面睛,即使彈出了所有的狀態(tài),返回值依舊為1尊搬,代表默認(rèn)狀態(tài)叁鉴。)。故想要回滾到指定畫布狀態(tài)佛寿,最好在調(diào)用save()方法時(shí)記錄saveID幌墓,然后調(diào)用restoreToCount()方法回滾到指定畫布狀態(tài)。
小練習(xí)走一波,利用save()和restore()繪制出如下圖形:
代碼:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
for(int i = 0;i < 36;i++){
canvas.save();
canvas.rotate(i*10);
canvas.drawLine(0,0,300,0,mPaint);
canvas.restore();
}
}
concat();
public void concat(Matrix matrix)
參數(shù):matrix
使整個(gè)畫布做matrix變形操作
總結(jié)
其中漏講了一些內(nèi)容,后面擇機(jī)補(bǔ)上