一起看画布Android Canvas

版权声明:本文为博主原创文章,未经博主允许不得转载

前言

Canvas 本意是画布的意思,然而将它理解为绘制工具一点也不为过。通过 Canvas 提供的 API,你可以在画布上绘制出绝大部分图形,再配合上一些操作画布的 API,比如旋转剪裁等变换画布的操作,就能够巧妙地画出更加复杂的图形。本文将通过结合实例带你深入学习 Android 中的 Canvas。如有需要进一步学习画笔 Paint ,请移步另一篇文章—— 一起看Android Paint

drawXXX系列

canvas.drawArc

  1. 方法:

     drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint)
     drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
    
  2. 画的方向为顺时针

  3. 对参数的解释:

    • userCenter 若为true表示此弧会和 RectF 中心相连形成扇形,否则,弧的两头直接相连形成图形。
    • startAngle,负数或大于360则对360模除。
    • sweepAngle,大于360,则画出一圈。
    • 角度:以 RectF 中心为坐标中心,中心所在直线为水平线,负角度弧斜上走,正角度弧斜下走,或者说以时钟三点钟为0度,顺时针为正,逆时针为负。
  4. 例子:

     mPaint.setAntiAlias(true);
     mPaint.setColor(Color.RED);
     RectF mRecF=new RectF(20,20,200,200);
     canvas.drawArc(mRecF,-45,135,true,mPaint);//以斜上45度为起点,顺时针扫过135度
    
  • useCenter=true
    image
  • useCenter=false
    image

canvas.drawCircle

  1. 方法:

     drawCircle(float cx, float cy, float radius, Paint paint) 
    
  2. 对参数的解释:

    • cx,cy 为所画圆的中心坐标,radius 为圆的半径
  3. 例子

     mPaint.setAntiAlias(true);
     mPaint.setColor(Color.RED);
     canvas.drawCircle(100,100,80,mPaint);   
    
  • 圆心为(100,100),半径为80
    image
  1. 注意: 当画笔设置了 StrokeWidth 时,圆的半径=内圆的半径+StrokeWidth/2

canvas.drawBitmap

  1. 方法1:

     drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) 
    

对参数的解释:
- bitmap:要画在画布上的位图
- matrix:构建的矩阵作用于将要画出的位图

  1. 方法2:

     drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
    

对参数的解释:
- src:可为 null,表示画整个位图,否则只花出位图的一块矩形区域图.subset of bitmap
- dst:定义的一个矩形范围,位图会平移或缩放来将自身放入矩形内

  1. 方法3:

     drawBitmap(Bitmap bitmap, float left, float top, Paint paint)  
    
  2. 方法4:

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

网格扭曲,水波等的绘制
知识:https://www.zybuluo.com/cxm-2016/note/506317

  1. 例子:
  • 方法1:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          Matrix matrix = new Matrix();
          matrix.postTranslate(100,0);//左移100
          matrix.postRotate(45);//顺时针旋转45度
          canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher),matrix,mPaint);  
    
image
  • 方法2:

         Rect src = new Rect(20, 20, 40, 40);//取bitmap上src区域的部分图像
         Rect dst = new Rect(100, 100, 200, 200);//绘制的最终区域,一定填满
         mPaint.setAntiAlias(true);
         mPaint.setColor(Color.RED);
         canvas.drawBitmap(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher),src,dst,mPaint);  
    
image
  • 方法3:

           canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 100, 100, mPaint);  
    
image

canvas.drawColor,drawRGB

  1. 方法:

     drawColor(int color, PorterDuff.Mode mode) 
    

画整个画布的背景,但若区域受到剪裁,则只绘制剪裁区域的背景. 关键类 PorterDuff.Mode

  1. 方法:drawRGB(int r, int g, int b)
    同上

canvas.drawLine(s)

  1. 方法1:

     drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
    

对参数的解释:
- 前四个参数为直线的起点和终点的 XY 轴坐标

  1. 方法2:

     drawLines(float[] pts,Paint paint)
    
  2. 方法3:

     drawLines(float[] pts, int offset, int count, Paint paint)  
    

