有大佬指引着方向,才能順着明亮的道路走的更遠。核心的知識都在扔物線大佬的HenCoder中有寫,並且真心贊!爲了不給大佬和閱讀這篇文章的同窗形成不適,就再也不過多引用大佬文章中的內容了。css
Canvas既然是畫布,天然先從「畫」看起。html
drawARGBandroid
drawColorios
drawRGBgit
drawArcgithub
drawCirclecanvas
drawDoubleRoundRectapi
繪製雙框圓角矩形數組
drawLine緩存
drawOval
drawPoint
drawRect
drawRoundRect
drawPosText
能夠分別指定每一個文字的位置
drawText
drawTextOnPath
根據路徑繪製文字
drawTextRun
用於輔助一些文字結構比較特殊的語言的繪製,例如阿拉伯文字
drawBitmap(bitmap,left,top,paint)
指定bitmap的左邊界和上邊界,而後繪製bitmap
drawBitmap(bitmap,srcRect,dstRect,paint)
用srcRect去切割bitmap指定區域內容,dst指定繪製到canvas的區域邊界。
drawBitmap(bitmap,matrix,paint)
繪製bitmap時,將matrix設置的幾何變換應用上。
drawBitmapMesh
將整個Bitmap分紅若干個網格,再對每個網格進行相應的扭曲處理。可實現水波紋效果。
drawPaint
用畫筆當前的配置,填充畫布。
drawPath
將一個複雜的繪製場景進行拆分,能夠進行部分重繪。
繪製3D圖形利器
更多詳細內容,請移步Hencoder-繪製基礎,香!
將canvas裁剪成規定的形狀,而後再繪製的內容就在這個裁剪區域內了。
clipOutPath
clipOutRect
clipPath
clipRect
translate
rotate
scale
skew
setMatrix
用 Matrix 直接替換 Canvas 當前的變換矩陣,即拋棄 Canvas 當前的變換。
concat(matrix)
用 Canvas 當前的變換矩陣和 Matrix 相乘,即基於 Canvas 當前的變換,疊加上 Matrix 中的變換。
詳細內容,請移步HenCoder-Canvas對繪製的輔助,香!
須要注意,若是繪製過程須要對canvas進行屢次的幾何變換,那麼須要倒敘來寫幾何變換過程。好比須要先平移再旋轉,那麼在寫代碼的時候,就須要先旋轉再平移。
這裏主要是由於屏幕的座標系和canvas座標系是兩個座標系,須要進行必定的的空間想象。
固然,也能夠初始化一個Matrix,合理的使用preXXX和postXXX,對該Matrix進行幾何變換操做,而後將其應用到canvas上。
詳細內容,請移步HenCoder-自定義View繪製順序,這裏就只引用兩張大佬文章中的圖片鎮樓:
當咱們在使用canvas的輔助函數,對canvas進行操做時,這些操做都是不可逆的。好比,在繪製某個內容以前,使用clipRect(0,0,100,100),那麼以後的繪製就只能在[0,0,100,100]這個矩形內,除非在經過手動調用api,讓canvas回到以前的某個狀態。
爲了不發生這種狀況,就能夠在特定的位置進行保存和恢復,在進行變換前,使用save保存canvas當前的狀態,而後進行變換,接着繪製咱們要繪製的內容,最後再經過restore恢復以前保存的狀態。
若是在一次繪製中,屢次調用save方法,那麼會將每次save時,canvas的狀態壓入相似一個棧中,每個狀態都對應一個數字,表明其是棧中的第幾個,能夠經過方法restoreToCount(count),將canvas回退到指定的那個。也能夠調用restore,一個一個的回退canvas的狀態。
須要注意的是,不論是調用restore仍是restoreToCount,都須要在save的數量範圍內,否者系統就會拋出異常。
當咱們使用canvas.drawXXX時,系統會在一個新的透明區域,繪製咱們要繪製的內容,而後迅速與屏幕當前顯示內容進行重疊,這個重疊的過程也會受xfermode或blendmode的影響。以下示例,就演示了這個狀況:
不設置xfermode:
override fun onDraw(canvas: Canvas) {
//先將背景塗紅
canvas.drawColor(Color.RED)
//在中心畫一個綠色的圓
paint.color = Color.GREEN
canvas.drawCircle(width/2f,height/2f,radios/2f,paint)
}
複製代碼
獲得的結果是這樣的:
設置xfermode爲DST:
override fun onDraw(canvas: Canvas) {
//先將背景塗紅
canvas.drawColor(Color.RED)
//在中心畫一個綠色的圓
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST)
paint.color = Color.GREEN
canvas.drawCircle(width/2f,height/2f,radios/2f,paint)
paint.xfermode = null
}
複製代碼
獲得的結果是這樣的:
若是在繪製過程當中只是給paint設置xfermode,而沒有進行操做,如保存Canvas狀態信息等,那麼:
若是設置的mode須要削掉DST(即已經在屏幕上顯示的)部分或所有內容,那麼這個mode不會生效
若是設置的mode爲SRC_OUT、DST_OUT或XOR時,那麼SRC區域顯示爲黑色,再覆蓋在已顯示的內容上
既然只是直接使用paint.setXfermode設置的效果,會跟預期的不一致,那麼應該怎麼樣才能得到預期的效果呢?
canvas提供了saveLayer方法,抽取一個透明區域,執行繪製方法,隨後再一併將繪製的內容,覆蓋在已顯示內容上,使用和不使用saveLayer的大體工做流程:
在調用saveLayer時,能夠傳入一個saveFlags參數,它有以下幾個參數能夠設置:
MATRIX_SAVE_FLAG
只保存圖層的matrix矩陣
CLIP_SAVE_FLAG
只保存大小信息
HAS_ALPHA_LAYER_SAVE_FLAG
代表該圖層有透明度,和下面的標識衝突,都設置時如下面的標誌爲準
FULL_COLOR_LAYER_SAVE_FLAG
徹底保留該圖層顏色(和上一圖層合併時,清空上一圖層的重疊區域,保留該圖層的顏色)
CLIP_TO_LAYER_SAVE_FLAG
建立圖層時,會把canvas(全部圖層)裁剪到參數指定的範圍,若是省略這個flag將致使圖層開銷巨大(實際上圖層沒有裁剪,與原圖層同樣大)
ALL_SAVE_FLAG
保存全部信息
再來看一下canvas.save()源碼:
public int save() {
return nSave(mNativeCanvasWrapper,
//保留矩陣信息(記錄了canvas位移、縮放、旋轉狀況)
//保留clipXXX信息(clipRect、clipPath等)
//其餘信息不保留,如已繪製內容信息等
MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
}
複製代碼
從 Android 3.0(API 級別 11)開始,Android 2D 渲染管道支持硬件加速,也就是說,在 View 的畫布上執行的全部繪製操做都會使用 GPU。啓用硬件加速須要更多資源,所以應用會佔用更多內存。若是最低 版本爲 14 及更高級別,則硬件加速默認處於啓用狀態。
所謂硬件加速,指的是把某些計算工做交給專門的硬件來作,而不是和普通的計算工做同樣交給 CPU 來處理。這樣不只減輕了 CPU 的壓力,並且因爲有了「專人」的處理,這份計算工做的速度也被加快了。這就是「硬件加速」。
而對於 Android 來講,硬件加速有它專屬的意思:在 Android 裏,硬件加速專指把 View 中繪製的計算工做交給 GPU 來處理。進一步地再明確一下,這個「繪製的計算工做」指的就是把繪製方法中的那些 Canvas.drawx3X() 變成實際的像素這件事。
用了 GPU(自身的設計原本就對於不少常見類型內容的計算,例如簡單的圓形、簡單的方形等具備優點),繪製變快了
繪製機制的改變,致使界面內容改變時的刷新效率極大提升
若是想了解更多關於硬件加速的底層原理,能夠查看這篇文章——理解Android硬件加速原理的小白文。
Application
<application android:hardwareAccelerated="true" ...>
複製代碼
Activity
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
複製代碼
Window
//window層級,只能開啓,沒法進行關閉
window.setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
複製代碼
View
//控制當前view圖層是否使用硬件加速,若是應用整體未開啓硬件加速,那麼即使設置type爲LAYER_TYPE_HARDWARE,也沒法開啓硬件加速
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
複製代碼
程序運行中
1.view.isHardwareAccelerated(),判斷當前是否開啓硬件加速
2.canvas.isHardwareAccelerated(),判斷當前是否開啓硬件加速
因爲一般狀況下,canvas是繪製的載體,因此應該經過canvas進行判斷
硬件加速雖好,但開啓硬件加速後,繪製Canvas的某些方法失效或無效,在使用時須要注意這些方法:
Canvas
方法 | 開始支持API版本 |
---|---|
drawBitmapMesh()(顏色數組) | 18 |
drawPicture() | 23 |
drawPosText() | 16 |
drawTextOnPath() | 16 |
drawVertices() | ✗ |
setDrawFilter() | 16 |
clipPath() | 18 |
clipRegion() | 18 |
clipRect(Region.Op.XOR) | 18 |
clipRect(Region.Op.Difference) | 18 |
clipRect(Region.Op.ReverseDifference) | 18 |
clipRect()(經過旋轉/透視) | 18 |
Paint
方法 | 開始支持API版本 |
---|---|
setAntiAlias()(適用於文本)(顏色數組) | 18 |
setAntiAlias()(適用於線條) | 16 |
setFilterBitmap() | 17 |
setLinearText() | ✗ |
setMaskFilter() | ✗ |
setPathEffect()(適用於線條) | 28 |
setShadowLayer()(除文本以外) | 28 |
setStrokeCap()(適用於線條) | 18 |
setStrokeCap()(適用於點) | 19 |
setSubpixelText() | 28 |
Xfermode
方法 | 開始支持API版本 |
---|---|
PorterDuff.Mode.DARKEN(幀緩衝區) | 28 |
PorterDuff.Mode.LIGHTEN(幀緩衝區) | 28 |
PorterDuff.Mode.OVERLAY(幀緩衝區) | 28 |
Shader
方法 | 開始支持API版本 |
---|---|
ComposeShader 內的 ComposeShader | 28 |
ComposeShader 內相同類型的着色器) | 26 |
ComposeShader 上的本地矩陣 | 18 |
Scale
方法 | 開始支持API版本 |
---|---|
drawText() | 18 |
drawPosText() | 28 |
drawTextOnPath() | 28 |
簡單的形狀 | 17 |
複雜的形狀 | 28 |
drawPath() | 28 |
陰影層 | 28 |
簡單形狀,是指使用 Paint 發出的 drawRect()、drawCircle()、drawOval()、drawRoundRect() 和 drawArc()(其中 useCenter=false)命令,該 Paint 不包含 PathEffect,也不包含非默認聯接(經過 setStrokeJoin()/setStrokeMiter())。這些繪製命令的其餘實例都屬於上表中的「複雜」形狀。
前面說到了,在view中進行的操做,若是不被硬件加速支持,那麼就須要適時的關閉硬件加速:
view.setLayerType(LAYER_TYPE_SOFTWARE, null);
複製代碼
可是這個方法的本意是設置view layer的類型,若是類型設置爲LAYER_TYPE_SOFTWARE,那麼會順便關閉硬件加速。
所謂 View Layer,又稱爲離屏緩衝(Off-screen Buffer),它的做用是單獨啓用一塊地方來繪製這個 View ,而不是使用軟件繪製的 Bitmap 或者經過硬件加速的 GPU。這塊「地方」多是一塊單獨的 Bitmap,也多是一塊 OpenGL 的紋理(texture,OpenGL 的紋理能夠簡單理解爲圖像的意思),具體取決於硬件加速是否開啓。
採用什麼來繪製 View 不是關鍵,關鍵在於當設置了 View Layer 的時候,它的繪製會被緩存下來,並且緩存的是最終的繪製結果,而不是像硬件加速那樣只是把 GPU 的操做保存下來再交給 GPU 去計算。經過這樣更進一步的緩存方式,View 的重繪效率進一步提升了:只要繪製的內容沒有變,那麼不管是 CPU 繪製仍是 GPU 繪製,它們都不用從新計算,而只要只用以前緩存的繪製結果就能夠了。
基於這樣的原理,在進行移動、旋轉等(無需調用 invalidate())的屬性動畫的時候開啓 Hardware Layer 將會極大地提高動畫的效率,由於在動畫過程當中 View 自己並無發生改變,只是它的位置或角度改變了,而這種改變是能夠由 GPU 經過簡單計算就完成的,並不須要重繪整個 View。因此在這種動畫的過程當中開啓 Hardware Layer,可讓原本就依靠硬件加速而變流暢了的動畫變得更加流暢。
view.setLayerType(LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(LAYER_TYPE_NONE, null);
}
});
animator.start();
或者
view.animate()
.rotationY(90)
.withLayer(); // withLayer() 能夠自動完成上面這段代碼的複雜操做
複製代碼
不過必定要注意,只有你在對 translationX translationY rotation alpha scale等無需調用 invalidate() 的屬性作動畫的時候,這種方法才適用,由於這種方法自己利用的就是當界面不發生時,緩存未更新所帶來的時間的節省。
減小應用中的視圖數量
系統須要繪製的視圖越多,運行速度越慢。這也適用於軟件渲染。減小視圖是優化界面最簡單的方法之一。
避免過分繪製
請勿在彼此上方繪製過多層。移除全部被上方的其餘不透明視圖徹底遮擋的視圖。若是須要在彼此上方混合繪製多個層,請考慮將它們合併爲一個層。
不要在繪製方法中建立渲染對象
一個常見的錯誤是,每次調用渲染方法時都建立新的 Paint 或 Path。這會強制GC更頻繁地運行,同時還會繞過硬件管道中的緩存和優化。
不要過於頻繁地修改形狀
例如,使用紋理遮罩渲染複雜的形狀、路徑和圓圈。每次建立或修改路徑時,硬件管道都會建立新的遮罩,成本可能比較高。
不要過於頻繁地修改位圖
每次更改位圖的內容時,系統都會在下次繪製時將其做爲 GPU 紋理再次上傳。
謹慎使用 Alpha
當使用 setAlpha()、AlphaAnimation 或 ObjectAnimator 將視圖設置爲半透明時,該視圖會在屏幕外緩衝區渲染,致使所需的填充率翻倍。在超大視圖上應用 Alpha 時,請考慮將視圖的層類型設置爲 LAYER_TYPE_HARDWARE。
Canvas的父類是BaseCanvas,canvas的一系列drawXXX行爲,都是調用的super.drawXXX,在BaseCanvas中,各個drawXXX都會對paint的shader進行一個校驗:
private void throwIfHasHwBitmapInSwMode(Paint p) {
if (isHardwareAccelerated() || p == null) {
return;
}
//若是當前canvas沒有使用硬件加速,那麼會進入這個方法
throwIfHasHwBitmapInSwMode(p.getShader());
}
private void throwIfHasHwBitmapInSwMode(Shader shader) {
if (shader == null) {
return;
}
if (shader instanceof BitmapShader) {
//若是paint設置了shaer且爲BitmapShader,那麼再進一步判斷
throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap);
}
if (shader instanceof ComposeShader) {
throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA);
throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB);
}
}
private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
//當前沒有啓用硬件加速,可是bitmap須要硬件加速
onHwBitmapInSwMode();
}
}
protected void onHwBitmapInSwMode() {
//檢驗是否容許硬件加速bitmap存在於非硬件加速的環境繪製,若是不容許,那麼會直接拋出異常
if (!mAllowHwBitmapsInSwMode) {
throw new IllegalArgumentException(
"Software rendering doesn't support hardware bitmaps");
}
}
//這個屬性默認爲false
private boolean mAllowHwBitmapsInSwMode = false;
//setter方法也被hide標記
/**
* @hide
*/
public void setHwBitmapsInSwModeEnabled(boolean enabled) {
mAllowHwBitmapsInSwMode = enabled;
}
複製代碼
那麼在給Paint設置shader時,若是是BitmapShader必定要注意,Config是否爲Bitmap.Config.HARDWARE類型。