Canvas詳解(android自定義view,onDraw()繪製各種圖形)

Canvas

Canvas:The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

根據doc的介紹我們可以看到canvas是畫集合圖形的一個重要類,它持有draw動作(就是畫圖).使用draw的時候我們需要4個基本條件:
1. 一個用來保存像素的Bitmap
2. Canvas用來調用draw方法
3. 我們要畫的材料(Rect,Path,Text,Bitmap,Color…)
4. Paint,用來設置內容的色值,樣式,透明度…
這是根據文檔中的介紹而翻譯的,但是我們可以仔細瞅瞅,平時我們的自定義view也就是這個條件,例子我就列舉了,隨便找個自定義view就可以發現都是這麼做的.

函數和常量的介紹

常量
  • ALL_SAVE_FLAG
    在調用saveLayer()時調用,用來表示保存全部數據,例如色值和透明度等等…,在調用restore()可以恢復全部數據.
  • MATRIX_SAVE_FLAG
    只保存圖層的matrix矩陣(開啓硬件加速),在O版本中,canvas自動含有該功能
  • CLIP_SAVE_FLAG
    使用方法同上:只是保存和恢復的是當前clip的內容(開啓硬件加速),在O版本中,canvas自動含有該功能
  • CLIP_TO_LAYER_SAVE_FLAG
    創建圖層時,會把canvas(所有圖層)裁剪到參數指定的範圍,如果省略這個flag將導致圖層開銷巨大(實際上圖層沒有裁剪,與原圖層一樣大)
  • FULL_COLOR_LAYER_SAVE_FLAG
    完全保留該圖層顏色(和上一圖層合併時,清空上一圖層的重疊區域,保留該圖層的顏色)
  • HAS_ALPHA_LAYER_SAVE_FLAG
  • 表明該圖層有透明度,和下面的標識衝突,都設置時以下面的標誌爲準

MATRIX_SAVE_FLAG,CLIP_SAVE_FLAG在使用save(flag)時被調用
ALL_SAVE_FLAG,CLIP_TO_LAYER_SAVE_FLAG,FULL_COLOR_LAYER_SAVE_FLAG,HAS_ALPHA_LAYER_SAVE_FLAG在使用saveLayer()或者saveLayerAlpha時調用.

我們可以使用如下code來進行測試:

...
canvas?.save(Canvas.flag);
...
canvas?.draw()...
canvas?.restore();
...
canvas?.draw()
構造函數
  • Canvas()
  • Canvas(Bitmap bitmap) bitmap要繪製的位圖
函數
  • setBitmap(Bitmap bitmap)bitmap是可變的
    使用該函數後,除了當前矩陣和剪輯堆棧(例如save(Matrix,Clip))之外,所有畫布狀態如層、過濾器和保存/恢復堆棧(saveLayer()…)都將重置

Canvas(Bitmap bitmap) == Canvas() + setBitmap(Bitmap bitmap)

裁剪:clipPath();clipRect()
在介紹裁剪前,我們先介紹一下region這個類.

Region

用於指定的幾何區域剪裁區域圖。

Region.Op

組合兩個區域時可以執行的邏輯操作,既在多個裁剪區域發生重疊時,可以使用該類來實現我們需要的功能,如果是單個圖形裁剪時,各個值對應顯示的裁剪形狀相同,同時,clip()都有默認值

我們可以舉例:有A和B兩個幾何圖形,對他們進行裁剪,同時他們相交,B使用Region.Op指定

  • DIFFERENCE(0),從第一個區域減去op區域(是A形狀中不同於B的部分顯示出來)
  • INTERSECT(1),顯示相交的兩個地區
  • UNION(2),顯示這兩個地區結合起來全部顯示
  • XOR(3),顯示全集部分減去相交部分
  • REVERSE_DIFFERENCE(4),是B形狀中不同於A的部分顯示出來(既顯示Op不相交的部分)
  • REPLACE(5);用op區域替換dst區域(既只顯示op區域)

注:該部分的含義我們可以從SkRegion.h中的註釋瞭解,如果想要理解具體內容的話,可以自己研究SkRegion.h,SkRegion.cpp

    /**
     *  The logical operations that can be performed when combining two regions.
     */
    enum Op {
        kDifference_Op, //!< subtract the op region from the first region
        kIntersect_Op,  //!< intersect the two regions
        kUnion_Op,      //!< union (inclusive-or) the two regions
        kXOR_Op,        //!< exclusive-or the two regions
        /** subtract the first region from the op region */
        kReverseDifference_Op,
        kReplace_Op,    //!< replace the dst region with the op region

        kLastOp = kReplace_Op
    };