对参数的解释:
- pts:待画的坐标点数组,格式为(x1,y1,x2,y2,...),至少4个值
- offset:要跳过坐标点数组中几个值才开始画(必须是4的倍数)
- count:至少为2,offset 之后数组的大小。

  1. 例子
  • 方法1:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          mPaint.setTextSize(16);
          canvas.drawText("起点(20,100)", 22, 100, mPaint);
          canvas.drawText("终点(50,100)", 52, 150, mPaint);
          canvas.drawLine(20, 100, 50, 150, mPaint);  
    
image
  • 方法2:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          mPaint.setTextSize(16);
          canvas.drawText("A1(20,100)", 0, 90, mPaint);
          canvas.drawText("A2(100,350)", 40, 370, mPaint);
          canvas.drawText("B1(100,100)", 80, 90, mPaint);
          canvas.drawText("B2(180,350)", 150, 370, mPaint);
          float[] points=new float[]{20,100,100,350,100,350,100,100,100,100,180,350};//至少4个值,即能够绘制一条直线
          canvas.drawLines(points,mPaint);  
    
image
  • 方法3:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          mPaint.setTextSize(16);
          canvas.drawText("A1(100,350)", 40, 370, mPaint);
          canvas.drawText("B2(100,100)", 80, 90, mPaint);
          canvas.drawText("B3(180,350)", 150, 370, mPaint);
          float[] points=new float[]{20,100,100,350,100,350,100,100,100,100,180,350};//至少4个点
          canvas.drawLines(points,4,8,mPaint);  
    
image

canvas.drawRect

  1. 方法1:

     void drawRect(float left, float top, float right, float bottom, Paint paint)  
    

确定矩形四个顶点的位置配上画笔即可

  1. 方法2:

     void drawRect(Rect r, Paint paint) 
    

矩形的四个位置为整型时使用

  1. 方法3:

     void drawRect(RectF r, Paint paint)  
    

方法1的另一简版,矩形的四个位置为浮点型时使用

  1. 例子:

     mPaint.setAntiAlias(true);
     mPaint.setColor(Color.RED);
     canvas.drawRect(new RectF(20f, 20f, 120f, 120f), mPaint);
     //canvas.drawRect(new Rect(20, 20, 120, 120), mPaint);  
    
image

canvas.drawOval

绘制椭圆
类似 drawRect

canvas.drawPaint

方法:
drawPaint(Paint paint)
自定义的 paint 画在整个画布上,等于用 paint 在画布上画一个无限大的矩形,但当前画布受到剪裁,则染色区域仅限于剪裁部分。

canvas.drawPoint(s)

绘制点,方法基本类似drawLine(s)

canvas.drawRoundRect

  1. 方法1:

     drawRoundRect(RectF rect, float rx, float ry, Paint paint)   
    
  2. 方法2:

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

对参数的解释:
- rx,ry 表示 leftleft+rxleftleft+ry 所围区域做弧,其余三个角类似,当 rx=ry>=(right-left)/2 时表示画一个半径为 rx(ry) 的圆(刚好外接一个矩形)

  1. 例子
  • 方法1:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          RectF mRecf = new RectF(20, 100, 200, 200);
          canvas.drawRoundRect(mRecf, 30, 50, mPaint);  
    
image
  • 方法2:API level至少21,做法一样

canvas.drawText

  1. 方法1:

     drawText(String text, float x, float y, Paint paint)  
    

x,y 位置开始画 text
注意:其中 y 表示文字的基线(baseline )所在的坐标,说白了就是我们小学写字用的那种带有横线的本子(一般都是按照一条基线来写字是吧?),用于规范你写的字是否成一条直线,否则很多人写着写着就往上飘了。而 x 坐标就是文字绘制的起始水平坐标,但是每个文字本身两侧都有一定的间隙,故实际文字的位置会比 x 的位置再偏右侧一些。

  1. 图:
    基线类似下图深绿色的横线

    image

  2. 方法2:

     drawText(CharSequence text, int start, int end, float x, float y, Paint paint)  
    

x,y 位置上画出 startend(不含 end) 之间的字符 CharSequence charSequence = "charSequence";

  1. 方法3:

     drawText(char[] text, int index, int count, float x, float y, Paint paint)  
    

对参数的解释:
- index:表示从第几个字符开始,
- count:表示截取的数组长度
- 字符数组的定义: char[] a="abc".toCharArray()

  1. 方法4:

     drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
    

