Canvas的常见用法

作者:彩笔学长
原文地址:http://blog.csdn.net/XSF50717/article/details/51527140

Canvas

Canvas是一种抽象概念,是2D图形系统中的重要部分,canvas一系列函数最终都是Android 2D图形库Skia的一些列封装,对应在SKCanvas.cpp。canvas在系统中的位置如下图所示:

可以将canvas看成一个透明的图层,使用canvas之后会产生一个透明图层,然后在这个新图层上画图,画完之后覆盖在屏幕上显示,叠加。 比较经典的例子就是:

 protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //构造一个矩形
    Rect rect1 = new Rect(0, 0, 400, 220);
    //在平移画布前用绿色画下边框
    canvas.drawRect(rect1, paint_green);

    //平移画布后,再用红色边框重新画下这个矩形
    canvas.translate(100, 100);
    canvas.drawRect(rect1, paint_red);
}

我们首先画了一个绿色矩形框,然后将canvas平移了,接着画了一个红色的矩形框,结果如下:

这里我们会发现,平移了canvas之后绿色矩形框没发生变化!拉近镜头我们仔细看看究竟发生了什么,如何印证canvas每次会产生一个透明图层?

调用canvas.drawRect(rect1, paint_green);时,产生一个Canvas透明图层,由于当时还没有对坐标系平移,所以坐标原点是(0,0);再在系统在Canvas上画好之后,覆盖到屏幕上显示出来,过程如下图(图片来自网络,链接在文后):

然后再第二次调用canvas.drawRect(rect1, paint_red);时,又会重新产生一个全新的Canvas画布,此时由于使用tranlate移动了画布 因而在画图时是以新原点来产生视图的,然后合成到屏幕上,超出部分不显示,最后就是我们看到结果了。

Canvas常用方法

下面来清点一下重要的API,绘制颜色就不赘述了。

绘制基本形状

drawRoundRecf画圆角矩形

// 第一种
    RectF rectF = new RectF(100,100,400,400);
    canvas.drawRoundRect(rectF,30,30,paint_red);

 // 第二种 需要api21,一般常用第一种
 // canvas.drawRoundRect(100,100,800,400,30,30,paint_red);

这里是采用第一种方法画出来的,参数30(rx),30(ry)是椭圆的两个半径,用来确定圆角的弧度。

drawOval画椭圆

// 第一种
    RectF rectF = new RectF(100,100,500,400);
    canvas.drawOval(rectF,paint_red);

// 第二种
    canvas.drawOval(100,100,800,400,paint_red);

通常也是使用第一种,画椭圆只需要一个矩形即可,椭圆是改矩形的内切。

drawArc画圆弧

// 第一种 
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}

// 第二种 
public void drawArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean useCenter, @NonNull Paint paint) {}

可以看出这里使用了椭圆的形状,除此之外加上了三个参数:

startAngle // 开始角度
sweepAngle // 扫过角度
useCenter // 是否和中心点连成一个封闭的图形
RectF rectF = new RectF(100, 100, 400, 400);
RectF rectF1 = new RectF(200, 200, 500, 500);

canvas.drawArc(rectF, 0, 40, true, paint_green);
canvas.drawArc(rectF1, 0, 40, false, paint_green);

画圆弧要清楚,0-40画弧,首先水平那根线是起点,按顺时针40度停止。

drawPath

这个结合path一起使用会非常强大!(path用法也是内涵满满,这里就不展开叙述了)

这里看下canvas中的用法:

public void drawPath(@NonNull Path path, @NonNull Paint paint){}

传入路径,和对应的画笔即可:

Path path = new Path();
path.moveTo(50,50);
path.quadTo(30,220,320,450);

canvas.drawPath(path,paint_green);

这里使用path画了一条贝塞尔曲线。

画布裁剪

裁剪涉及到一个Region.Op区域组合的问题:

假设用region A  去组合region B   
public enum Op {  
        DIFFERENCE(0), //A和B的差集范围,即A - B,只有在此范围内的绘制内容才会被显示;
        INTERSECT(1), // 即A和B的交集范围,只有在此范围内的绘制内容才会被显示
        UNION(2),      //即A和B的并集范围,即两者所包括的范围的绘制内容都会被显示;
        XOR(3),        //A和B的补集范围,此例中即A除去B以外的范围,只有在此范围内的绘制内容才会被显示;
        REVERSE_DIFFERENCE(4), //B和A的差集范围,即B - A,只有在此范围内的绘制内容才会被显示;
        REPLACE(5); //不论A和B的集合状况,B的范围将全部进行显示,如果和A有交集,则将覆盖A的交集范围;
 } 