注意:op指的是我們裁剪時,裁剪完後顯示的圖形區域.如果clip()中沒有使用Region.Op時,我們可以去Canvas中看看,一般都有默認Region.Op被調用
示例:

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    canvas?.save()
    canvas?.translate(5f, 5f)
    paint.setColor(Color.parseColor("#00ff00"))
    canvas?.drawRect(RectF(0f, 0f, 300f, 300f), paint)
    canvas?.drawCircle(300f, 300f, 150f, paint)

    paint.setColor(Color.parseColor("#ff0000"))

    // 對一個矩形進行裁剪
    canvas?.clipRect(RectF(0f, 0f, 300f, 300f))

    val mPath = Path()
    mPath.addCircle(300f, 300f, 150f, Path.Direction.CCW)
    // 對指定的path進行裁剪
    canvas?.clipPath(mPath, Region.Op.INTERSECT)

    // 顯示裁剪內容
    canvas?.drawRect(RectF(0f, 0f, Integer.MAX_VALUE.toFloat(), Integer.MAX_VALUE.toFloat()), paint)
    canvas?.restore()
}

我們可以看到Rect裁剪和Path裁剪完後顯示出來的內容
注意:使用完clip()後,必須使用canvas.draw(),纔可以把我們要裁剪的內容畫出來

效果圖:
使用Region.Op剪切的效果圖

  • clipPath()對路徑進行裁剪
    • clipPath(Path path, Region.Op op) // 指定裁剪路徑和顯示的內容
    • clipPath(Path path) // 指定裁剪路徑
      使用clipPath可以根據path將圖片裁剪成各種形狀:
      例如:圓形圖片
override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    canvas?.save()
    canvas?.translate(5f, 5f)
    var width: Int = bitmap.width
    var hegith: Int = bitmap.height
    var radius: Int
    when {
        width < hegith -> radius = width / 2
        width > hegith -> radius = hegith / 2
        else -> radius = width / 2
    }
    var path = Path()
    // path.addCircle(radius.toFloat(), radius.toFloat(), radius.toFloat(), Path.Direction.CW)
    path.addRoundRect(RectF(0f, 0f, width.toFloat(), hegith.toFloat()), 30f, 30f, Path.Direction.CW)
    canvas?.clipPath(path, Region.Op.INTERSECT)
    canvas?.drawBitmap(bitmap, 0f, 0f, paint)
    canvas?.restore()
}

圓形頭像
矩形弧度頭像
我記得前邊寫過這種圖形,其中一個是BitmapShader,如果有興趣的話可以去看看這個類,這個類可以實現圖片的各種形狀.
canvas.clipPath()也可以根據Path繪製各種形狀的圖片,例如五角星,三角形,正方形…….

  • clipRect()使用矩形進行裁剪
    • clipRect(Rect rect) // 按指定矩形裁剪
    • clipRect(Rect rect,Region.Op op) 按指定矩形裁剪,並指定顯示內容
    • ……其他clipRect()和上邊的這兩個基本沒有什麼區別.只是Rect顯示方式不同而已
paint.setShader(BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP))
canvas?.drawRect(0f, 0f, 600f, 300f, paint)
paint.setShader(null)
paint.color = Color.parseColor("#4400ff00")
canvas?.clipRect(50, 20, 550, 280)
canvas?.drawRect(0f, 0f, Float.MAX_VALUE, Float.MAX_VALUE, paint)
canvas?.restore()

這裏寫圖片描述

paint.setShader(BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP))
paint.color = Color.RED
canvas?.clipRect(50, 20, 400, 200)
canvas?.drawRect(0f, 0f, 600f, 300f, paint)
paint.setShader(null)

這裏寫圖片描述

  • clipRegion()對區域進行裁剪

這個就不舉例子了,最上面的那個就是,如果有興趣的話,可以自己找些例子看看,或者自己寫寫

  • clipOut() 對指定形狀進行剪切,顯示差值部分
    • clipOutRect()
    • clipOutPath()

這個也不舉例了,把上面的代碼複製一份,將clip()修改爲clipOut()就可以了,注意這個clipOut()需要運行在API26版本上.