对参数的解释:
- path:文本绘制的路径(关键)
- hOffset:相对于路径的水平偏移量
- vOffset:相对于路径的垂直偏移量

  1. 方法5:

     drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)  
    

    方法3和4的合体

  2. 方法6:

     drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)  
    

对参数的解释:
- contextStart:可选,直接=start
- contextEnd:可选,直接=end
- x,y:文字绘制起点
- isRt1(isRightToLeft):文字是否支持rtl
- 0 <= contextStart <= start <= end <= contextEnd <= text.length

  1. 方法7:

     drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, float x, float y, boolean isRtl, Paint paint)  
    

方法3和方法6合体

```count = end - start, contextCount = contextEnd - contextStart.``` 
  1. 例子:
  • 方法1:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          mPaint.setTextSize(20);
          canvas.drawText("Canvas学习",50,100,mPaint);  
    
image
  • 方法2:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          mPaint.setTextSize(20);
          CharSequence charSequence="Canvas学习";
          canvas.drawText(charSequence,1,charSequence.length(),30,50,mPaint);  
    
image
  • 方法3:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          mPaint.setTextSize(20);
          char[] chars="Canvas学习".toCharArray();
          canvas.drawText(chars,1,chars.length-1,30,50,mPaint);  
    

效果图同方法2

  • 方法4:

          mPaint.setAntiAlias(true);
          mPaint.setColor(Color.RED);
          mPaint.setTextSize(20);
          Path path=new Path();
          String text="Canvas学习";
          path.addCircle(100,100,50, Path.Direction.CCW);
          canvas.drawTextOnPath(text,path,0f,0f,mPaint);  
    
image

canvas.drawPath

方法:drawPath(Path path, Paint paint)
根据定义的路径画出图
例子:

        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        Path path=new Path();
        path.addCircle(100,100,50, Path.Direction.CCW);
        canvas.drawPath(path,mPaint);  

效果等于画一个圆

canvas.clipxxx系列

canvas.clipPath

  1. 方法1:

     clipPath(Path path)  
    

按所定义的路线剪裁,默认Region.Op.INTERSECT表示剪裁出相交的部分

  1. 方法2:

     clipPath(Path path, Region.Op op)  
    

解释:用指定的路径 path 修改当前的剪裁
对op参数的理解:以剪裁两次的区域分别为A,B来区别
- Region.Op.DIFFERENCE:剪裁出差异的部分,类似 A-B 部分
- Region.Op.REPLACE:后剪裁B的覆盖剪裁的A
- Region.Op.REVERSE_DEFFERENCE:剪裁出差异的部分,类似 B-A 部分
- Region.Op.INTERSECT:剪裁出相交的部分,类似 A交B 部分
- Region.Op.UNION:剪裁出AB合并的部分,类似** AUB**
- Region.Op.XOR:是** (AUB)-(A交B)** 刚好与** A交B** 相对

  1. 方法3:

     clipRect(Rect[F] rect, Region.Op op)[]表示可选  
    

解释:用指定的矩形来修改当前的剪裁

  1. 方法4:

     clipRect(Rect rect)  
    

剪裁一个矩形区域,还有其他能构造矩形的方法不再列出

  1. 例子(以剪裁路径为例):
    首次剪裁

         Path path=new Path();
         path.addCircle(100,100,50, Path.Direction.CCW);
         canvas.clipPath(path);
         canvas.clipPath(path);
         canvas.drawColor(Color.RED);//红色区域即为剪裁的区域  
    

由于 clipPath 方法剪裁模式默认为Region.Op.INTERSECT,故当前剪裁部分和整个画布相交即为本身。

image

  1. 剪裁模式(绿色区域为最终得到的剪裁部分)

     mPaint.setAntiAlias(true);
     mPaint.setColor(Color.RED);
     canvas.drawColor(Color.BLUE);
     canvas.drawRect(new RectF(20, 20, 120, 120), mPaint);
     canvas.drawCircle(120, 70, 50, mPaint);
     canvas.clipRect(new RectF(20, 20, 120, 120));
     Path path = new Path();
     path.addCircle(120, 70, 50, Path.Direction.CCW);
     canvas.clipPath(path, Region.Op.INTERSECT);
     canvas.drawColor(Color.GREEN);  
    
  • Region.Op.INTERSECT
    image
  • Region.Op.REPLACE
    image
  • Region.Op.REVERSE_DEFFERENCE
    image
  • Region.Op.UNION
    image
  • Region.Op.XOR
    image

