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(),纔可以把我們要裁剪的內容畫出來
效果圖:
- 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()
}
- 使用Matrix.setTranslate後,一直點擊按鈕,發現最終的效果如上圖所示.
- 使用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
- 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暫時就寫到這了…,沒有解決的問題後期會繼續解決的