您的位置:首页 > 其它

自定义View学习笔记08—Path基本操作

2017-12-27 18:08 387 查看
从本篇笔记开始,我们进入上一篇笔记里面提到的大杀器Path的世界。

同样的,我们依循惯例先来看看Path的常用方法(API21以下的):



同样的,使用到Path,也要关闭硬件加速。方法同前。

Path作用

之所以称Path是一个大杀器,并单独来学习,就是因为Path不仅能够绘制简单图形,也可以绘制这些比较复杂的图形。另外,根据路径绘制文本和剪裁画布都会用到Path。在2D绘图中基本离不开Path。

Path含义

Path封装了由直线和曲线(二次,三次贝塞尔曲线)构成的几何路径。你能用Canvas中的drawPath来把这条路径画出来(同样支持Paint的不同绘制模式),也可以用于剪裁画布和根据路径绘制文字。我们有时会用Path来描述一个图像的轮廓,所以也会称为轮廓线(轮廓线仅是Path的一种使用方法,两者并不等价)。

另外路径有开放和封闭的区别。开放路径:没有首位相接形成封闭区域;封闭路径:首尾相接形成了一个封闭区域。

Path使用详解

第1组: lineTo、moveTo、 setLastPoint和 close

lineTo:是指从某个点到参数坐标点之间连一条线,这里的某个点就是上次操作结束的点,如果没有进行过操作则默认点为坐标原点。

依照惯例我们先看看API提供的方法:

public void lineTo (float x, float y)


就是指指从某个点到参数坐标点之间连一条线,这里的“某个点”指的是默认的点(如坐标原点或者上一次操作划线的点),如下:

Path mPath = new Path();
Path.lineTo(100,24);
Path.lineTo(520,550);


这里我们调用了两次lineTo,第一次由于之前没有过操作,所以默认点就是坐标原点左上角(这里是View的左上角),结果就是坐标原点到A(100,24)之间连直线。

第二次lineTo,由于我们已经操作过一次了,所以这次的起点就是上一次操作过的终点(100,24),其结果就是绘制了一条从坐标(100,24)到(520,550)的点:



这里我们以常见的图表为例做进一步的学习:

private void drawLineTo(Canvas canvas, Paint mPaint){
//默认起始点为上次操作结束的点或者坐标原点(屏幕或者控件的左上角)。
//如下,做了canvas.translate(10, hei-10)之后,默认点为(10, hei-10)处。
canvas.drawColor(Color.parseColor("#dfdfdf"));
canvas.translate(10, hei-10);
//准备数据
float[] x = {100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600};
float[] y = {24, 150, 380, 73, 400, 220, 245, 280, 350, 465, 500};
List<PathCanvasBean> pathList = new ArrayList<>();
for (int i = 0; i < x.length; i++) {
pathList.add(new PathCanvasBean(x[i], y[i]*(-1)));
}

//绘制折线
mPaint.setStrokeWidth(4);
//线冒样式(相当于给原来的直线末端加上一个帽子):圆形,方块,无;
//http://blog.csdn.net/harvic880925/article/details/51010839
mPaint.setStrokeCap(Paint.Cap.SQUARE);
//设置线段连接处过渡样式:锐角过渡(尖锐的意思),圆弧过渡,直线过渡;
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setColor(Color.parseColor("#93A5F4"));
Path mPath = new Path();
//设置折线的起点位置,否则折线起点是系统默认的左上角或者View的左上角。
mPath.moveTo(pathList.get(0).getX(), pathList.get(0).getY());
for (int i = 0; i < x.length; i++) {
mPath.lineTo(pathList.get(i).getX(), pathList.get(i).getY());
canvas.drawPath(mPath, mPaint);
}

float textSize = 12;
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setTextSize(textSize);
mPaint.setStrokeWidth(1);

//cirPaint用于画每段线段之间的点
Paint cirPaint = new Paint();
cirPaint.setAntiAlias(true);
cirPaint.setStyle(Paint.Style.FILL_AND_STROKE);
cirPaint.setColor(Color.WHITE);
for (int i = 0; i < x.length; i++) {
//绘制折线转折出的点
int x = pathList.get(i).getX();
int y = pathList.get(i).getY();
canvas.drawCircle(x, y, 5, cirPaint);
String coordinate = "(" + x + ",  " + y*(-1) + ")";
//绘制每个点对应的坐标值,至于为什么这里要分别加10和textSize/2,
//掌握了上一篇笔记“自定义View学习笔记07—Canvas绘制文字”的都应该知道
canvas.drawText(coordinate, x + 10, y + textSize/2, mPaint);
}
}


在onDraw()方法里面调用上述方法,运行结果如下:



moveTo 和 setLastPoint

依照惯例,我们先看下API提供的方法:

