Android必知必會——Canvas

有大佬指引着方向,才能順着明亮的道路走的更遠。核心的知識都在扔物線大佬的HenCoder中有寫,並且真心贊!爲了不給大佬和閱讀這篇文章的同窗形成不適,就再也不過多引用大佬文章中的內容了。css

Canvas既然是畫布,天然先從「畫」看起。html

基本繪製drawXXX系列

顏色

  • drawARGBandroid

  • drawColorios

  • drawRGBgit

基礎圖形

  • drawArcgithub

  • drawCirclecanvas

  • drawDoubleRoundRectapi

    繪製雙框圓角矩形數組

  • drawLine緩存

  • drawOval

  • drawPoint

  • drawRect

  • drawRoundRect

文字

  • drawPosText

    能夠分別指定每一個文字的位置

  • drawText

  • drawTextOnPath

    根據路徑繪製文字

  • drawTextRun

    用於輔助一些文字結構比較特殊的語言的繪製,例如阿拉伯文字

Bitmap

  • 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

  • drawPicture

  • drawRenderNode

    將一個複雜的繪製場景進行拆分,能夠進行部分重繪。

  • drawVertices

    繪製3D圖形利器

更多詳細內容,請移步Hencoder-繪製基礎,香!

對繪製的輔助

裁切

將canvas裁剪成規定的形狀,而後再繪製的內容就在這個裁剪區域內了。

  • clipOutPath

  • clipOutRect

  • clipPath

  • clipRect

幾何變換

  • translate

  • rotate

  • scale

  • skew

  • setMatrix

    用 Matrix 直接替換 Canvas 當前的變換矩陣,即拋棄 Canvas 當前的變換。

  • concat(matrix)

    用 Canvas 當前的變換矩陣和 Matrix 相乘,即基於 Canvas 當前的變換,疊加上 Matrix 中的變換。

使用Camera作三維變換

詳細內容,請移步HenCoder-Canvas對繪製的輔助,香!

繪製過程當中需屢次進行幾何變換時

須要注意,若是繪製過程須要對canvas進行屢次的幾何變換,那麼須要倒敘來寫幾何變換過程。好比須要先平移再旋轉,那麼在寫代碼的時候,就須要先旋轉再平移。

這裏主要是由於屏幕的座標系canvas座標系是兩個座標系,須要進行必定的的空間想象。

固然,也能夠初始化一個Matrix,合理的使用preXXX和postXXX,對該Matrix進行幾何變換操做,而後將其應用到canvas上。

View的繪製順序

詳細內容,請移步HenCoder-自定義View繪製順序,這裏就只引用兩張大佬文章中的圖片鎮樓:

Canvas的回退棧

當咱們在使用canvas的輔助函數,對canvas進行操做時,這些操做都是不可逆的。好比,在繪製某個內容以前,使用clipRect(0,0,100,100),那麼以後的繪製就只能在[0,0,100,100]這個矩形內,除非在經過手動調用api,讓canvas回到以前的某個狀態。

save和restore

爲了不發生這種狀況,就能夠在特定的位置進行保存和恢復,在進行變換前,使用save保存canvas當前的狀態,而後進行變換,接着繪製咱們要繪製的內容,最後再經過restore恢復以前保存的狀態。

若是在一次繪製中,屢次調用save方法,那麼會將每次save時,canvas的狀態壓入相似一個棧中,每個狀態都對應一個數字,表明其是棧中的第幾個,能夠經過方法restoreToCount(count),將canvas回退到指定的那個。也能夠調用restore,一個一個的回退canvas的狀態。

須要注意的是,不論是調用restore仍是restoreToCount,都須要在save的數量範圍內,否者系統就會拋出異常。

Canvas.drawXXX工做過程

當咱們使用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區域顯示爲黑色,再覆蓋在已顯示的內容上

使用layer綜合繪製操做

既然只是直接使用paint.setXfermode設置的效果,會跟預期的不一致,那麼應該怎麼樣才能得到預期的效果呢?

canvas提供了saveLayer方法,抽取一個透明區域,執行繪製方法,隨後再一併將繪製的內容,覆蓋在已顯示內容上,使用和不使用saveLayer的大體工做流程:

  • 不使用layer

  • 使用layer

在調用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())。這些繪製命令的其餘實例都屬於上表中的「複雜」形狀。

離屏緩衝 —— 引用自Hencoder

前面說到了,在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。

BitmapShader可能引發崩潰

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類型。

相關文章
相關標籤/搜索