動畫:移動(translate),旋轉(rotate),縮放(scale),錯切(skew);矩陣(Matrix)變換(translate,rotate,scale,skew)

canvas的幾何變換實際上還是對畫布的矩陣進行改變來實現目地的

例:

/**
 * Preconcat the current matrix with the specified translation
 *
 * @param dx The distance to translate in X
 * @param dy The distance to translate in Y
 */
public void translate(float dx, float dy) {
    if (dx == 0.0f && dy == 0.0f) return;
    nTranslate(mNativeCanvasWrapper, dx, dy);
}

這是Canvas源碼中的translate()函數,通過註釋(將指定矩陣前乘當前矩陣)可以發現實際上是通過Matrix來實現變換的.矩陣的變換我們下邊再介紹

  • setMatrix(@Nullable Matrix matrix)
  • concat(@Nullable Matrix matrix)
override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    canvas?.translate(5f, 5f)
    canvas?.concat(mMatrix);
    // canvas?.matrix=mMatrix
    canvas?.drawBitmap(bitmap, 0f, 0f, paint)
}
public fun click() {
    mMatrix.setTranslate(10f, 20f)
    // mMatrix.postTranslate(10f, 20f)
    // mMatrix.preTranslate(10f, 20f)
    invalidate()
}
  1. 使用Matrix.setTranslate後,一直點擊按鈕,發現最終的效果如上圖所示.
    這裏寫圖片描述
  2. 使用Matrix.postTranslate或者Matrix.preTranslate後發現最終的效果如下圖:通過不斷移動,最終消失在屏幕上
    這裏寫圖片描述

我們先看一下canvas.matrix和canvas.concat()他們的效果相同,但是有什麼區別呢?來我們看看源碼:

/**
 * Preconcat the current matrix with the specified matrix. If the specified
 * matrix is null, this method does nothing.
 *
 * @param matrix The matrix to preconcatenate with the current matrix
 */
public void concat(@Nullable Matrix matrix) {
    if (matrix != null) nConcat(mNativeCanvasWrapper, matrix.native_instance);
}
/**
 * Completely replace the current matrix with the specified matrix. If the
 * matrix parameter is null, then the current matrix is reset to identity.
 *
 * <strong>Note:</strong> it is recommended to use {@link #concat(Matrix)},
 * {@link #scale(float, float)}, {@link #translate(float, float)} and
 * {@link #rotate(float)} instead of this method.
 *
 * @param matrix The matrix to replace the current matrix with. If it is
 *               null, set the current matrix to identity.
 *
 * @see #concat(Matrix)
 */
public void setMatrix(@Nullable Matrix matrix) {
    nSetMatrix(mNativeCanvasWrapper,
                     matrix == null ? 0 : matrix.native_instance);
}

根據註釋我們可以看到
(1)concat(matrix)的意思是將制定矩陣和canvas當前矩陣連接起來,也就是將它前乘制定矩陣;
(2)setMatrix(matrix),使用制定矩陣替代canvas當前矩陣
(3)在canvas變換的時候一般使用concat(),rotate(float),scale(float, float),translate(float, float)來替代setMatrix()

setTranslate()和preXXX或者postXXX效果不相同,這是怎麼回事呢???
通過源碼我們發現:
(1) 使用mMatrix.setXXX()的時候,Matrix的值變爲單位值,也就是Matrix先變成成單位矩陣,讓後在執行變換,也就是說上文中的setTranslate(10f, 20f)實際上是,先變成單位矩陣(回到最初效果),然後只執行我們的移動,這樣就出現了上圖中的效果,移動一次後再也不移動了.
(2)而使用preTranslate(),postTranslate()時,M’ = M * T,既每次調用它們時,我們要變換的效果矩陣乘以當前矩陣,把相乘後的矩陣賦給Matrix,這樣不斷循環,一直相乘我們的變換矩陣,不斷移動,就會出現上圖效果了.
注:我們在自定義view中實現圖片拖動,旋轉等效果就是根據這個原理來實現的.

Matrix

簡單介紹,具體的內容後邊介紹

