自定义绘制概述

二话不说,我反手就是一个视频:

 

首先总结一下视频中的关键点:

  • 自定义绘制的方式是重写绘制方法,其中最常用的是 onDraw()
  • 绘制的关键是 Canvas 的使用 
    • Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
    • Canvas 的辅助类方法:范围裁切和几何变换
  • 可以使用不同的绘制方法来控制遮盖关系

概念已经在视频里全部讲出来了,知识点并不多,但你可能也看出来了,我讲得并不细。这是因为知识点虽然不多,但细节还是很多的,仅仅靠一节分享不可能讲完。我按照顺序把这些知识分成了 4 个级别,拆成几节来讲,你按照这 4 个级别的顺序学习下来,就能够平滑地逐步进阶。

自定义绘制知识的四个级别

  1. Canvas 的 drawXXX() 系列方法及 Paint 最常见的使用

    Canvas.drawXXX() 是自定义绘制最基本的操作。掌握了这些方法,你才知道怎么绘制内容,例如怎么画圆、怎么画方、怎么画图像和文字。组合绘制这些内容,再配合上 Paint的一些常见方法来对绘制内容的颜色和风格进行简单的配置,就能够应付大部分的绘制需求了。

    今天这篇分享我要讲的就是这些内容。也就是说,你在看完这篇文章并做完练习之后,上面这几幅图你就会绘制出来了。从今以后,你也很少再需要假装一本正经地对设计师说「不行这个图技术上实现不了」,也不用心惊胆战得等待设计师的那句「那 iOS 怎么可以」了。

  2. Paint 的完全攻略

    Paint 可以做的事,不只是设置颜色,也不只是我在视频里讲的实心空心、线条粗细、有没有阴影,它可以做的风格设置真的是非常多、非常细。例如:

    拐角要什么形状?

    开不开双线性过滤?

    加不加特效?

    可以调节的非常多,我就不一一列举了。当你掌握到这个级别,就真的不会有什么东西会是 iOS 能做到但你做不到的了。就算设计师再设计出了很难做的东西,做不出来的也不再会是你们 Android 组了。

  3. Canvas 对绘制的辅助——范围裁切和几何变换。

    范围裁切:

    几何变换:

    大多数时候,它们并不会被用到,但一旦用到,通常都是很炫酷的效果。范围裁切和几何变换都是用于辅助的,它们本身并不酷,让它们变酷的是设计师们的想象力与创造力。而你要做的,是把他们的想象力与创造力变成现实。

  4. 使用不同的绘制方法来控制绘制顺序

    控制绘制顺序解决的并不是「做不到」的问题,而是性能问题。同样的一种效果,你不用绘制顺序的控制往往也能做到,但需要用多个 View 甚至是多层 View 才能拼凑出来,因此代价是 UI 的性能;而使用绘制顺序的控制的话,一个 View 就全部搞定了。

自定义绘制的知识,大概就分为上面这四个级别。在你把这四个级别依次掌握了之后,你就是一个自定义绘制的高手了。它们具体的细节,我将分成几篇来讲。今天这篇就是第一篇:Canvas.drawXXX() 系列方法及 Paint 最基本的使用。我要正式开始喽?

一切的开始:onDraw()

自定义绘制的上手非常容易:提前创建好 Paint 对象,重写 onDraw(),把绘制代码写在onDraw() 里面,就是自定义绘制最基本的实现。大概就像这样:

Paint paint = new Paint();

@Override
protected void onDraw(Canvas canvas) {  
    super.onDraw(canvas);

    // 绘制一个圆
    canvas.drawCircle(300, 300, 200, paint);
}
 
 
 
 
 
 
 

就这么简单。所以关于 onDraw() 其实没什么好说的,一个很普通的方法重写,唯一需要注意的是别漏写了 super.onDraw()

Canvas.drawXXX() 和 Paint 基础

