參考
- https://blog.csdn.net/harvic880925/article/details/39080931
- http://www.reibang.com/p/f2b14c994f0f4
- https://blog.csdn.net/lijiuche/article/details/53467844
- https://blog.csdn.net/cquwentao/article/details/51423371
Canvas變換等操作幻枉,是非常重要的碰声,經(jīng)常忘,之前也有記錄一下熬甫,但總是忘胰挑,用的時(shí)候又過來查一下;
平移操作(translate)
canvas中函數(shù)translate()是用來實(shí)現(xiàn)畫布平移的椿肩,畫布的原狀是以手機(jī)屏幕左上角為原點(diǎn)瞻颂,向左是X軸正方向,向下是Y軸正方向郑象;
translate()
函數(shù)實(shí)現(xiàn)的相當(dāng)于平移坐標(biāo)系贡这,即平移坐標(biāo)系的原點(diǎn)的位置;
public void translate(float dx, float dy) {}
dx/dy正往右/下扣唱,否則藕坯,反之;值為偏移的量
屏幕顯示與Canvas的關(guān)系
例子:畫一個(gè)矩形噪沙,translate后炼彪,再畫一個(gè);
val paint1 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
}
val rect = RectF(10f, 10f, 200f, 168f)
canvas.drawRect(rect, paint1)
// canvas平移
canvas.translate(100f, 100f)
val paint2 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.GREEN
}
canvas.drawRect(rect, paint2)
紅色框正歼,并沒有平移辐马;
由于屏幕顯示與Canvas根本不是一個(gè)概念!Canvas是一個(gè)很虛幻的概念局义,相當(dāng)于一個(gè)透明圖層喜爷,每次Canvas畫圖時(shí)(即調(diào)用Draw系列函數(shù)),都會(huì)產(chǎn)生一個(gè)透明圖層萄唇,然后在這個(gè)圖層上畫圖檩帐,畫完之后覆蓋在屏幕上顯示。所以上面的兩個(gè)結(jié)果是由下面幾個(gè)步驟形成的(權(quán)值轉(zhuǎn)自原博客):
- 調(diào)用
canvas.drawRect(rect, paint1)
時(shí)另萤,產(chǎn)生一個(gè)Canvas透明圖層湃密,由于當(dāng)時(shí)還沒有對(duì)坐標(biāo)系平移,所以坐標(biāo)原點(diǎn)是(0四敞,0),再在系統(tǒng)在Canvas上畫好之后泛源,覆蓋到屏幕上顯示出來,過程如下圖:
- 再調(diào)用
canvas.drawRect(rect, paint2)
時(shí)忿危,又會(huì)重新產(chǎn)生一個(gè)全新的Canvas畫布达箍,但此時(shí)畫布坐標(biāo)已經(jīng)改變了,即向右和向下分別移動(dòng)了100像素铺厨,所以此時(shí)的繪圖方式為:
超出屏幕之外的將不會(huì)顯示缎玫;
總結(jié)
- 每次調(diào)用canvas.drawXXXX系列函數(shù)來繪圖硬纤,都會(huì)產(chǎn)生一個(gè)全新的Canvas透明畫布;
- 如果在DrawXXX前赃磨,調(diào)用平移咬摇、旋轉(zhuǎn)等函數(shù)來對(duì)Canvas進(jìn)行了操作,那么這個(gè)操作是不可逆的煞躬!每次產(chǎn)生的畫布的最新位置都是這些操作后的位置肛鹏。(
Save()、Restore()
后面會(huì)提) - 在Canvas與屏幕合成時(shí)恩沛,超出屏幕范圍的圖像是不會(huì)顯示出來的在扰;
旋轉(zhuǎn)(Rotate)
畫布的旋轉(zhuǎn)是默認(rèn)是圍繞坐標(biāo)原點(diǎn)
來旋轉(zhuǎn)的,這里容易產(chǎn)生錯(cuò)覺雷客,看起來覺得是圖片旋轉(zhuǎn)了芒珠,其實(shí)旋轉(zhuǎn)的是畫布,以后在此畫布上畫的東西顯示出來的時(shí)候全部看起來都是旋轉(zhuǎn)的搅裙。
canvas有2個(gè)rotate方法:
// 1. 正為順時(shí)針旋轉(zhuǎn)皱卓,負(fù)為指逆時(shí)針旋轉(zhuǎn),它的旋轉(zhuǎn)中心點(diǎn)是原點(diǎn)(0部逮,0)
public void rotate(float degrees)
// 2.構(gòu)造函數(shù)除了度數(shù)以外娜汁,還可以指定旋轉(zhuǎn)的中心點(diǎn)坐標(biāo)(px,py)
public final void rotate(float degrees, float px, float py)
val paint1 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
}
val rect = RectF(300f, 10f, 500f, 100f)
canvas.drawRect(rect, paint1)
// canvas rotate ,畫布順時(shí)針旋轉(zhuǎn)45度后
canvas.rotate(30f)
val paint2 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.FILL
color = Color.GREEN
}
canvas.drawRect(rect, paint2)
過程:
-
第一次畫的過程:
-
第二次畫的過程:
- 那么第三次呢,第三次兄朋,是以旋轉(zhuǎn)30度后的Canvas為參考進(jìn)行新一次的旋轉(zhuǎn)掐禁,來形成透明Canvas;
縮放(Scale)
canvas縮放也是2個(gè)方法:
// 1. 水平方向伸縮的比例與垂直縮放比例颅和,大于1.0為放大傅事,否則反之
public void scale(float sx, float sy)
// 2. 先平移(px,py),再縮放,再平移回去(-px,py),后續(xù)的操作不受影響
public final void scale(float sx, float sy, float px, float py)
示例代碼:
val paint1 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
}
canvas.drawCircle(300f, 300f, 148f, paint1)
canvas.scale(0.5f,1f)
//canvas.scale(0.5f,1f, 300f,300f)
val paint2 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.GREEN
}
canvas.drawCircle(300f, 300f, 148f, paint2)
上圖綠圓為什么可顯示完全蹭越,因?yàn)閠ranslate又平移回來了;
傾斜(skew)
canvas對(duì)應(yīng)的傾斜方法:
// 將畫布在x方向上傾斜相應(yīng)的角度教届,sx傾斜角度的tan值响鹃,sy類似
public void skew(float sx, float sy)
注意: 這里全是傾斜角度的tan值,比如我們打算在X軸方向上傾斜60度巍佑,tan60=根號(hào)3茴迁,小數(shù)對(duì)應(yīng)1.732; tan(45) = 1
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.BLACK
}
canvas.drawLine(0f, height / 2.toFloat(), width.toFloat(), height / 2.toFloat(), paint1)
canvas.drawLine(width / 2.toFloat(), 0f, width / 2.toFloat(), height.toFloat(), paint1)
canvas.translate(width / 2.toFloat(), height / 2.toFloat())
paint1.color = Color.RED
canvas.drawRect(0f, 0f, 200f, 100f, paint1)
// x傾斜45寄悯,y不變
//canvas.skew(1f, 0f) // x方向45度錯(cuò)切
// canvas.skew(-1f,0f) // x方向-45度錯(cuò)切
// canvas.skew(0f,1f) // y方向斜切45
canvas.skew(-1f, 1f)
paint1.color = Color.GREEN
canvas.drawRect(0f, 0f, 200f, 100f, paint1)
skew沒有完全理解意思萤衰,理解后,回來再更新
clip裁剪畫布(裁剪-非常重要)
裁剪畫布是利用Clip系列函數(shù)猜旬,通過與Rect脆栋、Path倦卖、Region取交、并椿争、差等集合運(yùn)算來獲得最新的畫布形狀怕膛。除了調(diào)用Save、Restore函數(shù)以外秦踪,這個(gè)操作是不可逆的褐捻,一但Canvas畫布被裁剪,就不能再被恢復(fù)椅邓!
canvas相關(guān)的clip函數(shù)比較多柠逞,這里列幾個(gè):
根據(jù)Rect、Path來取得最新畫布的函數(shù)
boolean clipPath(Path path)
boolean clipPath(Path path, Region.Op op)
boolean clipRect(Rect rect, Region.Op op)
val paint1 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
}
// 填充為灰色
canvas.drawColor(Color.parseColor("#27000000"))
// 移動(dòng)屏幕中間
canvas.translate(width / 4.toFloat(), height / 2.toFloat())
val rect1 = RectF(0f, 0f, 200f, 100f)
val rect2 = RectF(100f, 0f, 300f, 100f)
val path1 = Path().apply {
addRect(rect1, Path.Direction.CCW)
}
val path2 = Path().apply {
addRect(rect2, Path.Direction.CCW)
}
canvas.drawPath(path1, paint1)
paint1.color = Color.GREEN
canvas.drawPath(path2, paint1)
path1.op(path2, Path.Op.INTERSECT) // 交集
canvas.clipPath(path1) // 形成新畫布
canvas.drawColor(Color.YELLOW)
效果如下(去交集后景馁,填充):
畫布的保存與恢復(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ù)就最好了,這需要用到
畫布的保存與恢復(fù)相關(guān)的函數(shù)——save()贡珊、restore()
方法說明:
- 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è)畫布上做畫寒随。
例子:多次調(diào)用save
canvas.apply {
drawColor(Color.RED)
save() // 保存的畫布大小為全屏幕大小
clipRect(Rect(100, 100, 800, 800))
drawColor(Color.GREEN)
save() // 保存畫布大小為Rect(100, 100, 800, 800)
clipRect(Rect(200, 200, 700, 700))
drawColor(Color.BLUE)
save() // 保存畫布大小為Rect(200, 200, 700, 700)
clipRect(Rect(300, 300, 600, 600))
drawColor(Color.BLACK)
save() // 保存畫布大小為Rect(300, 300, 600, 600)
// 在上面clip糠悯,并作畫
clipRect(Rect(400, 400, 500, 500))
drawColor(Color.WHITE)
}
上面總共調(diào)用了四次Save操作。每調(diào)用一次Save()操作就會(huì)將當(dāng)前的畫布狀態(tài)保存到棧中妻往,下次繪制互艾,就在這個(gè)save上進(jìn)行作畫,所以這四次Save()所保存的狀態(tài)的棧的狀態(tài)如下:
例子2:多次調(diào)用restore
canvas.apply {
...
// ===== 多次restore
// 將棧頂?shù)漠嫴紶顟B(tài)取出來讯泣,作為當(dāng)前畫布纫普,并畫成黃色背景 (黑色變黃色)
restore()
restore()
restore() // 到上圖第2次狀態(tài)
drawColor(Color.YELLOW)
}
Canvas Layer層
Canvas在一般情況下,可以看到是一張畫布好渠,所有的繪制都是在此畫布上進(jìn)行昨稼,如果需要疊加节视,如:地圖上的標(biāo)記等,就需要用到Canvas的圖層了假栓,默認(rèn)情況下寻行,可以當(dāng)做只有一個(gè)圖層 Layer,如果需要按圖層來繪制圖形匾荆,
可通過 Canvas的SaveLayerXXX拌蜘,restore后 來創(chuàng)建一些中間層layer,并在layer進(jìn)行繪制牙丽;這些Layer是按照‘棧結(jié)構(gòu)’來管理的拦坠;
創(chuàng)建一個(gè)新的Layer到“棧”中剩岳,可使用saveLayer,saveLayerAlpha, 從棧中推出一個(gè)Layer贞滨,后續(xù)的操作都發(fā)生在此Layer上,使用 restore/restoreToCount拍棕,就會(huì)把本次的繪制的圖像“繪制”到上層Layer上晓铆;類似于入棧
一樣;
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
style = Paint.Style.FILL
}
canvas.apply {
drawColor(Color.WHITE)
drawCircle(100f,100f, 85f, paint)
paint.color = Color.BLUE
// 創(chuàng)建一個(gè)新的layer,后續(xù)的藍(lán)色圓是在這個(gè) layer 中繪制绰播,與 紅色圓 不是同一個(gè)layer
saveLayerAlpha(0f, 0f, 300f,300f,0x88) // 最后參數(shù)為透明度
drawCircle(150f, 150f, 85f, paint)
restore() // 把本次的繪制的圖像“繪制”到上層Layer上骄噪,試著注釋
drawLine(0f, 0f, 300f, 300f, paint)
}
注釋掉 restore() 效果:
注意藍(lán)線部分,因?yàn)闆]有restore蠢箩,藍(lán)色圓部分沒有畫上去链蕊;
更多請(qǐng)參考:
https://blog.csdn.net/cquwentao/article/details/51423371
save()和saveLayer()區(qū)別
- 相同點(diǎn)
- saveLayer可以實(shí)現(xiàn)save所能實(shí)現(xiàn)的功能
- 不同點(diǎn)
- saveLayer生成一個(gè)獨(dú)立的圖層而save只是保存了一下當(dāng)時(shí)畫布的狀態(tài)類似于一個(gè)還原點(diǎn)(本來就是)。
- saveLayer因?yàn)槎嗔艘粋€(gè)圖層的原因更加耗費(fèi)內(nèi)存慎用谬泌。
- saveLayer可指定保存相應(yīng)區(qū)域滔韵,盡量避免2中所指的情況。
- 在使用混合模式setXfermode時(shí)會(huì)產(chǎn)生不同的影響掌实。