public void moveTo (float x, float y);
public void setLastPoint (float dx, float dy);


这两个方法是完全不相同的



现在用代码来区别:

//moveTo的用法
private void drawMoveTo(Canvas canvas, Paint mPaint){
Path mPath = new Path();
mPath.moveTo(50, 50);
mPath.lineTo(500,60);
mPath.moveTo(180,160);
mPath.lineTo(520,550);
canvas.drawPath(mPath, mPaint);
//moveTo:移动起点到:x, y;
//含义解说:mPath.moveTo(50, 50):折现起点/原点,
// mPath.lineTo(500,60):从起点/原点划线到(500,60);
//然后将绘图起点移到【mPath.moveTo(180,160)】(180,160),
//再从(180,160)划线到(520,550)【mPath.lineTo(520,550)】;
}

//setLastPoint的用法
private void drawLastPoint(Canvas canvas, Paint mPaint){
Path mPath = new Path();
mPath.moveTo(50,50);
mPath.lineTo(100,24);
mPath.setLastPoint(200,320);
mPath.lineTo(500,580);
canvas.drawPath(mPath, mPaint);
//setLastPoint是重置上一次操作的最后一个点,在执行完第一次的lineTo的时候,
// 最后一个点是A(100,24),而setLastPoint更改最后一个点为C(300,220),
// 所以在实际执行的时候,第一次的lineTo就不是从原点O到A(100,24)的连线了,
// 而变成了从原点(50,50)到C(200,320)之间的连线。然后再从(200,320)划线到(500,580)
}


在onDraw()方法里面分别调用上述方法,二者运行效果如下:

moveTo运行效果:



setLastPoint运行效果



close:

依照惯例,我们先看下API提供的方法:

public void close ()


主要作用是:用于连接当前最后一个点和最初的一个点(如果两个点不重合的话),形成一个封闭的图形。

private void drawClose(Canvas canvas, Paint mPaint){
Path closePath = new Path();
closePath.moveTo(50,50);
closePath.lineTo(120, 260);
closePath.lineTo(300,400);
closePath.lineTo(500,300);
closePath.lineTo(400,500);
closePath.close();
canvas.drawPath(closePath, mPaint);
}


在onDraw()方法里面调用上述方法,运行效果如下:



注意:

close的作用是封闭路径,与连接当前最后一个点和第一个点,形成封闭图形。如果连接了最后一个点和第一个点仍然无法形成封闭图形,则close什么 也不做。

第2组: addXxx、arcTo

主要作用是在Path中添加基本图形,重点区分addArc与arcTo。

第一类addXxx(基本形状)方法预览:

// 第一类(基本形状)
public void addCircle(float x,float y,float radius,Path.Direction dir) // 圆形
public void addOval(RectF oval, Path.Direction dir)//椭圆
public void addRect(float left,float top,float right,float bottom,
Path.Direction dir)//矩形
public void addRect(RectF rect, Path.Direction dir)//矩形
public void addRoundRect(RectF rect,float[]radii,Path.Direction dir)//圆角矩形
public void addRoundRect(RectF rect,float rx,float ry,Path.Direction dir)//圆角矩形


这一类就是在path中添加一个基本形状,基本形状部分和前面所讲的绘制基本形状并无太大差别,使用上都是通过path.addXxx的方式调用,最后再通过canvas.drawPath()的方法绘制图形,如下:

private void drawRect(Canvas canvas, Paint mPaint){
canvas.translate(wid / 2, hei / 2);
Path rectPath = new Path();

rectPath.addCircle(0, 0, 100, Path.Direction.CCW);//圆

RectF rect = new RectF(-200,-200, 200, 200);//矩形
rectPath.addRect(rect, Path.Direction.CW);

RectF rect1 = new RectF(-200,-100, 200, 100);//椭圆
rectPath.addOval(rect1, Path.Direction.CW);

RectF rect2 = new RectF(-250,-200, 250, 200);//圆角矩形
rectPath.addRoundRect(rect2, 50, 50, Path.Direction.CCW);
canvas.drawPath(rectPath, mPaint);
}


在onDraw方法中调用上方法,运行效果如下:



方法中的参数含义都应该能理解到,这里重点讲解Path.Direction,Direction的意思是方向、趋势,包含两个枚举常量:



至于对闭合顺序到底有啥影响,图形的渲染等问题等请慢慢看下去。

a、先研究确定CW的闭合顺序的问题,添加一个矩形试试看:

private void addRxxCW(){
canvas.translate(wid / 2, hei / 2);
Path rectPath = new Path();
RectF rect = new RectF(-200,-200, 200, 200);
rectPath.addRect(rect, Path.Direction.CW);
canvas.drawPath(rectPath, mPaint);
}


在onDraw方法中调用上方法,运行效果如下:



