Canvas详解(android自定义view,onDraw()绘制各种图形)

Canvas

  • Canvas
      • 函数和常量的介绍
          • 常量
          • 构造函数
          • 函数
          • Region
            • Region.Op
          • Matrix
          • Path
          • Picture
          • DrawFilter
            • PaintFlagsDrawFilter

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(),才可以把我们要裁剪的内容画出来

效果图:
Canvas详解(android自定义view,onDraw()绘制各种图形)_第1张图片

  • 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()
}

Canvas详解(android自定义view,onDraw()绘制各种图形)_第2张图片
Canvas详解(android自定义view,onDraw()绘制各种图形)_第3张图片
我记得前边写过这种图形,其中一个是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()

Canvas详解(android自定义view,onDraw()绘制各种图形)_第4张图片

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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第5张图片

  • 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后,一直点击按钮,发现最终的效果如上图所示.
    Canvas详解(android自定义view,onDraw()绘制各种图形)_第6张图片
  2. 使用Matrix.postTranslate或者Matrix.preTranslate后发现最终的效果如下图:通过不断移动,最终消失在屏幕上
    Canvas详解(android自定义view,onDraw()绘制各种图形)_第7张图片

我们先看一下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.
 *
 * Note: 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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第8张图片
使用这个函数,我们可以画自定义圆形进度条、钟表、汽车油表…如果有兴趣,可以自己去写,当然还的有其他函数如: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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第9张图片


  • 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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第10张图片
从中我们可以看出,给字体设置样式,色值,阴影等效果都是使用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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第11张图片
上文中提到的自定义汽车油表中的文字就是使用该函数来实现的,只是路径不同而已.
如果有兴趣的话,可以修改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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第12张图片


  • 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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第13张图片


  • 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)
}

Canvas详解(android自定义view,onDraw()绘制各种图形)_第14张图片


  • 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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第15张图片

  • 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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第16张图片
这个函数类似
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)

Canvas详解(android自定义view,onDraw()绘制各种图形)_第17张图片
根据上图,我们可以看到:
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)
}

Canvas详解(android自定义view,onDraw()绘制各种图形)_第18张图片

    @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)
    }

Canvas详解(android自定义view,onDraw()绘制各种图形)_第19张图片
这是一个简单的示例:如果有不明白的可以修改属性和方法来测试.在学习Xfermode混合模式,编写示例的时候会用到这个几个函数.

在使用该类函数时,实际上有6个常量来表示保存不同的状态,但是,我们最好是使用系统推荐的ALL_SAVE_FLAG这个属性,一些其他属性和含有设置属性的方法已经过时了,不建议我们使用.


  • isHardwareAccelerated 判断该canvas是否设置硬件加速
    这个就不例了
  • quickReject()意思是指定区域是否在剪切区域外;
    这个不是特别理解,在网上查了查,发现说在一些优化自定义view性能的时候会使用该函数和clipRect()来提高性能.后期花点时间在研究这个,到时候好好上网查查.

好了,canvas暂时就写到这了…,没有解决的问题后期会继续解决的

你可能感兴趣的:(android,Canvas)