preXXX和postXXX一個是前乘一個是後乘。我們知道Matrix是一個矩陣,而我們設置的參數也是一個矩陣,最終的結果肯定是這兩個矩陣相互運算後得出的。
對於矩陣可以這樣說,圖形的變換實質上就是圖形上點的變換,而我們的Matrix計算也是基於此,比如點P(x0,y0)經過矩陣變換後會去到P(x,y)的位置。學過矩陣的就知道了,矩陣乘法是不能交換左右位置的,矩陣相乘時前乘和後乘就很重要了。

preXXX和postXXX一個是前乘一個是後乘.setxxx是設置當前矩陣,不進行運算。

舉例:

matrix.preScale(0.5f, 1);   
matrix.setScale(1, 0.6f);   
matrix.postScale(0.7f, 1);   
matrix.preTranslate(15, 0);  

把上面的代碼,變爲用兩個數字組成的 [x,x] 運算對象。
pre、post表示運算順序。
遇到post的矩陣,就把這個矩陣放在已有矩陣後面進行乘法;
遇到pre,那麼就把這個矩陣放到已有矩陣之前進行乘法。

那麼,上面代碼運算過程如下:
1. [0.5f,1] * 原始矩陣 = 矩陣A  (因爲是pre,所以設置的值放在原始矩陣之前相乘)
2. [1,0.6f] -> 矩陣A = [1,0.6f] = 矩陣B (因爲是set,所以不會進行運算,直接重置所有值)
3. 矩陣B * [0.7f,1] = 矩陣C   (因爲是post,所以把設置的值放在後面相乘)
4. [15,0] * 矩陣C = 最終結果   (因爲是pre,所以把設置值放在矩陣之前相乘)

所以,我們如果想要實現多種變換按照一定順序實現的話,可以使用preXXX()和postXXX()來實現我們想要的效果,例如圖片拖動和縮放控件


給畫布填充色值

  • drawARGB(int a, int r, int g, int b)
  • drawColor(@ColorInt int color)
  • drawRGB(int r, int g, int b)
    使用Color給畫布填充色值,注意混合模式使用的是srcover
  • drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode)
    使用指定的混合模式給畫布填充色值

draw(幾何圖形)

  • drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,@NonNull Paint paint)
  • drawArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean useCenter, @NonNull Paint paint)

畫弧形和弧線:

paint.strokeWidth = 3f
var rectF1 = RectF(0f, 0f, 100f, 100f)
paint.style = Paint.Style.FILL_AND_STROKE
paint.color = Color.RED
canvas?.drawRect(rectF1, paint)
paint.style = Paint.Style.STROKE
paint.color = Color.WHITE
canvas?.drawArc(rectF1, 0f, 120f, true, paint)

paint.style = Paint.Style.FILL_AND_STROKE
var rectF2 = RectF(0f, 150f, 100f, 250f)
paint.color = Color.RED
canvas?.drawRect(rectF2, paint)
paint.color = Color.WHITE
paint.style = Paint.Style.STROKE
canvas?.drawArc(rectF2, 0f, 120f, false, paint)

var rectF3 = RectF(150f, 0f, 250f, 100f)
paint.style = Paint.Style.FILL_AND_STROKE
paint.color = Color.RED
canvas?.drawRect(rectF3, paint)
paint.color = Color.WHITE
canvas?.drawArc(rectF3, 0f, 120f, true, paint)

var rectF4 = RectF(150f, 150f, 250f, 250f)
paint.color = Color.RED
canvas?.drawRect(rectF4, paint)
paint.color = Color.WHITE
canvas?.drawArc(rectF4, 0f, 120f, false, paint)

弧形和弧線
使用這個函數,我們可以畫自定義圓形進度條、鐘錶、汽車油表…如果有興趣,可以自己去寫,當然還的有其他函數如:drawText(),drawLine(),有這三個基本就可以實現了.


  • drawRect(float left, float top, float right, float bottom, Paint paint)
  • drawRect(Rect r, Paint paint)
  • drawRect(RectF rect, Paint paint)

這三個函數畫矩形,非常簡單,示例就不寫了


  • drawRoundRect(RectF rect, float rx, float ry, Paint paint)
  • drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)

rect:圓弧矩形邊界
rx:x方向上的圓角半徑。
ry:y方向上的圓角半徑

這兩個函數我們應該瞭解,圓形矩形,例如我們在使用BitmapShader畫帶弧度的的矩形圖片
例:

