Android自定义 View(持续更新)

来自 http://hencoder.com/

一,绘制
  • 自定义绘制的方式是重写绘制方法,其中最常用的是 onDraw()
  • 绘制的关键是 Canvas 的使用 
  • Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
  • Canvas 的辅助类方法:范围裁切canvas.clipXXX()和几何变换canvas.setMatrix();
  • 可以使用不同的绘制方法来控制遮盖关系
1.paint
Paint 类的几个最常用的方法。具体是: 
  • Paint.setStyle(Style style) 设置绘制模式,Style 具体来说有三种: FILL, STROKE 和 FILL_AND_STROKE 。FILL 是填充模式,STROKE 是画线模式(即勾边模式),FILL_AND_STROKE 是两种模式一并使用:既画线又填充。它的默认值是 FILL,填充模式。
paint. setStyle ( Paint . Style . STROKE ); // 画线模式,
canvas. drawCircle (300, 300, 200, paint) 空心圆
  • Paint.setColor(int color) 设置颜色
  • Paint.setStrokeWidth(float width) 设置线条宽度
  • Paint.setTextSize(float textSize) 设置文字大小
  • Paint.setAntiAlias(boolean aa) 设置抗锯齿开关
  • Paint.setStrokeCap(cap) 可以设置点的形状 但这个方法并不是专门用来设置点的形状的,而是一个设置线条端点形状的方法。端点有圆头 (ROUND)、平头 (BUTT) 和方头 (SQUARE) 三种
2.Canvas
Canvas类的几个最常用的方法。具体是:
  • drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形left, top, right, bottom 是矩形四条边的坐标。另外,它还有两个重载方法 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) 来设置
  • 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(),而是配合几何变换,后面会讲到)。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);

  • drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint 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(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形
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); // 绘制不封口的弧形

到此为止,以上就是 Canvas 所有的简单图形的绘制。除了简单图形的绘制, Canvas 还可以使用 drawPath(Path path) 来绘制自定义图形。
  • drawPath(Path path, Paint paint) 画自定义图形,这个方法比较复杂
path 的类型是 Path ,使用方法大概像下面这样:
Path path = new Path(); // 初始化 Path 对象
{ // 使用 path 对图形进行描述(这段描述代码不必看懂) path.addArc(200, 200, 400, 400, -225, 225); path.arcTo(400, 200, 600, 400, -180, 225, false); path.lineTo(400, 542); }
canvas.drawPath(path, paint); // 绘制出 path 描述的图形(心形),大功告成

Path 可以描述直线、二次曲线、三次曲线、圆、椭圆、弧形、矩形、圆角矩形。把这些图形结合起来,就可以描述出很多复杂的图形。
Path 有两类方法,一类是直接描述路径的,另一类是辅助的设置或计算。
Path 方法第一类:直接描述路径。
第一组:  addXxx()  ——添加子图形
addCircle(float x, float y, float radius, Direction dir) 添加圆
x, y, radius 这三个参数是圆的基本信息,最后一个参数 dir 是画圆的路径的方向。
在用 addCircle() 为 Path 中新增一个圆之后,调用 canvas.drawPath(path, paint) ,就能画一个圆出来。就像这样:
path.addCircle(300, 300, 200, Path.Direction.CW);canvas.drawPath(path, paint);
可以看出,path.AddCircle(x, y, radius, dir) + canvas.drawPath(path, paint) 这种写法,和直接使用 canvas.drawCircle(x, y, radius, paint) 的效果是一样的,区别只是它的写法更复杂。所以如果只画一个圆,没必要用 Path,直接用 drawCircle() 就行了。drawPath() 一般是在绘制组合图形时才会用到的。
其他的 Path.add-() 方法和这类似,例如:
addOval(float left, float top, float right, float bottom, Direction dir) / addOval(RectF oval, Direction dir) 添加椭圆
addRect(float left, float top, float right, float bottom, Direction dir) / addRect(RectF rect, Direction dir) 添加矩形
addRoundRect(RectF rect, float rx, float ry, Direction dir) / addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Direction dir) / addRoundRect(RectF rect, float[] radii, Direction dir) / addRoundRect(float left, float top, float right, float bottom, float[] radii, Direction dir) 添加圆角矩形
addPath(Path path) 添加另一个 Path

