Canvas&Paint使用心得

缘起

自己画图在平常开发中不算很常见的需求,但偶尔有些需求还必须通过Canvas自己画出来,最近笔者就遇到了这样的事情,由于以前对这些API不是很熟悉,一路走来也是磕磕绊绊,不过总算熬过来了,最终也算是对这些API有了更深刻的认识,正好写篇文章记录下,供参考。

实际开发中发现,当我们new一个Paint的时候,其默认的style就是FILL模式,strokeWidth默认大概1像素左右,一般如果你用到的话,最好自己显式设置成合适的值,单位为像素。

各种API的使用详解

  1. Canvas#drawCircle(float cx, float cy, float radius, Paint paint);
    作用:画一个圆;
    cx,cy表示圆心,radius表示半径,paint的style如果是stroke则画出来的是一个镂空的圆,否则会是填充效果的圆;
    正常画圆的时候,这个API还是很简单的,但当我们设置了paint的strokeWidth为某一具体值时,比如50px,即我们想画一个圆环效果。
    但当你通过这样的方式画一个带宽度的圆环时,半径要特别注意下,举例如下:
    假设你要在宽高都是y的矩形区域画一个宽度为strokeW的圆环,那么圆环的半径应该是:
    r1 = y /2; 外圆,紧切着最外面的矩形区域
    r2 = y/2 - strokeW; // 半径稍小的内圆
    这时,你可能以为半径为r2就ok了,可是实际测试发现,当通过drawCircle画一个带有宽度的圆时(圆环),正确的半径应该是r = r2 + strokeW/2, 比想象中的要往外再扩大点,即要加上一半的strokeW;
    可以简单理解成有宽度时,需要的半径是中心圆(即不是内圆也不是外圆)的半径(外圆半径-strokeW/2或者内圆半径+strokeW/2)。

  2. Canvas#drawOval(RectF oval, Paint paint);
    作用:画椭圆
    oval:给定的矩形区域,在这个边界内画椭圆(矩形的内切圆),如果这个矩形区域恰好是正方形,那么画出来的椭圆实际上就是圆了,介绍这个主要是为了给下面的画弧线做铺垫。

  3. Canvas#drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint);
    作用:画弧线,椭圆的某一部分;
    oval: 弧线所在的椭圆的边界矩形区域,同上面的解释;
    startAngle: 起始角度,0度是指x轴正方向(钟表上的3点钟方向),正数表示沿顺时针方向转,负数则是逆时针方向,<0或>360实际相当于startAngle%360;
    sweepAngle: 扫过的角度,正数表示顺时针扫过,负数表示逆时针扫过,如果>360 or <-360,则整个椭圆会被画出来;
    useCenter:一般为false,不包括中心点,可以设为true来画一部分扇形;

  4. Canvas#drawText(String text, float x, float y, Paint paint);
    作用:画文字,这个时候经常需要调用paint#setTextSize方法来设置文字的大小,单位是px;
    其中Paint里面的textAlign,对最终结果会有重要的影响。
    paint.setTextAlign(Paint.Align.RIGHT);
    比如text是“你好啊世界”
    假设你Text align是Left,那么 x,y是相对text的左边说的,也就是会把“你”这个文字画在x的位置;
    center也是同样的道理,x表示中间字的位置,Right是说会把“界”字画在x位置,其他文字还是会在左边依次画出来;
    这里的y是指文字的baseline,并不是我们常见的top顶部,效果上是要偏下方点,注意这个区别。

  5. Canvas#transalte(dx, dy) 平移画布
    作用:将当前的画布坐标原点移动dx,dy,比如原先是0,0,经过(100, 100)操作后,新的绘制对应的原点就是(100,100);

  6. Canvas#scale(sx, sy)、scale(sx, sy, px, py) 缩放画布
    这2个方法的区别,可以看下源码,很清楚的展现了区别:

public void scale(float sx, float sy) {  
        native_scale(mNativeCanvasWrapper, sx, sy);
}
public final void scale(float sx, float sy, float px, float py) { 
        translate(px, py);    
        scale(sx, sy);    
        translate(-px, -py);
}

画布的缩放,比如scale(0.5f, 0.5f) 表示x、y方向上各缩小一半,比如画了个400*400的矩形,出来的效果就是200*200的,px,py表示对原点的平移。

  1. Canvas#rotate(float degrees, float px, float py); 旋转画布
    它的理解几乎和scale一模一样,这个方法在有些情况下是非常有用的,比如有个需求是要画个类似钟表刻度那样的界面,如下:

    钟表刻度盘

    你要做的只是画一条垂直方向的直线(线段),然后不停的旋转不同的角度即可(旋转360度),按照这个思路一下子就可以将这个复杂的问题完美解决掉。
    对这几个变换方法的理解可以参考下这篇文章,写的非常不错。

  2. 给Paint设置渐变器Shader;
    对Shader的理解可以参考这篇文章Shader图文详解
    经常我们需要用渐变色填充一个区域(比如由path决定的),实际中常用在绘制收益率曲线,然后将曲线围起来的下方区域用渐变色填充起来,示意代码如下:

 private void fillRegionWithGradientColor(Canvas canvas, Paint paint) {
        int w = getWidth();
        int h = getHeight();

        Path path1 = new Path();
        path1.moveTo(0, h);
        path1.lineTo(100, 200);
        path1.lineTo(150, 300);
        path1.lineTo(w/2, h/2);
        path1.lineTo(w/2+150, h/2);

        path1.lineTo(w, h);
       //path1.close();

        // 为Paint设置渐变器 在竖直方向从h/2到h渐变2个颜色值
        Shader mShader = new LinearGradient(0, h/2, 0, h, new int[] {
                0xffff5377, 0xfffeeeee}, null,
                Shader.TileMode.CLAMP);
        paint.setShader(mShader);
        canvas.drawPath(path1, paint);
    }
  1. Paint#setShadowLayer(float radius, float dx, float dy, int shadowColor);
    作用:实现在几何图形底部画阴影的效果,可以想象成阳光从上往下斜着照射;
    radius:阴影模糊层的高度,为0表示没有阴影层,值越大阴影区域越大但也更模糊,人视觉上的效果相当于在dx/dy偏移量的基础上再拼上个radius这么大的模糊层,一般你不想让它看起来很模糊的话,传一个比较小的值,比如传1就好了,注意当这个值为0时,即使有dx/dy也不会有阴影画出来;
    dx,dy:这2个值是阴影图形相对于原图形的偏移,可以想象成阳光从上往下以不同的角度照过来,比如都为正数30,相当于往右下角平移30个像素;
    shadowColor:阴影的颜色,如果这个颜色值不带透明度的话,那透明度会使用Paint的(如果它有的话),否则用阴影自己的透明度;

总结

这些API其实用起来也没多少难度,大家在不确定的时候最好能够动手写个小demo跑起来看看效果,一般也都能搞明白作用。

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

推荐阅读更多精彩内容