paint.setShader(BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP))
var rectF1 = RectF(0f, 0f, 240f, 160f)
canvas?.drawRoundRect(rectF1, 40f, 40f, paint)
paint.shader = null
paint.style = Paint.Style.FILL_AND_STROKE
paint.strokeWidth = 1f
paint.color = Color.BLACK
canvas?.drawPoint(250f,40f,paint)
canvas?.drawPoint(250f,80f,paint)

弧度圓角圖片


  • drawText(String text, float x, float y, Paint paint)
  • drawText(String text, int start, int end, float x, float y, Paint paint)
  • drawText(char[] text, int index, int count, float x, float y, Paint paint)
  • drawText(CharSequence text, int start, int end, float x, float y, Paint paint)

根據基線(baseline)座標將文字畫到畫布上

paint.textSize = 20f
// 設置色值漸變
paint.shader = LinearGradient(0f, 0f, 80f, 30f, Color.RED, Color.GREEN, Shader.TileMode.CLAMP)
paint.setShadowLayer(5f, 2f, 3f, Color.parseColor("#77000000"))
canvas?.drawText("android開發", 0, 3, 5f, 30f, paint)

這裏寫圖片描述
從中我們可以看出,給字體設置樣式,色值,陰影等效果都是使用paint來設置


  • drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
  • drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)
    根據路線(path)繪製文本
canvas?.translate(500f, 500f)
canvas?.rotate(180f)
paint.textSize = 38f
paint.color = Color.RED
paint.style = Paint.Style.STROKE
var text = "I'm an android developer and I'm testing the canvas feature"
var path = Path()
path.addCircle(0f, 0f, 200f, Path.Direction.CW)
path.close()
paint.pathEffect = CornerPathEffect(60f)
canvas?.drawPath(path, paint)
paint.color = Color.BLACK
canvas?.drawTextOnPath(text, path, 0f, 0f, paint)

這裏寫圖片描述
上文中提到的自定義汽車油表中的文字就是使用該函數來實現的,只是路徑不同而已.
如果有興趣的話,可以修改hOffset, vOffset的值來看看效果,


  • drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint)
  • drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint)
    start:繪製文本的開始位置
    end:繪製文本的結束位置,start <= end
    index:繪製文本的開始位置
    count:繪製文本的長度,count = end - start
    contextStart: 上下文開始位置 ,contextStart 需要小於等於 start ,0 <= contextStart
    contextEnd:上下文的結束位置。contextEnd 需要大於等於 end,contextEnd <= text.length
    contextIndex:上下文的開始位置
    contextCount:上下文的長度,contextCount 大於等於 count,contextCount = contextEnd - contextStart
    x,y:繪製文本的基線開始座標

這個函數中的context我看了半天也不是太明白,網上搜了半天,找到一個解釋詳細的,如果想仔細理解的話點擊查看

canvas?.drawTextRun(text, 0, text.length, 0, text.length, 0f, 100f, false, paint)
canvas?.drawTextRun(text, 0, text.length, 0, text.length, 100f, 100f, true, paint)

這裏寫圖片描述


  • drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)
  • drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count, @NonNull Paint paint)
  • drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint)
    繪製線
    pts:直線斷點座標數組,每條直線四個數
    offset:偏移量,代表不參與劃線的點
    count:參與畫線的點的個數
var floatArray = floatArrayOf(0f, 0f, 10f, 400f, 10f, 10f, 20f, 410f, 20f, 20f, 30f, 420f, 30f, 30f, 40f, 430f)
canvas?.drawLines(floatArray, 4, 12, paint)
var floatArray1 = floatArrayOf(60f, 0f, 70f, 400f, 70f, 10f, 80f, 410f, 80f, 20f, 90f, 420f, 90f, 30f, 100f, 430f)
canvas?.drawLines(floatArray1, paint)

這裏寫圖片描述


  • drawPoint(float x, float y, Paint paint)
  • drawPoints(float[] pts, int offset, int count, Paint paint)

繪製點,同上面的繪製線,只是線由兩點四個數組成,而點由兩個數組成,其他相同


  • drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
    畫圓
paint.textSize = 20f;
canvas?.translate(200f, 200f)
paint.strokeWidth = 5f
paint.shader = SweepGradient(0f, 0f, Color.RED, Color.YELLOW)
paint.style = Paint.Style.STROKE
canvas?.drawCircle(0f, 0f, 100f, paint)
paint.shader = null
for (i in 0 until 12) {
    canvas?.drawLine(0f, 80f, 0f, 95f, paint)
    canvas?.drawText((i + 1).toString(), -10f, 75f, paint)
    canvas?.rotate(30f)
}