第二组: xxxTo()  ——画线(直线或曲线)
这一组和第一组 addXxx() 方法的区别在于,第一组是添加的完整封闭图形(除了 addPath() ),而这一组添加的只是一条线。
lineTo(float x, float y) / rLineTo(float x, float y) 画直线
从当前位置向目标位置画一条直线, x 和 y 是目标位置的坐标。这两个方法的区别是,lineTo(x, y) 的参数是绝对坐标,而 rLineTo(x, y) 的参数是相对当前位置的相对坐标 (前缀 r 指的就是 relatively 「相对地」)。
当前位置 :所谓当前位置,即最后一次调用画  Path  的方法的终点位置。初始值为原点 (0, 0)。
paint.setStyle(Style.STROKE); path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线 path.rLineTo(100, 0); // 由当前位置 (100, 100) 向正右方 100 像素的位置画一条直线

quadTo(float x1, float y1, float x2, float y2) / rQuadTo(float dx1, float dy1, float dx2, float dy2) 画二次贝塞尔曲线
这条二次贝塞尔曲线的起点就是当前位置,而参数中的 x1, y1 和 x2, y2 则分别是控制点和终点的坐标。和 rLineTo(x, y) 同理,rQuadTo(dx1, dy1, dx2, dy2) 的参数也是相对坐标
贝塞尔曲线 :贝塞尔曲线是几何上的一种曲线。它通过起点、控制点和终点来描述一条曲线,主要用于计算机图形学

cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) / rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线
和上面这个 quadTo() rQuadTo() 的二次贝塞尔曲线同理,cubicTo() 和 rCubicTo() 是三次贝塞尔曲线,不再解释。

moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置
不论是直线还是贝塞尔曲线,都是以当前位置作为起点,而不能指定起点。但你可以通过 moveTo(x, y) 或 rMoveTo() 来改变当前位置,从而间接地设置这些方法的起点。
paint.setStyle(Style.STROKE); path.lineTo(100, 100); // 画斜线 path.moveTo(200, 100); // 我移~~ path.lineTo(200, 0); // 画竖线
moveTo(x, y) 虽然不添加图形,但它会设置图形的起点,所以它是非常重要的一个辅助方法。

另外,第二组还有两个特殊的方法: arcTo() 和 addArc()。它们也是用来画线的,但并不使用当前位置作为弧线的起点。

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) 画弧形
这个方法和 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 ); // 直接连线连到弧形起点(有痕迹)

addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle) / addArc(RectF oval, float startAngle, float sweepAngle)
又是一个弧形的方法。一个叫 arcTo ,一个叫 addArc(),都是弧形,区别在哪里?其实很简单: addArc()只是一个直接使用了 forceMoveTo = true 的简化版 arcTo() 。
paint. setStyle ( Style . STROKE ); path. lineTo (100, 100); path. addArc (100, 100, 300, 300, -90, 90);

close() 封闭当前子图形
它的作用是把当前的子图形封闭,即由当前位置向当前子图形的起点绘制一条直线。
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)

close() 和 lineTo(起点坐标) 是完全等价的。