b、再确定CCW的闭合顺序的问题,添加一个矩形试试看:

private void addRxxCCW(){
canvas.translate(wid / 2, hei / 2);
Path rectPath = new Path();
RectF rect = new RectF(-200,-200, 200, 200);
rectPath.addRect(rect, Path.Direction.CCW);
canvas.drawPath(rectPath, mPaint);
}


在onDraw方法中调用上方法,运行效果如下:



运行结果一模一样,感觉很神奇,并且,你他妈的都一模一样还弄两个出来搞毛啊,消遣人么?其实这个东东是自带隐身技能的,想要让它现出原形,就要用到咱们刚刚学到的setLastPoint(重置当前最后一个点的位置)。

在addRxxCCW()方法中的canvas.drawPath(rectPath, mPaint);之前再加一行代码:rectPath.setLastPoint(-250, 250);运行结果如下,可以明显看到,图形发生了奇怪的变化



需要注意的是,交换坐标点的顺序可能就会影响到某些绘制内容哦,例如上面的例子,你可以尝试交换两个坐标点,或者指定另外两个点来作为参数,虽然指定的是同一个矩形,但实际绘制出来是不同的————参数中点的顺序很重要!参数中点的顺序很重要!参数中点的顺序很重要!一定要记得,重要的事情说三遍。

第二类(Path)方法预览:

// 第二类(Path)
public void addPath (Path src)
public void addPath (Path src, float dx, float dy)
public void addPath (Path src, Matrix matrix)


第一个方法相对比较简单,也很容易理解,就是将两个Path合并成为一个;

第二个方法比第一个方法多出来的两个参数是将src进行了位移之后再添加进当前path中;

第三个方法是将src添加到当前path之前先使用Matrix进行(矩阵)变换。难度一个个的增大。

下面我们用哪个代码来做演示:

private void addPathPaint(){
canvas.translate(wid / 2, hei / 2);
canvas.scale(1, -1);
Path mergePath = new Path();
Path src = new Path();
mergePath.addRect(-100,-100,100,100, Path.Direction.CW);//矩形
src.addCircle(0,100,80, Path.Direction.CW);//圆
mergePath.addPath(src);//将圆添加到矩形path可以,反过来则出问题
canvas.drawPath(mergePath, mPaint);
}


在onDraw方法中调用上方法,运行效果如下:



第三类(addArc与arcTo) 方法预览:

public void addArc(RectF oval,float startAngle,float sweepAngle);
public void arcTo(RectF oval,float startAngle,float sweepAngle);
public void arcTo(RectF oval,float startAngle,float sweepAngle,boolean forceMoveTo);


addArc与arcTo的区别:



其中参数的含义如下:



特别注意:

sweepAngle取值范围是 [-360, 360),当 >= 360 或者 < -360 时将不会绘制任何内容, 对于360,你可以用一个接近的值替代,例如: 359.99。

forceMoveTo作用: 是否使用moveTo强制将变量移动到圆弧的起点位移。

addArc ()示例:

private void drawAddArc(Canvas canvas, Paint mPaint){
canvas.translate(wid / 2, hei / 2);// 移动坐标系到屏幕中心
Path path = new Path();
path.lineTo(141.5f, 141.5f);//画直线
RectF oval = new RectF(-200, -200, 200, 200);//画圆弧
//path.addArc(oval, 135, 270);
path.arcTo(oval, 135, 270, true);//和上面一句作用等价
canvas.drawPath(path, mPaint);
}


在onDraw方法中调用上方法,运行效果如下:



arcTo ()示例:

private void drawArcTo(Canvas canvas, Paint mPaint){
canvas.translate(wid / 2, hei / 2);  // 移动坐标系到屏幕中心
Path path = new Path();
path.lineTo(141.5f, 141.5f);

RectF oval = new RectF(-200, -200, 200, 200);
//path.addArc(oval, 135, 270);
//arcTo中Boolean值作用:是否形成封闭的图形
path.arcTo(oval, 135, 270, false);//和上一行代码作用等价
canvas.drawPath(path,mPaint);
}


在onDraw方法中调用上方法,运行效果如下:



第3组:isEmpty、 isRect、isConvex、 set 、offset:

这一组比较简单,稍微说一下就可以了。

isEmpty()方法预览:

public boolean isEmpty ();作用是判断path中是否包含内容。

Path path = new Path();
Log.e("1",path.isEmpty()+"");
path.lineTo(100,100);
Log.e("2",path.isEmpty()+"");


log输出结果: com.sloop.canvas E/1: true;;com.sloop.canvas E/2: false

isRect()方法预览:

public boolean isRect (RectF rect); 作用是判断path是否是一个矩形,如果是,会将矩形的信息存放进参数rect中