鐘錶或者油表


  • drawOval(float left, float top, float right, float bottom, Paint paint)
  • drawOval(RectF oval, Paint paint)

繪製橢圓,這個函數就不舉例,自己寫吧


  • drawPath(Path path, Paint paint)
    繪製由path路徑描述的幾何形狀
Path

別人寫的非常值得一看的一篇path文章


  • drawPicture(Picture picture, Rect dst)
  • drawPicture(Picture picture, RectF dst)
  • drawPicture(Picture picture)
Picture

該函數和Picture類在其他文中有單獨介紹有興趣可以點擊查看


  • drawPaint(Paint paint)

給畫布中的位圖填充色值


  • drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint)

將bitmap位圖繪製到畫布中,matrix作用到bitmap

canvas?.translate(500f, 500f)
mMatrix.postRotate(45f)
mMatrix.postSkew(0.2f, -0.5f)
canvas?.drawBitmap(bitmap, mMatrix, paint)

這裏寫圖片描述

  • drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)
    該函數在API21中已經過時
canvas?.translate(100f, 100f)
var mBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
var mCanvas: Canvas = Canvas(mBitmap)
paint.color = Color.RED
canvas?.drawLine(0f, 0f, 200f, 0f, paint)
var intarray = intArrayOf(
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY,
        Color.RED, Color.GREEN, Color.DKGRAY, Color.RED, Color.GREEN, Color.DKGRAY)
// 顏色數組的長度>=width*height,否則會出現數組越界異常
mCanvas.drawBitmap(intarray, 0, 10, 0, 5, 10, 10, false, paint)
canvas?.drawBitmap(mBitmap, 0f, 0f, paint)

這裏寫圖片描述
這個函數類似
Bitmap.createBitmap(@NonNull @ColorInt int[] colors, int offset, int stride,int width, int height, @NonNull Config config)

  • drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
  • drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
canvas?.drawBitmap(bitmap, 0f, 0f, paint)
canvas?.translate(0f, 10f + bitmap.height.toFloat())
var imgRect = Rect(0, 0, 300, 250)
var rect = Rect(0, 0, 300, 250)
canvas?.drawBitmap(bitmap, imgRect, rect, paint)
canvas?.translate(0f, 260f)
var rect2 = Rect(0, 0, 380, 300)
canvas?.drawBitmap(bitmap, imgRect, rect2, paint)
canvas?.translate(0f, 310f)
var rect3 = Rect(0, 0, 100, 80)
canvas?.drawBitmap(bitmap, imgRect, rect3, paint)
canvas?.translate(0f, 90f)
var imgRect1 = Rect(0, 0, 600, 400)
var rect4 = Rect(0, 0, 525, 328)
canvas?.drawBitmap(bitmap, imgRect1, rect4, paint)

這裏寫圖片描述
根據上圖,我們可以看到:
src:指的是圖片的剪切位置
dst:指的是繪製圖片的位置
如果src小於原圖,則剪切,如果dst小於或者大於src,圖片進行縮放,然後存放到指定位置中.

  • drawBitmap(Bitmap bitmap, float left, float top, Paint paint)

根據左邊和頂部左邊將圖片繪製到位圖中.


  • drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)

該函數可以對 Bitmap進行各種扭曲
bitmap - 需要進行扭曲的位圖
meshWidth - 橫向網格數量
meshHeight - 縱向網格數量
verts - 網格頂點座標數組,記錄扭曲後圖片各頂點的座標.大小最小爲 :(meshWidth+1) * (meshHeight+1) * 2 + vertOffset
vertOffset - 從第幾個頂點開始對位圖進行扭曲,通常傳 0
colors - 設置網格頂點的顏色,該顏色會和位圖對應像素的顏色疊加,數組大小爲 (meshWidth+1) * (meshHeight+1) + colorOffset,可以傳 null
colorOffset - 從第幾個頂點開始轉換顏色,通常傳 0

這是google APIDemo中的示例

