自定义View(Path一)
上一篇内容自定义View(Canvas)
http://www.jianshu.com/p/d25fb10ad34e
Path这东西,这东西太麻烦了,牵扯的东西很多。但是又特别重要,所以一定要写,思路不清晰的地方回头再整理吧。
前面我们已经用canvas的drawXxx()方法绘制了一些简单的基本图形,但这显然远远不能满足我们的需求。比如我们想绘制一些稍微复杂一点(如绘制一个心形 正多边形 五角星等)的图形就没法去绘制,而使用Path不仅能够绘制简单图形,也可以绘制这些比较复杂的图形。另外,根据路径绘制文本和剪裁画布都会用到Path。以下是Path类中常用到的方法:
作用 | 相关方法 | 备注 |
---|---|---|
移动起点 | moveTo | 移动下一次操作的起点位置 |
设置终点 | setLastPoint | 重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同 |
连接直线 | lineTo | 添加上一个点到当前点之间的直线到Path |
闭合路径 | close | 连接第一个点连接到最后一个点,形成一个闭合区域 |
添加内容 | addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo | 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别) |
是否为空 | isEmpty | 判断Path是否为空 |
是否为矩形 | isRect | 判断path是否是一个矩形 |
替换路径 | set | 用新的路径替换到当前路径所有内容 |
偏移路径 | offset | 对当前路径之前的操作进行偏移(不会影响之后的操作) |
贝塞尔曲线 | quadTo, cubicTo | 分别为二次和三次贝塞尔曲线的方法 |
rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | 不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量) |
填充模式 | setFillType, getFillType, isInverseFillType, toggleInverseFillType | 设置,获取,判断和切换填充模式 |
提示方法 | incReserve | 提示Path还有多少个点等待加入(这个方法貌似会让Path优化存储结构) |
布尔操作(API19) | op | 对两个Path进行布尔运算(即取交集、并集等操作) |
计算边界 | computeBounds | 计算Path的边界 |
重置路径 | reset, rewind | 清除Path中的内容 reset不保留内部数据结构,但会保留FillType rewind会保留内部的数据结构,但不保留FillType |
矩阵操作 | transform | 矩阵变换 |
注意:我们可以把path看成是存储操作轨迹的一个容器,我们把希望进行的操作轨迹记录在path中。由Canvas的drawPath()方法把path记录的图形真正绘制到画布上
然后我们简单的把path分一个类:
接下来看看path的一些主要方法
xxxTo()系列
xxxTo家族成员主要有:
public void moveTo(float x, float y)
public void lineTo(float x, float y)
public void quadTo(float x1, float y1, float x2, float y2)
public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
public void arcTo (RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)
moveTo()
将绘制的起点移动到某处,打个比方,比如要写字,moveTo()方法则是把笔尖移动到纸的相应位置,准备开始书写
lineTo()
在上一次操作结束的点与参数坐标点之间连一条线
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
Path path = new Path();// 创建Path
path.moveTo(100,100);//移动绘制点
path.lineTo(200,100);//从(100,100)位置画一条线到(200,100)位置
path.lineTo(200,200);//从(200,100)位置画一条线到(200,200)位置
//path.close();闭合区域
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(15);
canvas.drawPath(path, mPaint);// 绘制Path
}
通过两次lineTo()方法绘制了两条线段:
讲到这里顺带提一手close()这个方法。close()方法没有参数,作用是连接第一个点连接到最后一个点,形成一个闭合区域。去掉
//path.close();
这一句代码的注释,显示如下:
注:如果Paint的填充模式是FILL,即mPaint.setStyle(Paint.Style.FILL);则即使是开放型path,也会自动填充,效果和close()后的效果一致
arcTo()
这是一个画弧线的方法,原理是从一个椭圆上截取一段弧
public void arcTo (RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)
这个方法(还有两个重载方法,大同小异,这里只讲这一个)有四个参数:
RectF oval:用于确定椭圆(重要说明:该RectF 的4个参数必须遵从left,top,right,bottom的顺序,否则无法画出弧线)
float startAngle:起始角度
float sweepAngle:扫过的角度,顺时针方向为正
boolean forceMoveTo:用于确定是否与上一点连接(是否抬起笔)
这样说可能有点抽象,看代码:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
RectF rectF = new RectF(0,0,300,200);//用于绘制椭圆
Path path = new Path();// 创建Path
path.moveTo(0,0);
path.arcTo(rectF,0,60,false);//截取0度到60度之间的弧
mPaint.setColor(Color.RED);
canvas.drawPath(path, mPaint);// 绘制Path
}
因为这里forceMoveTo参数设置为false,所以不抬起笔(连接上一点)。
如果把forceMoveTo参数设置为true(抬起笔):
最后,quadTo()和cubicTo()方法是分别绘制二阶和三阶贝塞尔曲线的方法,我们先了解一下贝塞尔曲线
什么叫贝赛尔曲线?其实很简单,使用三个或多个点来确定的一条曲线,贝塞尔曲线在图形图像学中有相当重要的地位,Path中也提供了一些方法来给我们模拟低阶贝赛尔曲线。
贝塞尔曲线的定义也比较简单,你只需要一个起点、一个终点和至少零个控制点则可定义一个贝赛尔曲线,当控制点为零时,只有起点和终点,此时的曲线说白了就是一条线段,我们称之为一阶贝赛尔曲线。
PS:以下图片和公式均来自维基百科和互联网
一阶贝赛尔曲线:
其公式可概括为:
其中B(t)为时间为t时点的坐标,P0为起点、Pn为终点
贝塞尔曲线于1962年由法国数学家Pierre Bézier第一次研究使用并给出了详细的计算公式,So该曲线也是由其名字命名。Path中给出的quadTo方法属于
二阶贝赛尔曲线:
二阶贝赛尔曲线的一个明显特征是其拥有一个控制点,大家可以这样想想贝赛尔曲线,在一根两端固定橡皮筋上有一块磁铁,现在我们拿另一块磁铁去吸引橡皮筋上的磁铁,因为引力,橡皮筋会随着我们手上磁铁的移动而改变形状,又因为橡皮筋的张力让束缚在橡皮筋上的磁铁不会轻易吸附到我们手上的磁铁,这时橡皮筋的状态就可以看成是一条贝塞尔曲线,而我们手中的磁铁就是一个控制点,通过这个控制点我们“拉扯”橡皮筋的曲度。
二阶贝赛尔曲线的公式为:
同样的,Path中也提供了三阶贝塞尔曲线的方法cubicTo,按照上面我们的推论,三阶应该是有两个控制点才对对吧
三阶贝赛尔曲线:
公式:
高阶贝赛尔曲线在Path中没有对应的方法,对我们来说三阶也足够了,不过大家可以了解下,难得我在墙外找到如此动感的贝赛尔曲线高清无码动图
高阶贝塞尔曲线:
四阶:
五阶:
贝塞尔曲线通用公式:
quadTo()
quadTo方法有四个参数,前两个参数为控制点的坐标,后两个参数为终点坐标,至于起点默认是画布的左上角。这里的屏幕中心就是起点,(x1,y1)就是中点P1,(x2,y2)就是末端点P2。
看一下代码:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
Path path = new Path();// 创建Path
path.quadTo(100,100,200,0);//绘制二阶贝塞尔曲线
mPaint.setColor(Color.RED);
canvas.drawPath(path, mPaint);// 绘制Path
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth(15);//加粗 便于显示
canvas.drawPoint(0,0,mPaint);//绘制起点
canvas.drawPoint(100,100,mPaint);//绘制控制点
canvas.drawPoint(200,0,mPaint);//绘制终点
}
cubicTo
使用方法与quadTo()方法相同,前4个参数分别为两个控制点,最后两个参数是终点
总结一下:xxxTo()方法的作用是从某一位置连接(除moveTo)到另一位置。
addXxx()系列
add家族成员主要有:addRect, addRoundRect, addOval, addCircle, addPath, addArc
前面几个方法小伙伴们一看就知道啥东西了。和上一期讲的内容相似,只不过是把基础图形添加到path中绘制。
以addRect为例,
public void addRect(RectF rect, Direction dir)
第二个参数是一个枚举,有Path.Direction.CW和Path.Direction.CCW两种选择,分别代表顺时针和逆时针的闭合方向(所有add封闭型path都需要这个参数)。什么叫闭合方向呢?光说大家一定会蒙,有学习激情的童鞋看到后肯定会马上敲代码试验一下两者的区别,可是不管你如何改,单独地在一条闭合曲线上你是看不出所谓闭合方向的区别的。为了弄懂它,我们在path上绘制一些文字来说明最后参数的意义:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
Path path = new Path();// 创建Path
path.addRect(100,100,300,200, Path.Direction.CW);
mPaint.setTextSize(50);
// 绘制路径上的文字
canvas.drawTextOnPath("abcdefghijklmn", path, 0, 0, mPaint);
}
顺时针
逆时针
addArc()
这东西咋一看和arcTo()很像,没错,事实上也差不多。
addArc(参数一,参数二,参数三)等价于arcTo(参数一,参数二,参数三,true);
addPath()
顾名思义,把一个path添加到另一个path中:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
RectF rectF = new RectF(100,100,400,300);
Path path = new Path();
path.addRect(rectF, Path.Direction.CW);
Path path1 = new Path();
path1.addOval(rectF, Path.Direction.CW);
path1.addPath(path);
// path1.addPath(path,100,100);//后两个参数控制平移
canvas.drawPath(path1,mPaint);
}
结果如下:
如果添加上两个参数,则在X轴和Y轴方向上分别平移了100px:
rXXXTo()系列
rXXXTo()系列基本上是XXXTo()系列的小弟
public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
public void rLineTo(float dx, float dy)
public void rMoveTo(float dx, float dy)
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
这一系列rXXXTo方法其实跟上面的那些XXXTo差不多的,唯一的不同是rXXXTo方法的参考坐标是相对的而XXXTo方法的参考坐标始终是参照画布原点坐标,什么意思呢?举个简单的例子:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
Path path = new Path();
path.moveTo(100,100);
path.lineTo(200,200);
//path.rLineTo(200,200);
canvas.drawPath(path,mPaint);
}
把path.lineTo(200,200);换成path.rLineTo(200,200);结果如下:
线段长了很多,因为这里的(200,200)是相对于开始点(100,100)来说的,是相对坐标。如果换算成绝对坐标就是绘制一条(100,100)到(300,300)之间的线段。其实,这个前缀“r”也就是relative(相对)的简写!
其他几个比较重要的方法
setLastPoint()
重置当前path中最后一个点位置,会对上一次的绘制造成影响。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
Path path = new Path();
path.lineTo(200,200);
path.setLastPoint(300,200);
canvas.drawPath(path,mPaint);
mPaint.setStrokeWidth(15);//加粗,便于显示
mPaint.setColor(Color.RED);
canvas.drawPoint(200,200,mPaint);
}
虽然我们lineTo(200,200),但是紧接着调用setLastPoint(300,200)方法,把最后一个点移动到(300,200)的位置,所以所画的直线是从原点到(300,200)的位置
offset()
对当前路径之前的操作进行偏移(不会影响之后的操作)
//将新的path赋值到现有path
public void offset (float dx, float dy)
//将新的path赋值到dst,不影响现有path,当dst为null时,效果同上一种方法
public void offset (float dx, float dy, Path dst)
参数:
dx:X轴方向上的平移
dy:Y轴方向上的平移
dst:用于储存平移后的结果
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth()/2,getHeight()/2);
//绘制坐标系
float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
mPaint.setColor(Color.BLACK);
canvas.drawLines(pts,mPaint);
Path path = new Path(); // path中添加一个圆形(圆心在坐标原点)
path.addCircle(0,0,100, Path.Direction.CW);
Path dst = new Path(); // dst中添加一个矩形
dst.addRect(-200,-200,200,200, Path.Direction.CW);
path.offset(300,0,dst); // 平移
canvas.drawPath(path,mPaint); // 绘制path
mPaint.setColor(Color.BLUE); // 更改画笔颜色
canvas.drawPath(dst,mPaint); // 绘制dst
}
1500287070(1).jpg
结语
剩余的一些方法比较简单,查看上面的表就可以了