drawXXX() 系列方法和 Paint 的基础掌握了,就能够应付简单的绘制需求。它们主要包括:

  1. Canvas 类下的所有 draw- 打头的方法,例如 drawCircle() drawBitmap()
  2. Paint 类的几个最常用的方法。具体是: 
    • Paint.setStyle(Style style) 设置绘制模式
    • Paint.setColor(int color) 设置颜色
    • Paint.setStrokeWidth(float width) 设置线条宽度
    • Paint.setTextSize(float textSize) 设置文字大小
    • Paint.setAntiAlias(boolean aa) 设置抗锯齿开关

对于比较习惯于自学的人(我就是这样的人),你看到这里就已经可以去 Google 的官方文档里,打开 Canvas 和 Paint 的页面,把上面的这两类方法学习一下,然后今天的内容就算结束了。当然,这篇文章也可以关掉了。

下面的内容就是展开讲解上面的这两类方法。

Canvas.drawColor(@ColorInt int color) 颜色填充

这是最基本的 drawXXX() 方法:在整个绘制区域统一涂上指定的颜色。

例如 drawColor(Color.BLACK) 会把整个区域染成纯黑色,覆盖掉原有内容;drawColor(Color.parse("#88880000") 会在原有的绘制效果上加一层半透明的红色遮罩。

drawColor(Color.BLACK);  // 纯黑  
 
 
 
 
 
 
 

drawColor(Color.parse("#88880000"); // 半透明红色  
 
 
 
 
 
 
 

类似的方法还有 drawRGB(int r, int g, int b) 和 drawARGB(int a, int r, int g, int b) ,它们和 drawColor(color) 只是使用方式不同,作用都是一样的。

canvas.drawRGB(100, 200, 100);  
canvas.drawARGB(100, 100, 200, 100);  
 
 
 
 
 
 
 

这类颜色填充方法一般用于在绘制之前设置底色,或者在绘制之后为界面设置半透明蒙版。

drawCircle(float centerX, float centerY, float radius, Paint paint) 画圆

前两个参数 centerX centerY 是圆心的坐标,第三个参数 radius 是圆的半径,单位都是像素,它们共同构成了这个圆的基本信息(即用这几个信息可以构建出一个确定的圆);第四个参数 paint 我在视频里面已经说过了,它提供基本信息之外的所有风格信息,例如颜色、线条粗细、阴影等。

canvas.drawCircle(300, 300, 200, paint);  
 
 
 
 
 
 
 

那位说:「你等会儿!先别往后讲,你刚才说圆心的坐标,我想问坐标系在哪儿呢?没坐标系你跟我聊什么坐标啊。」

我想说:问得好(强行插入剧情)。在 Android 里,每个 View 都有一个自己的坐标系,彼此之间是不影响的。这个坐标系的原点是 View 左上角的那个点;水平方向是 x 轴,右正左负;竖直方向是 y 轴,下正上负(注意,是下正上负,不是上正下负,和上学时候学的坐标系方向不一样)。也就是下面这个样子。

所以一个 View 的坐标 (x, y) 处,指的就是相对它的左上角那个点的水平方向 x 像素、竖直方向 y 像素的点。例如,(300, 300) 指的就是左上角的点向右 300 、向下 300 的位置; (100, -50) 指的就是左上角的点向右 100 、向上 50 的位置。

也就是说, canvas.drawCircle(300, 300, 200, paint) 这行代码绘制出的圆,在 View 中的位置和尺寸应该是这样的:

圆心坐标和半径,这些都是圆的基本信息,也是它的独有信息。什么叫独有信息?就是只有它有,别人没有的信息。你画圆有圆心坐标和半径,画方有吗?画椭圆有吗?这就叫独有信息。独有信息都是直接作为参数写进 drawXXX() 方法里的(比如drawCircle(centerX, centerY, radius, paint) 的前三个参数)。

而除此之外,其他的都是公有信息。比如图形的颜色、空心实心这些,你不管是画圆还是画方都有可能用到的,这些信息则是统一放在 paint 参数里的。

插播一: Paint.setColor(int color)

例如,你要画一个红色的圆,并不是写成 canvas.drawCircle(300, 300, 200, RED, paint) 这样,而是像下面这样:

paint.setColor(Color.RED); // 设置为红色  
canvas.drawCircle(300, 300, 200, paint);  
 
 
 
 
 
 
 

Paint.setColor(int color) 是 Paint 最常用的方法之一,用来设置绘制内容的颜色。你不止可以用它画红色的圆,也可以用它来画红色的矩形、红色的五角星、红色的文字。

插播二: Paint.setStyle(Paint.Style style)

而如果你想画的不是实心圆,而是空心圆(或者叫环形),也可以使用paint.setStyle(Paint.Style.STROKE) 来把绘制模式改为画线模式。

paint.setStyle(Paint.Style.STROKE); // Style 修改为画线模式  
canvas.drawCircle(300, 300, 200, paint);  
 
 
 
 
 
 
 

setStyle(Style style) 这个方法设置的是绘制的 Style 。Style 具体来说有三种: FILL,STROKE 和 FILL_AND_STROKE 。FILL 是填充模式,STROKE 是画线模式(即勾边模式),FILL_AND_STROKE 是两种模式一并使用:既画线又填充。它的默认值是 FILL,填充模式。

插播三: Paint.setStrokeWidth(float width)

在 STROKE 和 FILL_AND_STROKE 下,还可以使用 paint.setStrokeWidth(float width) 来设置线条的宽度:

paint.setStyle(Paint.Style.STROKE);  
paint.setStrokeWidth(20); // 线条宽度为 20 像素  
canvas.drawCircle(300, 300, 200, paint);  
 
 
 
 
 
 
 

插播四: 抗锯齿

在绘制的时候,往往需要开启抗锯齿来让图形和文字的边缘更加平滑。开启抗锯齿很简单,只要在 new Paint() 的时候加上一个 ANTI_ALIAS_FLAG 参数就行:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);  
 
 
 
 
 
 
 

另外,你也可以使用 Paint.setAntiAlias(boolean aa) 来动态开关抗锯齿。

抗锯齿的效果如下:

可以看出,没有开启抗锯齿的时候,图形会有毛片现象,啊不,毛边现象。所以一定记得要打开抗锯齿哟!

可以跳过的冷知识

好奇的人可能会问:抗锯齿既然这么有用,为什么不默认开启,或者干脆把这个开关取消,自动让所有绘制都开启抗锯齿?

短答案:因为抗锯齿并不一定适合所有场景。

长答案:所谓的毛边或者锯齿,发生的原因并不是很多人所想象的「绘制太粗糙」「像素计算能力不足」;同样,抗锯齿的原理也并不是选择了更精细的算法来算出了更平滑的图形边缘。 
实质上,锯齿现象的发生,只是由于图形分辨率过低,导致人眼察觉出了画面中的像素颗粒而已。换句话说,就算不开启抗锯齿,图形的边缘也已经是最完美的了,而并不是一个粗略计算的粗糙版本。 
那么,为什么抗锯齿开启之后的图形边缘会更加平滑呢?因为抗锯齿的原理是:修改图形边缘处的像素颜色,从而让图形在肉眼看来具有更加平滑的感觉。一图胜千言,上图: 
 
上面这个是把前面那两个圆放大后的局部效果。看到没有?未开启抗锯齿的圆,所有像素都是同样的黑色,而开启了抗锯齿的圆,边缘的颜色被略微改变了。这种改变可以让人眼有边缘平滑的感觉,但从某种角度讲,它也造成了图形的颜色失真。 
所以,抗锯齿好不好?好,大多数情况下它都应该是开启的;但在极少数的某些时候,你还真的需要把它关闭。「某些时候」是什么时候?到你用到的时候自然就知道了。

除了圆,Canvas 还可以绘制一些别的简单图形。它们的使用方法和 drawCircle() 大同小异,我就只对它们的 API 做简单的介绍,不再做详细的讲解。

drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形

lefttoprightbottom 是矩形四条边的坐标。

paint.setStyle(Style.FILL);  
canvas.drawRect(100, 100, 500, 500, paint);

paint.setStyle(Style.STROKE);  
canvas.drawRect(700, 100, 1100, 500, paint);  
 
 
 
 
 
 
 

另外,它还有两个重载方法 drawRect(RectF rect, Paint paint) 和drawRect(Rect rect, Paint paint) ,让你可以直接填写 RectF 或 Rect 对象来绘制矩形。

drawPoint(float x, float y, Paint paint) 画点

x 和 y 是点的坐标。点的大小可以通过 paint.setStrokeWidth(width) 来设置;点的形状可以通过 paint.setStrokeCap(cap) 来设置:ROUND 画出来是圆形的点,SQUARE 或 BUTT 画出来是方形的点。(点还有形状?是的,反正 Google 是这么说的,你要问问 Google 去,我也很懵逼。)

注:Paint.setStrokeCap(cap) 可以设置点的形状,但这个方法并不是专门用来设置点的形状的,而是一个设置线条端点形状的方法。端点有圆头 (ROUND)、平头 (BUTT) 和方头 (SQUARE) 三种,具体会在下一节里面讲。

paint.setStrokeWidth(20);  
paint.setStrokeCap(Paint.Cap.ROUND);  
canvas.drawPoint(50, 50, paint);  
 
 
 
 
 
 
 

paint.setStrokeWidth(20);  
paint.setStrokeCap(Paint.Cap.SQUARE);  
canvas.drawPoint(50, 50, paint);  
 
 
 
 
 
 
 

好像有点像 FILL 模式下的 drawCircle() 和 drawRect() ?事实上确实是这样的,它们和drawPoint() 的绘制效果没有区别。各位在使用的时候按个人习惯和实际场景来吧,哪个方便和顺手用哪个。

drawPoints(float[] pts, int offset, int count, Paint paint) / drawPoints(float[] pts, Paint paint) 画点(批量)

同样是画点,它和 drawPoint() 的区别是可以画多个点。pts 这个数组是点的坐标,每两个成一对;offset 表示跳过数组的前几个数再开始记坐标;count 表示一共要绘制几个点。说这么多你可能越读越晕,你还是自己试试吧,这是个看着复杂用着简单的方法。

float[] points = {0, 0, 50, 50, 50, 100, 100, 50, 100, 100, 150, 50, 150, 100};  
// 绘制四个点:(50, 50) (50, 100) (100, 50) (100, 100)
canvas.drawPoints(points, 2 /* 跳过两个数,即前两个 0 */,  
          8 /* 一共绘制 8 个数(4 个点)*/, paint);
 
 
 
 
 
 
 

drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆

只能绘制横着的或者竖着的椭圆,不能绘制斜的(斜的倒是也可以,但不是直接使用drawOval(),而是配合几何变换,后面会讲到)。lefttoprightbottom 是这个椭圆的左、上、右、下四个边界点的坐标。

paint.setStyle(Style.FILL);  
canvas.drawOval(50, 50, 350, 200, paint);

paint.setStyle(Style.STROKE);  
canvas.drawOval(400, 50, 700, 200, paint);  
 
 
 
 
 
 
 

另外,它还有一个重载方法 drawOval(RectF rect, Paint paint),让你可以直接填写 RectF 来绘制椭圆。

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

startXstartYstopXstopY 分别是线的起点和终点坐标。

canvas.drawLine(200, 200, 800, 500, paint);  
 
 
 
 
 
 
 

由于直线不是封闭图形,所以 setStyle(style) 对直线没有影响。

drawLines(float[] pts, int offset, int count, Paint paint) / drawLines(float[] pts, Paint paint) 画线(批量)

drawLines() 是 drawLine() 的复数版。

float[] points = {20, 20, 120, 20, 70, 20, 70, 120, 20, 120, 120, 120, 150, 20, 250, 20, 150, 20, 150, 120, 250, 20, 250, 120, 150, 120, 250, 120};  
canvas.drawLines(points, paint);  
 
 
 
 
 
 
 

咦,不小心打出两个汉字。——是汉字吧?

drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 画圆角矩形

lefttoprightbottom 是四条边的坐标,rx 和 ry 是圆角的横向半径和纵向半径。

canvas.drawRoundRect(100, 100, 500, 300, 50, 50, paint);  
 
 
 
 
 
 
 

另外,它还有一个重载方法 drawRoundRect(RectF rect, float rx, float ry, Paint paint),让你可以直接填写 RectF 来绘制圆角矩形。

drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形

文章来源: hencoder

原文链接: http://hencoder.com/ui-1-1/