// 原理:將bitmap的高寬分爲HEIGHT*WIDTH數組.根據實際高寬將相對應的值賦值到數組中.
//      通過觸摸bitmap,將觸摸的x,y點獲取到,修改與之對應的數組內容,讓後調用drawBitmapMesh改變圖片的某個地方.
    var paint: TextPaint
    var bitmap: Bitmap
    private val WIDTH = 60
    private val HEIGHT = 60
    private val COUNT = (WIDTH + 1) * (HEIGHT + 1)

    private val mVerts = FloatArray(COUNT * 2)
    private val mOrig = FloatArray(COUNT * 2)

    private val mMatrix = Matrix()
    private val mInverse = Matrix()

init {
        paint = TextPaint(Paint.ANTI_ALIAS_FLAG)
        bitmap = BitmapFactory.decodeResource(resources, R.mipmap.mn)
        isFocusable = true

        val w = bitmap.getWidth().toFloat()
        val h = bitmap.getHeight().toFloat()
        // 根據圖片高寬和網格數量,填充mVerts內容
        var index = 0
        for (y in 0..HEIGHT) {
            val fy = h * y / HEIGHT
            for (x in 0..WIDTH) {
                val fx = w * x / WIDTH
                setXY(mVerts, index, fx, fy)
                setXY(mOrig, index, fx, fy)
                index += 1
            }
        }

        mMatrix.setTranslate(10f, 10f)
        mMatrix.invert(mInverse)
    }

    private fun setXY(array: FloatArray, index: Int, x: Float, y: Float) {
        array[index * 2 + 0] = x
        array[index * 2 + 1] = y
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawColor(-0x333334)

        canvas?.concat(mMatrix)
        canvas?.drawBitmapMesh(bitmap, WIDTH, HEIGHT, mVerts, 0, null, 0, null)
    }

    private fun warp(cx: Float, cy: Float) {
        val K = 10000f
        val src = mOrig
        val dst = mVerts
        var i = 0
        while (i < COUNT * 2) {
            val x = src[i + 0]
            val y = src[i + 1]
            // 原數組中的數據
            val dx = cx - x
            val dy = cy - y
            // 求原數組和給定數據的差值
            val dd = dx * dx + dy * dy
            val d = Math.sqrt(dd.toDouble()).toFloat()
            var pull = K / (dd + 0.000001f)
            pull /= d + 0.000001f

            if (pull >= 1) {
                dst[i + 0] = cx
                dst[i + 1] = cy
            } else {
                dst[i + 0] = x + dx * pull
                dst[i + 1] = y + dy * pull
            }
            i += 2
        }
    }

    private var mLastWarpX = -9999
    private var mLastWarpY: Int = 0

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val pt = floatArrayOf(event.x, event.y)
        mInverse.mapPoints(pt)

        val x = pt[0].toInt()
        val y = pt[1].toInt()
        Log.e("ontouchevent", x.toString() + "--" + y.toString());
        if (mLastWarpX != x || mLastWarpY != y) {
            mLastWarpX = x
            mLastWarpY = y
            warp(pt[0], pt[1])
            invalidate()
        }
        return true
    }

效果圖就不貼了,自己複製代碼看效果吧


  • getClipBounds(Rect bounds)
  • getClipBounds()
    將canvas中剪切的邊界賦值到Rect中.
override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    canvas?.drawColor(-0x333334)
    canvas?.clipRect(0f, 0f, 200f, 100f)
    canvas?.drawBitmap(bitmap, 0f, 0f, paint)
    var rect = Rect();
    // rect = getClipBounds()
    canvas?.getClipBounds(rect)
    Log.e("Rect", rect.bottom.toString() + "--" + rect.right)
}

這裏寫圖片描述


  • getDensity() 獲取canvas的密度.
  • setDensity(int density)
    指定此畫布的背景位圖的密度。既修改畫布本身的目標密度,以及通過bitmap.setdensity (int)修改其背景位圖的密度


  • getDrawFilter()

  • setDrawFilter(DrawFilter filter) // 通過PaintFlagsDrawFilter給canvas設置或清除一些常量屬性
DrawFilter
PaintFlagsDrawFilter

PaintFlagsDrawFilter(int clearBits, int setBits)
clearBits:要清除的屬性:例如抗鋸齒…
setBits:要設置的屬性


  • getHeight()
  • getWidth()
override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    var bitmaps = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
    var canvass = Canvas(bitmaps)
    Log.e("Recanvassct", canvas?.width.toString() + "--" + canvass.width)
}

