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

參考

  1. https://blog.csdn.net/harvic880925/article/details/39080931
  2. http://www.itdecent.cn/p/f2b14c994f0f4
  3. https://blog.csdn.net/lijiuche/article/details/53467844
  4. https://blog.csdn.net/cquwentao/article/details/51423371

Canvas變換等操作,是非常重要的,經(jīng)常忘,之前也有記錄一下,但總是忘,用的時候又過來查一下;

平移操作(translate)

canvas中函數(shù)translate()是用來實現(xiàn)畫布平移的,畫布的原狀是以手機(jī)屏幕左上角為原點,向左是X軸正方向,向下是Y軸正方向;

translate()函數(shù)實現(xiàn)的相當(dāng)于平移坐標(biāo)系,即平移坐標(biāo)系的原點的位置;

 public void translate(float dx, float dy) {}

dx/dy正往右/下,否則,反之;值為偏移的量

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

例子:畫一個矩形,translate后,再畫一個;

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根本不是一個概念!Canvas是一個很虛幻的概念,相當(dāng)于一個透明圖層,每次Canvas畫圖時(即調(diào)用Draw系列函數(shù)),都會產(chǎn)生一個透明圖層,然后在這個圖層上畫圖,畫完之后覆蓋在屏幕上顯示。所以上面的兩個結(jié)果是由下面幾個步驟形成的(權(quán)值轉(zhuǎn)自原博客):

  1. 調(diào)用canvas.drawRect(rect, paint1)時,產(chǎn)生一個Canvas透明圖層,由于當(dāng)時還沒有對坐標(biāo)系平移,所以坐標(biāo)原點是(0,0),再在系統(tǒng)在Canvas上畫好之后,覆蓋到屏幕上顯示出來,過程如下圖:
    注意透明圖層
  2. 再調(diào)用canvas.drawRect(rect, paint2)時,又會重新產(chǎn)生一個全新的Canvas畫布,但此時畫布坐標(biāo)已經(jīng)改變了,即向右和向下分別移動了100像素,所以此時的繪圖方式為:
    第二次繪制

超出屏幕之外的將不會顯示;

總結(jié)

  • 每次調(diào)用canvas.drawXXXX系列函數(shù)來繪圖,都會產(chǎn)生一個全新的Canvas透明畫布;
  • 如果在DrawXXX前,調(diào)用平移、旋轉(zhuǎn)等函數(shù)來對Canvas進(jìn)行了操作,那么這個操作是不可逆的!每次產(chǎn)生的畫布的最新位置都是這些操作后的位置。(Save()、Restore()后面會提)
  • 在Canvas與屏幕合成時,超出屏幕范圍的圖像是不會顯示出來的;

旋轉(zhuǎn)(Rotate)

畫布的旋轉(zhuǎn)是默認(rèn)是圍繞坐標(biāo)原點來旋轉(zhuǎn)的,這里容易產(chǎn)生錯覺,看起來覺得是圖片旋轉(zhuǎn)了,其實旋轉(zhuǎn)的是畫布,以后在此畫布上畫的東西顯示出來的時候全部看起來都是旋轉(zhuǎn)的。

canvas有2個rotate方法:

// 1. 正為順時針旋轉(zhuǎn),負(fù)為指逆時針旋轉(zhuǎn),它的旋轉(zhuǎn)中心點是原點(0,0)
public void rotate(float degrees) 
// 2.構(gòu)造函數(shù)除了度數(shù)以外,還可以指定旋轉(zhuǎ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 ,畫布順時針旋轉(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

過程:

  1. 第一次畫的過程:


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


    image.png
  3. 那么第三次呢,第三次,是以旋轉(zhuǎn)30度后的Canvas為參考進(jìn)行新一次的旋轉(zhuǎn),來形成透明Canvas;

縮放(Scale)

canvas縮放也是2個方法:

// 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縮放一半,即對應(yīng)的canvas畫布在x方向上密度縮小
canvas.scale(0.5f,1f, 300f,300f)效果

上圖綠圓為什么可顯示完全,因為translate又平移回來了;

傾斜(skew)

canvas對應(yīng)的傾斜方法:

// 將畫布在x方向上傾斜相應(yīng)的角度,sx傾斜角度的tan值,sy類似
public void skew(float sx, float sy) 

注意: 這里全是傾斜角度的tan值,比如我們打算在X軸方向上傾斜60度,tan60=根號3,小數(shù)對應(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度錯切
// canvas.skew(-1f,0f)       // x方向-45度錯切
//        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ù)以外,這個操作是不可逆的,一但Canvas畫布被裁剪,就不能再被恢復(fù)!

canvas相關(guān)的clip函數(shù)比較多,這里列幾個:
根據(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"))
// 移動屏幕中間
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,非常重要)

前面所有對畫布的操作都是不可逆的,這會造成很多麻煩,比如,我們?yōu)榱藢崿F(xiàn)一些效果不得不對畫布進(jìn)行操作,但操作完了,畫布狀態(tài)也改變了,這會嚴(yán)重影響到后面的畫圖操作。能對畫布的大小和狀態(tài)(旋轉(zhuǎn)角度、扭曲等)進(jìn)行實時保存和恢復(fù)就最好了,這需要用到
畫布的保存與恢復(fù)相關(guān)的函數(shù)——save()、restore()

方法說明:

  • save(): 每次調(diào)用Save()函數(shù),都會把當(dāng)前的畫布的狀態(tài)進(jìn)行保存,然后放入特定的棧中
  • restore(): 每當(dāng)調(diào)用Restore()函數(shù),就會把棧中最頂層的畫布狀態(tài)取出來,并按照這個狀態(tài)恢復(fù)當(dāng)前的畫布,并在這個畫布上做畫。

例子:多次調(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()操作就會將當(dāng)前的畫布狀態(tài)保存到棧中,下次繪制,就在這個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)做只有一個圖層 Layer,如果需要按圖層來繪制圖形,

可通過 Canvas的SaveLayerXXX,restore后 來創(chuàng)建一些中間層layer,并在layer進(jìn)行繪制;這些Layer是按照‘棧結(jié)構(gòu)’來管理的;

圖層layer示例圖

創(chuàng)建一個新的Layer到“棧”中,可使用saveLayer,saveLayerAlpha, 從棧中推出一個Layer,后續(xù)的操作都發(fā)生在此Layer上,使用 restore/restoreToCount,就會把本次的繪制的圖像“繪制”到上層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)建一個新的layer,后續(xù)的藍(lán)色圓是在這個 layer 中繪制,與 紅色圓 不是同一個layer
    saveLayerAlpha(0f, 0f, 300f,300f,0x88)  // 最后參數(shù)為透明度
    drawCircle(150f, 150f, 85f, paint)
    restore()    // 把本次的繪制的圖像“繪制”到上層Layer上,試著注釋
    drawLine(0f, 0f, 300f, 300f, paint)
}
效果

注釋掉 restore() 效果:
注意藍(lán)線部分,因為沒有restore,藍(lán)色圓部分沒有畫上去;


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

更多請參考:
https://blog.csdn.net/cquwentao/article/details/51423371

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

  1. 相同點
  • saveLayer可以實現(xiàn)save所能實現(xiàn)的功能
  1. 不同點
    • saveLayer生成一個獨立的圖層而save只是保存了一下當(dāng)時畫布的狀態(tài)類似于一個還原點(本來就是)。
    • saveLayer因為多了一個圖層的原因更加耗費內(nèi)存慎用。
    • saveLayer可指定保存相應(yīng)區(qū)域,盡量避免2中所指的情況。
    • 在使用混合模式setXfermode時會產(chǎn)生不同的影響。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容