canvas的保存与恢复

  1. 解释:用来保存或恢复 Canvas 的状态
  2. 作用:save 之后可以调用 Canvas 的平移、放缩、旋转、错切、裁剪等对当前画布进行操作,再进行相应的绘制,避免影响画布上已绘制的 view,配合 canvas.restore()(将当前画布恢复到初始状态) 使用

canvas的变幻操作

canvas.translate

  1. 方法:

     canvas.translate(float dx, float dy)  
    

作用:移动当前画布水平距离 dx,竖直距离 dy

  1. 例子:

     mPaint.setAntiAlias(true);
     mPaint.setColor(Color.RED);
     canvas.drawColor(Color.BLUE);
     canvas.translate(100,100);
     canvas.drawCircle(0,0,50,mPaint);  
    
image

canvas.scale

  1. 方法1:

     canvas.scale(float sx, float sy)  
    

作用:sx、syx、y 方向上缩放的倍数,画布缩放后,再画出的图片相应的坐标都会进行缩放

  1. 例子:

     mPaint.setAntiAlias(true);
     mPaint.setColor(Color.RED);
     canvas.drawColor(Color.BLUE);
     canvas.save();
     canvas.scale(0.5f,0.5f);//x,y均缩小一半
     canvas.drawCircle(100,100,50,mPaint);
     canvas.restore();
     mPaint.setColor(Color.WHITE);
     canvas.drawCircle(100,100,50,mPaint);  
    
image
  1. 方法2:

     canvas.scale (float sx, float sy, float px, float py)  
    

作用:缩放画布并平移画布到基准点 (px,py)
对参数的解释:
- px,py 为缩放后画布新的坐标原点(也叫缩放基准点)

  1. 例子:

     mPaint.setAntiAlias(true);
     canvas.drawColor(Color.BLUE);
     mPaint.setColor(Color.WHITE);
     canvas.drawCircle(100,100,50,mPaint);
     canvas.save();
     canvas.scale(0.5f,0.5f,100,100);
     mPaint.setColor(Color.RED);
     canvas.drawCircle(100,100,50,mPaint);
     canvas.restore();  
    
image

canvas.rotate

  1. 方法:

     canvas.rotate(float degrees)  
    

作用:顺时针旋转当前画布一定角度,也可加入基准点坐标

  1. 例子:

     mPaint.setAntiAlias(true);
     canvas.drawColor(Color.BLUE);
     mPaint.setColor(Color.WHITE);
     canvas.drawRect(new RectF(80,80,180,180),mPaint);
     canvas.save();
     canvas.rotate(45);
     //canvas.rotate(45,200,200);
     mPaint.setColor(Color.RED);
     canvas.drawRect(new RectF(80,80,180,180),mPaint);
     canvas.restore();  
    
  • 无基准点

    image
  • 有基准点 (200,200)

    image

canvas.skew

  1. 方法:

     canvas.skew(float sx, float sy)  
    

作用:画布的错切

  • sx:将画布在 x 方向上倾斜相应的角度,sx 为倾斜角度的 tan 值;
  • sy:将画布在 y 轴方向上倾斜相应的角度,sy 为倾斜角度的 tan 值;
    比如在 X 轴方向上倾斜45度,tan45=1;
  1. 例子:

     mPaint.setAntiAlias(true);
     canvas.drawColor(Color.BLUE);
     mPaint.setColor(Color.WHITE);
     canvas.drawRect(new RectF(0,0,180,180),mPaint);
     canvas.save();
     canvas.skew(1,0);//画布X轴倾斜45度
     mPaint.setColor(Color.RED);
     canvas.drawRect(new RectF(0,0,180,180),mPaint);
     canvas.restore();  
    
image

总结

以上大致介绍了 Canvas 类中众多绘制方法。首先,先对方法进行解析;其次,给出相应的示例代码并结合运行效果,旨在帮助读者更好地理解诸如上述绘制方法的基本使用;最后,对于方法的理解如有纰漏,欢迎指正。

感谢

官方Canvas类API
Canvas之translate、scale、rotate、skew方法讲解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容