path.lineTo(0,400);
path.lineTo(400,0); path.lineTo(0,0);
RectF rect = new RectF();
boolean b = path.isRect(rect);
Log.e("Rect","isRect:"+b+"| left:"+rect.left+"| top:"+rect.top+"| right:"+rect.right+"| bottom:"+rect.bottom);


log 输出结果:

com.sloop.canvas E/Rect: isRect:true| left:0.0| top:0.0| right:400.0| bottom:400.0

set()方法预览:

public void set (Path src); 将新的path赋值到现有path。

canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
canvas.scale(1,-1);// <-- 注意 翻转y坐标轴
Path path = new Path();// path添加一个矩形
path.addRect(-200,-200,200,200, Path.Direction.CW);
Path src = new Path();// src添加一个圆
src.addCircle(0,0,100, Path.Direction.CW);
path.set(src);// 大致相当于 path = src;
canvas.drawPath(path,mPaint);


运行结果如下:



offset()方法预览:

public void offset (float dx, float dy);
public void offset (float dx, float dy, Path dst);


这个的作用就是对path进行一段平移,它和Canvas中的translate作用很像,但Canvas作用于整个画布,而path的offset只作用于当前path。第二个方法的dst参数是存储平移后的path的: dst不为空, 将当前path平移后的状态存入dst中,不会影响当前path; dst为空(null),平移将作用于当前path,相当于第一种方法。

canvas.translate(mWidth / 2, mHeight / 2);// 移动坐标系到屏幕中心
canvas.scale(1,-1);// <-- 注意 翻转y坐标轴
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


运行结果如下:



第4组: rXxx方法,该方法整体上来讲就是利用相对坐标编程。

rXxx方法的坐标使用的是相对位置(基于当前点的位移),而之前方法的坐标是绝对位置(基于当前坐标系的坐标)。最前面的r就是RelativeLayout的缩写。

使用如下:

private void drawRxxx(Canvas canvas, Paint mPaint){
Path path = new Path();
path.lineTo(100, 200);
canvas.drawPath(path, mPaint);
Path path1 = new Path();
path1.moveTo(100, 100);
//Rxxx系列方法:相对坐标编程
//相对于上一个点(100, 100);绝对坐标下的坐标为:(200, 300)
path1.rLineTo(100, 200);
canvas.drawPath(path1, mPaint);
canvas.drawCircle(200, 300, 10, mPaint);
}


onDraw方法里面利用上述方法啊,运行结果如下:



此处仅以 rLineTo 为例,只要理解 “绝对坐标” 和 “相对坐标” 的区别,其他方法类比即可。

布尔操作(API19):

布尔操作与我们中学所学的集合操作非常像,只要知道集合操作中的子集,交集,并集,差集,补集等操作,那么理解布尔操作也是很容易的。

布尔操作是两个Path之间的运算,主要作用是用一些简单的图形通过一些规则合成一些相对比较复杂,或难以直接得到的图形。

布尔运算的核心:布尔逻辑,它有五种逻辑,如下:



在Path中的布尔运算有两个方法:

boolean op (Path path, Path.Op op);
boolean op (Path path1, Path path2, Path.Op op);


两个方法中的返回值用于判断布尔运算是否成功,它们使用方法如下:

对 path1 和 path2 执行布尔运算,运算方式由第二个参数指定,运算结果存入到path1中。

path1.op(path2, Path.Op.DIFFERENCE);
// 对 path1 和 path2 执行布尔运算,运算方式由第三个参数指定,运算结果存入到path3中。
path3.op(path1, path2, Path.Op.DIFFERENCE);


如太极中的阴阳鱼,如果用贝塞尔曲线制作的话,可能需要六段贝塞尔曲线才行,而在这里我们可以用四个Path通过布尔运算得到,而且会相对来说更容易理解一点。

这里我就用画阴阳鱼来演示布尔操作:

private void drawTaiJi(Canvas canvas, Paint mPaint){
canvas.translate(wid / 2, hei / 2);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.BLACK);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);

Path path1 = new Path();
Path path2 = new Path();
Path path3 = new Path();
Path path4 = new Path();

path1.addCircle(0, 0, 200, Path.Direction.CW);
path2.addRect(0, -200, 200, 200, Path.Direction.CW);
path3.addCircle(0, -100, 100, Path.Direction.CW);
path4.addCircle(0, 100, 100, Path.Direction.CCW);

path1.op(path2, Path.Op.DIFFERENCE);
path1.op(path3, Path.Op.UNION);
path1.op(path4, Path.Op.DIFFERENCE);
canvas.drawPath(path1, mPaint);

mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(0, 0, 200, mPaint);

mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(0, 100, 30, mPaint);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(0, -100, 30, mPaint);
}


onDraw方法里面利用上述方法啊,运行结果如下:



Path的基本操作到此结束。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: