自定義控件繪圖(Canvas變換、圖層等)篇二

參考

  1. https://blog.csdn.net/harvic880925/article/details/39080931
  2. http://www.reibang.com/p/f2b14c994f0f4
  3. https://blog.csdn.net/lijiuche/article/details/53467844
  4. 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)
image.png

紅色框正歼,并沒有平移辐马;

由于屏幕顯示與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)自原博客):

  1. 調(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上畫好之后泛源,覆蓋到屏幕上顯示出來,過程如下圖:
    注意透明圖層
  2. 再調(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)
沿原點(diǎn)順時(shí)針旋轉(zhuǎn)30

過程:

  1. 第一次畫的過程:


    image.png
  2. 第二次畫的過程:


    image.png
  3. 那么第三次呢,第三次兄朋,是以旋轉(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)
x縮放一半峡扩,即對(duì)應(yīng)的canvas畫布在x方向上密度縮小
canvas.scale(0.5f,1f, 300f,300f)效果

上圖綠圓為什么可顯示完全蹭越,因?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)
x方向斜切45度
x方向斜切-45度
y方向斜切45度
image.png

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)
}
image.png

上面總共調(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)’來管理的拦坠;

圖層layer示例圖

創(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)色圓部分沒有畫上去链蕊;


藍(lán)線有一部分別截掉了

更多請(qǐng)參考:
https://blog.csdn.net/cquwentao/article/details/51423371

save()和saveLayer()區(qū)別

  1. 相同點(diǎn)
  • saveLayer可以實(shí)現(xiàn)save所能實(shí)現(xiàn)的功能
  1. 不同點(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)生不同的影響掌实。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末陪蜻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贱鼻,更是在濱河造成了極大的恐慌宴卖,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邻悬,死亡現(xiàn)場(chǎng)離奇詭異症昏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)父丰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門肝谭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事分苇。” “怎么了屁桑?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵医寿,是天一觀的道長。 經(jīng)常有香客問我蘑斧,道長靖秩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任竖瘾,我火速辦了婚禮沟突,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捕传。我一直安慰自己惠拭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布庸论。 她就那樣靜靜地躺著职辅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪聂示。 梳的紋絲不亂的頭發(fā)上域携,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音鱼喉,去河邊找鬼秀鞭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扛禽,可吹牛的內(nèi)容都是我干的锋边。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼编曼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宠默!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灵巧,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤搀矫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后刻肄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓤球,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年敏弃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卦羡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绿饵,靈堂內(nèi)的尸體忽然破棺而出欠肾,到底是詐尸還是另有隱情,我是刑警寧澤拟赊,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站吸祟,受9級(jí)特大地震影響瑟慈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一呻纹、第九天 我趴在偏房一處隱蔽的房頂上張望专缠。 院中可真熱鬧吩跋,春花似錦梁丘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窗宦。三九已至,卻和暖如春脖苏,著一層夾襖步出監(jiān)牢的瞬間擂啥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工帆阳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哺壶,地道東北人屋吨。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像山宾,于是被迫代替她去往敵國和親至扰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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