「子图形」:官方文档里叫做  contour  。但由于在这个场景下我找不到这个词合适的中文翻译(直译的话叫做「轮廓」),所以我换了个便于中国人理解的词:「子图形」。前面说到,第一组方法是「添加子图形」,所谓「子图形」,指的就是一次不间断的连线。一个  Path  可以包含多个子图形。当使用第一组方法,即  addCircle()   addRect()  等方法的时候,每一次方法调用都是新增了一个独立的子图形;而如果使用第二组方法,即  lineTo()   arcTo()  等方法的时候,则是每一次断线(即每一次「抬笔」),都标志着一个子图形的结束,以及一个新的子图形的开始
另外,不是所有的子图形都需要使用  close()  来封闭。当 需要填充图形时(即  Paint.Style  为  FILL 或  FILL_AND_STROKE Path  会自动封闭子图形。
java paint.setStyle(Style.FILL); path.moveTo(100, 100); path.lineTo(200, 100); path.lineTo(150, 150); // 这里只绘制了两条边,但由于 Style 是 FILL ,所以绘制时会自动封口




以上就是  Path  的第一类方法:直接描述路径的。

Path 方法第二类:辅助的设置或计算
这类方法的使用场景比较少,这里就不多讲了,只讲其中一个方法: setFillType(FillType fillType)。
前面在说  dir  参数的时候提到,  Path.setFillType(fillType)  是用来设置图形自相交时的填充算法的:
方法中填入不同的 FillType 值,就会有不同的填充效果。FillType 的取值有四个:
  • EVEN_ODD
  • WINDING (默认值)
  • INVERSE_EVEN_ODD
  • INVERSE_WINDING
其中后面的两个带有 INVERSE_ 前缀的,只是前两个的反色版本,所以只要把前两个,即 EVEN_ODD 和 WINDING,搞明白就可以了。
EVEN_ODD 和 WINDING 的原理有点复杂,直接讲出来的话信息量太大,所以我先给一个简单粗暴版的总结,你感受一下: WINDING 是「全填充」,而 EVEN_ODD 是「交叉填充」:

并不完全准确。

EVEN_ODD 和 WINDING 的原理
EVEN_ODD
即 even-odd rule (奇偶原则):对于平面中的任意一点,向任意方向射出一条射线,这条射线和图形相交的次数(相交才算,相切不算哦)如果是奇数,则这个点被认为在图形内部,是要被涂色的区域;如果是偶数,则这个点被认为在图形外部,是不被涂色的区域。还以左右相交的双圆为例:

射线的方向无所谓,同一个点射向任何方向的射线,结果都是一样的,不信你可以试试。
从上图可以看出,射线每穿过图形中的一条线,内外状态就发生一次切换,这就是为什么 EVEN_ODD 是一个「交叉填充」的模式。
WINDING
即 non-zero winding rule (非零环绕数原则):首先,它需要你图形中的所有线条都是有绘制方向的:

然后,同样是从平面中的点向任意方向射出一条射线,但计算规则不一样:以 0 为初始值,对于射线和图形的所有交点,遇到每个顺时针的交点(图形从射线的左边向右穿过)把结果加 1,遇到每个逆时针的交点(图形从射线的右边向左穿过)把结果减 1,最终把所有的交点都算上,得到的结果如果不是 0,则认为这个点在图形内部,是要被涂色的区域;如果是 0,则认为这个点在图形外部,是不被涂色的区域。

和 EVEN_ODD 相同,射线的方向并不影响结果。
所以,我前面的那个「简单粗暴」的总结,对于 WINDING 来说并不完全正确:如果你所有的图形都用相同的方向来绘制,那么 WINDING 确实是一个「全填充」的规则;但如果使用不同的方向来绘制图形,结果就不一样了。
图形的方向:对于添加子图形类方法(如 Path.addCircle() Path.addRect())的方向,由方法的 dir 参数来控制,这个在前面已经讲过了;而对于画线类的方法(如 Path.lineTo() Path.arcTo())就更简单了,线的方向就是图形的方向。
所以,完整版的 EVEN_ODD 和 WINDING 的效果应该是这样的:

而 INVERSE_EVEN_ODD 和 INVERSE_WINDING ,只是把这两种效果进行反转而已,你懂了 EVEN_ODD 和 WINDING ,自然也就懂 INVERSE_EVEN_ODD 和 INVERSE_WINDING 了,我就不讲了。

好,花了好长的篇幅来讲 drawPath(path) 和 Path,终于讲完了。同时, Canvas 对图形的绘制就也讲完了。图形简单时,使用 drawCircle() drawRect() 等方法来直接绘制;图形复杂时,使用 drawPath() 来绘制自定义图形。

除此之外, Canvas 还可以绘制 Bitmap 和文字。
drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 画 Bitmap
绘制  Bitmap  对象,也就是把这个  Bitmap  中的像素内容贴过来。其中  left  和  top  是要把  bitmap  绘制到的位置坐标。它的使用非常简单。
drawBitmap (bitmap, 200, 100, paint);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值