clipPath( )clipRect( )clipRegion( );通过Path,Rect,Region的不同组合,几乎可以支持任意形状的裁剪区域!

这里演示一个Region.Op.DIFFERENCE,将画笔改为FILL,然后:

canvas.clipRect(10, 10, 110, 110);        //第一个
canvas.clipRect(50, 50, 150, 150, Region.Op.DIFFERENCE); //第二个
canvas.drawRect(0, 0, 200, 200, paint_green);

画布状态

  • save():把当前状态的状态进行保存,然后放入栈中
  • restore():把栈顶画布状态取出

画布变换

画布变换主要包括translate(位移)、scale(缩放)、rotate(旋转)、skew(倾斜),画布操作可以帮助我们更加容易的方式制作图形。

【位移translate】

public void translate(float dx, float dy)

基于当前坐标系的移动,并不是屏幕左上角的原点位置:

//构造一个矩形
Rect rect1 = new Rect(0, 0, 200, 100);
canvas.translate(50, 50);
//在平移画布前用绿色画下边框
canvas.drawRect(rect1, paint_green);

//平移画布后,再用红色边框重新画下这个矩形
canvas.translate(50, 50);
canvas.drawRect(rect1, paint_red);

【缩放scale】

public void scale (float sx, float sy),分别是x,y轴的缩放比例
public final void scale (float sx, float sy, float px, float py),x.y轴的缩放比例,px,py表示缩放中心位置

其中当缩放比例为负数时候,会根据缩放中心轴进行翻转。

套用网上一个经典的例子(画笔要设置粗一点,不然会有部分显示不全):

canvas.translate(mWidth/2,mHeight/2);

    RectF rectf = new RectF(-300,-300,300,300);
    for (int i=0;i<15;i++){
        canvas.scale(0.8f,0.8f);
        canvas.drawRect(rectf,paint_red);
    }

【旋转 rotate】

public void rotate (float degrees),旋转角度(顺时针)
public final void rotate (float degrees, float px, float py),旋转角度和控制点

这个在画表盘啥的很常用。同样跟位移一样,旋转度数也是可以叠加的。

【错切 skew】

public void skew (float sx, float sy)

float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值, 
float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值.
Rect rect1 = new Rect(10,10,200,100);

canvas.drawRect(rect1, paint_green);
canvas.skew(1,0);//X轴倾斜45度,Y轴不变
canvas.drawRect(rect1, paint_red);

绘制文本

普通水平绘制drawText

这个比较简单,但是需要注意绘制text绘制精确位置使用FontMetrics,主要包含四个参数:

ascent = ascent线的y坐标 - baseline线的y坐标;
descent = descent线的y坐标 - baseline线的y坐标;
top = top线的y坐标 - baseline线的y坐标;
bottom = bottom线的y坐标 - baseline线的y坐标;

指定每个文字位置

void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)
void drawPosText (String text, float[] pos, Paint paint)

参数说明

char[] text:要绘制的文字数组
int index::第一个要绘制的文字的索引
int count:要绘制的文字的个数,用来算最后一个文字的位置,从第一个绘制的文字开始算起
float[] pos:每个字体的位置,同样两两一组,如{x1,y1,x2,y2,x3,y3……}
float []pos=new float[]{80,100,
            100,200,
            120,300,
            140,400};
canvas.drawPosText("1234", pos, paint);

··

沿路径绘制

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

参数说明:

float hOffset : 与路径起始点的水平偏移距离
float vOffset : 与路径中心的垂直偏移量
    String string = "测试文字偏移的参数";

    Path circlePath = new Path();
    circlePath.addCircle(220, 200, 100, Path.Direction.CCW);
    canvas.drawPath(circlePath, paint_red);//绘制出路径原形

    Path circlePath2 = new Path();
    circlePath2.addCircle(550, 200, 100, Path.Direction.CCW);
    canvas.drawPath(circlePath2, paint_red);//绘制出路径原形

    paint_green.setTextSize(30);

     //hoffset、voffset参数值全部设为0,看原始状态是怎样的
    canvas.drawTextOnPath(string, circlePath, 0, 0, paint_green);
    //第二个路径,改变hoffset、voffset参数值
    canvas.drawTextOnPath(string, circlePath2, 80, 30, paint_green);

常用的基本介绍差不多了,还有诸如:

  • drawBitmapMesh:只对绘制的Bitmap作用,使其变形
  • drawVertices:使得画布变形

参考文章:

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

推荐阅读更多精彩内容