這裏寫圖片描述
從中可以看出,一個canvas我們沒有設置大小,它默認大小是該控件的高寬,而指定bitmap大小的canvas的大小是bitmap的大小


  • getMaximumBitmapWidth()
  • getMaxiMumBitmapHeight()

// 該函數是獲取canvas中允許畫Bitmap的最大寬度和高度.但是有一些不是特別理解,


  • saveLayerAlpha(float left, float top, float right, float bottom, int alpha)
  • saveLayerAlpha(@Nullable RectF bounds, int alpha)
  • saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint)
  • saveLayer(@Nullable RectF bounds, @Nullable Paint paint)
    保存圖層
  • save()
    保存canvas中的矩陣變換和剪切效果
  • restoreToCount()
    還原指定圖層
  • restore()
    還原所有圖層
  • getSaveCount()
    返回堆棧中存儲的圖層或者狀態數量
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    paint.color = Color.BLUE
    paint.style = Paint.Style.FILL_AND_STROKE
    // 在原始圖層上畫圖
    canvas?.drawCircle(200f, 200f, 80f, paint)
    // 創建一個新的透明圖層(圖層的邊界是:0,0,300,300)(如果在該圖層的paint沒有透明色值時,則使用0x77該透明度值,如果paint有透明色值,則使用該paint的透明值)
    canvas?.saveLayerAlpha(0f, 0f, 300f, 300f, 0x77)
    // 在透明圖層上畫圖
    canvas?.drawColor(Color.parseColor("#44ff0000"))
    // paint.color = Color.parseColor("#55ff0000")
    canvas?.drawCircle(150f, 150f, 80f, paint)
    // 恢復到原始圖層
    canvas?.restore()
    paint.color = Color.GREEN
    // 在原始圖層上繼續畫圖
    canvas?.translate(-50f, -50f)
    canvas?.drawCircle(250f, 250f, 50f, paint)
}

這裏寫圖片描述

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        paint.color = Color.BLUE
        paint.style = Paint.Style.FILL_AND_STROKE
        // 在原始圖層上畫圖
        canvas?.drawCircle(200f, 200f, 80f, paint)

        // 創建一個新的透明圖層(圖層的邊界是:0,0,300,300)(如果在該圖層的paint沒有透明色值時,則使用0x77該透明度值,如果paint有透明色值,則使用該paint的透明值)
        val layerAlpha: Int? = canvas?.saveLayerAlpha(0f, 0f, 300f, 300f, 0x77)
        // 在透明圖層上畫圖
        canvas?.drawColor(Color.parseColor("#44ff0000"))
        // paint.color = Color.parseColor("#55ff0000")
        canvas?.drawCircle(150f, 150f, 80f, paint)

        // 創建一個新的圖層layerAlpha1高寬400x400
        val layerAlpha1 = canvas?.saveLayerAlpha(0f, 0f, 400f, 400f, 0x255)
        paint.color = Color.parseColor("#ff0000")
        canvas?.drawRect(0f, 0f, 100f, 100f, paint)
        //該圖層上畫矩形

        // 還原layerAlpha1圖層
        layerAlpha1?.let { canvas?.restoreToCount(it) }
        paint.color = Color.GREEN
        // 在layerAlpha圖層上繼續畫圖
        canvas?.drawCircle(250f, 250f, 80f, paint)
        // 還原layerAlpha圖層到原始圖層上
        layerAlpha?.let { canvas?.restoreToCount(it) }
        // 在最初的圖層上畫圖
        canvas?.drawCircle(350f, 350f, 80f, paint)
    }

這裏寫圖片描述
這是一個簡單的示例:如果有不明白的可以修改屬性和方法來測試.在學習Xfermode混合模式,編寫示例的時候會用到這個幾個函數.

在使用該類函數時,實際上有6個常量來表示保存不同的狀態,但是,我們最好是使用系統推薦的ALL_SAVE_FLAG這個屬性,一些其他屬性和含有設置屬性的方法已經過時了,不建議我們使用.


  • isHardwareAccelerated 判斷該canvas是否設置硬件加速
    這個就不例了
  • quickReject()意思是指定區域是否在剪切區域外;
    這個不是特別理解,在網上查了查,發現說在一些優化自定義view性能的時候會使用該函數和clipRect()來提高性能.後期花點時間在研究這個,到時候好好上網查查.

好了,canvas暫時就寫到這了…,沒有解決的問題後期會繼續解決的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章