Canvas
类下的所有 draw-
打头的方法,例如 drawCircle()
drawBitmap()
。Paint
类的几个最常用的方法。具体是: Paint.setStyle(Style style)
设置绘制模式Paint.setColor(int color)
设置颜色Paint.setStrokeWidth(float width)
设置线条宽度Paint.setTextSize(float textSize)
设置文字大小Paint.setAntiAlias(boolean aa)
设置抗锯齿开关drawColor(Color.parse("#88880000"); // 半透明红色
前两个参数 centerX
centerY
是圆心的坐标,第三个参数 radius
是圆的半径,单位都是像素,它们共同构成了这个圆的基本信息(即用这几个信息可以构建出一个确定的圆);第四个参数 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(Color.RED); // 设置为红色
canvas.drawCircle(300, 300, 200, paint);
使用 paint.setStrokeWidth(float width)
来设置线条的宽度
你也可以使用 Paint.setAntiAlias(boolean aa)
来动态开关抗锯齿。
EVEN_ODD
和 WINDING
的原理有点复杂,直接讲出来的话信息量太大,所以我先给一个简单粗暴版的总结,你感受一下: WINDING
是「全填充」,而 EVEN_ODD
是「交叉填充」:
即 even-odd rule (奇偶原则):对于平面中的任意一点,向任意方向射出一条射线,这条射线和图形相交的次数(相交才算,相切不算哦)如果是奇数,则这个点被认为在图形内部,是要被涂色的区域;如果是偶数,则这个点被认为在图形外部,是不被涂色的区域。还以左右相交的双圆为例:
射线的方向无所谓,同一个点射向任何方向的射线,结果都是一样的,不信你可以试试。
从上图可以看出,射线每穿过图形中的一条线,内外状态就发生一次切换,这就是为什么 EVEN_ODD
是一个「交叉填充」的模式。
即 non-zero winding rule (非零环绕数原则):首先,它需要你图形中的所有线条都是有绘制方向的:
然后,同样是从平面中的点向任意方向射出一条射线,但计算规则不一样:以 0 为初始值,对于射线和图形的所有交点,遇到每个顺时针的交点(图形从射线的左边向右穿过)把结果加 1,遇到每个逆时针的交点(图形从射线的右边向左穿过)把结果减 1,最终把所有的交点都算上,得到的结果如果不是 0,则认为这个点在图形内部,是要被涂色的区域;如果是 0,则认为这个点在图形外部,是不被涂色的区域。
绘制 Bitmap
对象,也就是把这个 Bitmap
中的像素内容贴过来。其中 left
和 top
是要把 bitmap
绘制到的位置坐标。它的使用非常简单。
drawBitmap(bitmap, 200, 100, paint);
left
, top
, right
, bottom
是矩形四条边的坐标。
paint.setStyle(Style.FILL);
canvas.drawRect(100, 100, 500, 500, paint);
paint.setStyle(Style.STROKE);
canvas.drawRect(700, 100, 1100, 500, 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);
同样是画点,它和 drawPoint()
的区别是可以画多个点。pts
这个数组是点的坐标,每两个成一对;offset
表示跳过数组的前几个数再开始记坐标;count
表示一共要绘制几个点。说这么多你可能越读越晕,你还是自己试试吧,这是个看着复杂用着简单的方法。
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(20); // 线条宽度为 20 像素
canvas.drawCircle(300, 300, 200, paint);
只能绘制横着的或者竖着的椭圆,不能绘制斜的(斜的倒是也可以,但不是直接使用 drawOval()
,而是配合几何变换,后面会讲到)。left
, top
, right
, bottom
是这个椭圆的左、上、右、下四个边界点的坐标。
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
来绘制椭圆。
startX
, startY
, stopX
, stopY
分别是线的起点和终点坐标。
canvas.drawLine(200, 200, 800, 500, paint);
由于直线不是封闭图形,所以
setStyle(style)
对直线没有影响。
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);
left
, top
, right
, bottom
是四条边的坐标,rx
和 ry
是圆角的横向半径和纵向半径。
canvas.drawRoundRect(100, 100, 500, 300, 50, 50, paint);
另外,它还有一个重载方法 drawRoundRect(RectF rect, float rx, float ry, Paint paint)
,让你可以直接填写 RectF
来绘制圆角矩形。
drawArc()
是使用一个椭圆来描述弧形的。left
, top
, right
, bottom
描述的是这个弧形所在的椭圆;startAngle
是弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度),sweepAngle
是弧形划过的角度;useCenter
表示是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形。
paint.setStyle(Paint.Style.FILL); // 填充模式
canvas.drawArc(200, 100, 800, 500, -110, 100, true, paint); // 绘制扇形
canvas.drawArc(200, 100, 800, 500, 20, 140, false, paint); // 绘制弧形
paint.setStyle(Paint.Style.STROKE); // 画线模式
canvas.drawArc(200, 100, 800, 500, 180, 60, false, paint); // 绘制不封口的弧形
在用 addCircle()
为 Path
中新增一个圆之后,调用 canvas.drawPath(path, paint)
,就能画一个圆出来。就像这样:
path.addCircle(300, 300, 200, Path.Direction.CW);
...
canvas.drawPath(path, paint);
从当前位置向目标位置画一条直线, x
和 y
是目标位置的坐标。这两个方法的区别是,lineTo(x, y)
的参数是绝对坐标,而 rLineTo(x, y)
的参数是相对当前位置的相对坐标 (前缀 r
指的就是 relatively
「相对地」)。
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(RectF oval, float startAngle, float sweepAngle) 画弧形当前位置:所谓当前位置,即最后一次调用画
Path
的方法的终点位置。初始值为原点 (0, 0)。paint.setStyle(Style.STROKE);
path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线
path.rLineTo(100, 0); // 由当前位置 (100, 100) 向正右方 100 像素的位置画一条直线
这个方法和 Canvas.drawArc()
比起来,少了一个参数 useCenter
,而多了一个参数 forceMoveTo
。
少了 useCenter
,是因为 arcTo()
只用来画弧形而不画扇形,所以不再需要 useCenter
参数;而多出来的这个 forceMoveTo
参数的意思是,绘制是要「抬一下笔移动过去」,还是「直接拖着笔过去」,区别在于是否留下移动的痕迹。
paint.setStyle(Style.STROKE);
path.lineTo(100, 100);
path.arcTo(100, 100, 300, 300, -90, 90, true); // 强制移动到弧形起点(无痕迹)
paint.setStyle(Style.STROKE);
path.lineTo(100, 100);
path.arcTo(100, 100, 300, 300, -90, 90, false); // 直接连线连到弧形起点(有痕迹)
它的作用是把当前的子图形封闭,即由当前位置向当前子图形的起点绘制一条直线。
paint.setStyle(Style.STROKE);
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150); // 子图形未封闭
paint.setStyle(Style.STROKE);
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150);
path.close(); // 使用 close() 封闭子图形。等价于 path.lineTo(100, 100)
需要填充图形时(即 Paint.Style
为 FILL
或 FILL_AND_STROKE
),Path
会自动封闭子图形。
paint.setStyle(Style.FILL);
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150); // 这里只绘制了两条边,但由于 Style 是 FILL ,所以绘制时会自动封口