请尊重原创,转载请注明出处【tianyl】的博客
前言
在了解了Paint和Canvas之后,接下来就来说是Path,我是比较喜欢称Paint、Canvas和Path为自定义绘制View时的三剑客,主要是它们在绘制我们想要View时,能起到非常重要的作用。
接下来,就说说Path的一些用法
1 Path的基础用法
对于Path的基础操作,我比较喜欢归纳为点操作、线操作和图形操作
1.1 点操作
点操作的api主要有moveTo、rMoveTo,其实这两个方法的作用比较类似,只有细微的不同
- moveTo (x,y):移动下一个操作的起始点到坐标点(x,y)
例如:对于一个Path,它的默认起始点是(0,0),即屏幕左上角,当使用了moveTo(100,100),后,它的起始点就变成了坐标点(100,100)的位置
- rMoveTo(x,y):移动下一个操作的起始点到当前点的相对位置的坐标(x,y),这个r就是relative的首字母,它表示相对位置
例如:对于一个Path,它的默认起始点是(0,0),即屏幕左上角,当使用了moveTo(100,100),后,它的起始点就变成了坐标点(100,100)的位置,这个时候,如果我们再使用moveTo(100,100),就没有任何效果,因为我们的起始点已经在坐标点(100,100)上了,如果用rMoveTo(100,100),那么起始点就会变成(200,200),这就是rMoveTo的用途了
1.2 线操作
说完了点操作,那么线操作也就比较简单了,线操作的api也是两个lineTo、rLineTo
lineTo (x,y):从起始点(默认是坐标原点(0,0))到坐标点(x,y)绘制一条线
rLineTo(x,y):从起始点(默认是坐标原点(0,0))到相对坐标点(x,y)绘制一条线,这个相对坐标也是当前的起始点,比如当前起始点是坐标点(100,100),那么实际的结束点就是坐标点(100+x,100+y)
除此之外,还有setLastPoint和close两个api
- setLastPoint(dx, dy):改变之前操作的终点的位置
例如:
//初始化Path
Path path = new Path();
//画从(0,0)到(400,400)之间的直线
path.lineTo(400, 400);
//新加的setLastPoint
path.setLastPoint(100, 800);
path.lineTo(400, 800);
canvas.drawPath(path, mPaint);
这段代码等价于
//初始化Path
Path path = new Path();
//画从(0,0)到(100,400)之间的直线
path.lineTo(100, 800);
path.lineTo(400, 800);
canvas.drawPath(path, mPaint);
关于对api setLastPoint的解释,我查了一下资料,网上说moveTo影响的是后面操作的起点位置。不会影响之前的操作;而 setLastPoint改变前一步操作最后一个点的位置,不仅影响前一步操作,同一时候也会影响后一步操作。其实我感觉因为它可以直接被之前的lineTo取代,即直接改lineTo的传递的参数即可,所以这个方法我用得并不多
还有一个方法
- clost():封闭当前的绘制路径,这个api很简单,也算比较常用
1.3 图形操作
接下来就是Path的图形操作了,也是我们经常用来进行图形绘制的api,它们分别是
绘制圆:addCircle
绘制椭圆:addOval
绘制矩形:addRect
绘制圆角矩形:addRoundRect
对于这些api,除了它们各自特定的参数之外,还会有一个相同的参数Direction
对于参数Direction,它有两个选项
Direction.CW:按照逆时针方向绘制
Direction.CCW:按照顺时针方向绘制
对于这个参数,在大多数情况下,我们使用顺时针绘制和逆时针绘制都不会影响最终结果,只是在少数情况下,比如如果使用了setLastPoint这个方法,那么就会因为绘制方向的不同得到不同的结果,或者我们需要根据绘制的方向来自定义一些动画,或者使用绘制的路径,这时也需要考虑我们绘制的方向造成的影像。
2 贝塞尔曲线
说完了Path的基础api,再说说Path稍微难一点的用法,这也是Android中实现一些比较炫酷特效的方法——贝塞尔曲线。
说到贝塞尔曲线,就先简单的介绍一下什么是贝塞尔曲线
2.1 介绍
关于贝塞尔曲线的介绍,这里参考维基百科
线性曲线
线性贝塞尔曲线函数中的t会经过由P0至P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线
二次曲线
为建构二次贝塞尔曲线,可以中介点Q0和Q1作为由0至1的t:
由P0至P1的连续点Q0,描述一条线性贝塞尔曲线。
由P1至P2的连续点Q1,描述一条线性贝塞尔曲线。
由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线。
高阶曲线
为建构高阶曲线,便需要相应更多的中介点。对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构
2.2 Android 中的贝塞尔曲线
介绍了贝塞尔曲线的基础知识,接下来说说Android中的贝塞尔曲线,Android系统已经封装好了二次贝塞尔曲线和三次贝塞尔曲线的api,我们可以直接使用
二次贝塞尔曲线:quadTo()
三次贝塞尔曲线:cubicTo()
这两个api就是Android系统提供给我们使用的api,当然,如果想要使用更高阶的贝塞尔曲线,那么就需要自己去实现了(一般情况下,这两个api就已经够用了),接下来我就通过一个简单水波纹的例子,说明贝塞尔曲线在Android中的应用
2.2.1 通过贝塞尔曲线实现水波纹效果
首先,在写代码之前,我们要先确定如何通过一个贝塞尔曲线绘制出一个水波纹,首先,我们需要绘制出一个波,然后我们对这个波进行移动,那么就是我们想要的动画效果了
如图所示
这是我们实现水波纹的基本思路
首先我们移动到波的起始点(x,y)
假设一个波的完整长度是waveLength,波的高度是waveHeight
首先从波的起始点(x,y),控制点为1,绘制贝塞尔曲线到中间点(x1,y1),然后在使用控制点2,从中间点绘制贝塞尔曲线到结束点(x2,y2)
假设这个波的一个规则的正弦波,那么起始点的坐标为(0,height/2),控制点1的坐标为(waveLength/4,0),中间点的坐标为(waveHeight/2,height/2),控制点2的坐标为(3*waveLength/4,height),结束点的坐标为(waveLength,height/2)
封闭绘制路径
具体代码如下
Path path = new Path();
//步骤1
path.moveTo(0, height/2);
//步骤3 注意这里使用的是rQuadTo,也就是相对当前的位置
path.rQuadTo(waveLength / 4, -height/2, waveLength / 2,0);
path.rQuadTo(waveLength / 4, height/2, waveLength / 2, 0);
//步骤4
path.lineTo(mWidth, mHeight);
path.lineTo(0, mHeight);
path.close();
canvas.drawPath(path, mPaint);
具体效果如下(为了效果突出,所以设置的波高较大))
- 完成了静态的波,接下来就只需要将这个波进行平移即可,之前我们波的起始点是(x,y),我们通过不断的平移这个起始点,就可以实现动画的效果,当然为了避免边界的空白,我们一般可以从view外的开始绘制,然后超出view,这样就通过一个贝塞尔曲线实现了水波纹效果
//这里移动到-waveLength是为了超出屏幕外,避免动画的时候出现空白,offsetX就是波的偏移量,只要不断的修改这个偏移量,就能实现动画效果
path.moveTo(-waveLength + offsetX, mHeight / 2);
//绘制到超出屏幕即可
for (int i = -waveLength; i < getWidth() + waveLength; i += waveLength) {
path.rQuadTo(waveLength / 4, -height/2, waveLength / 2,0);
path.rQuadTo(waveLength / 4, height/2, waveLength / 2, 0);
}
下面是添加了动画效果后的完整draw代码
private void drawWave(Canvas canvas) {
Path path = new Path();
path.moveTo(-mWaveWidth + offsetX, mHeight / 2);
//注意这里使用的是rQuadTo,也就是相对当前的位置
for (int i = -mWaveWidth; i < getWidth() + mWaveWidth; i += mWaveWidth) {
path.rQuadTo(mWaveWidth / 4, -mHeight / 2, mWaveWidth / 2, 0);
path.rQuadTo(mWaveWidth / 4, mHeight / 2, mWaveWidth / 2, 0);
}
path.lineTo(mWaveWidth, mHeight);
path.lineTo(0, mHeight);
path.close();
canvas.drawPath(path, mPaint);
}
效果如下