前言

  • 自己定义View是Android开发人员必须了解的基础;而Path类的使用在自己定义View绘制中发挥着很关键的数据
  • 网上有大量关于自己定义View中Path类的文章。但存在一些问题:内容不全、思路不清晰、简单问题复杂化等等
  • 今天。我将全面总结自己定义View中Path类的使用,我能保证这是市面上的最全面、最清晰、最易懂的
  1. 文章较长,建议收藏等充足时间再进行阅读
  2. 阅读本文前请先阅读自己定义View基础 - 最易懂的自己定义View原理系列

文件夹


1. 简单介绍

  • 定义:路径。即无数个点连起来的线
  • 作用:设置绘制的顺序 & 区域

    Path仅仅用于描写叙述顺序 & 区域,单使用Path无法产生效果

  • 应用场景:绘制复杂图形(如心形、五角星等等)

    Path类封装了由直线和曲线(2、3次贝塞尔曲线)构成的几何路径。


2. 基础

2.1 开放路径与闭合路径的差别

2.2 怎样推断点在图形内还是图形外

  • 推断方法分为奇偶规则 & 非零围绕规则。详细介绍例如以下:

举例说明1:(奇偶规则)

由上图知:

  • p1发出的射线与图形相交1个点,即奇数点,所以P1点在图形内
  • p2发出的射线与图形相交2个点,即偶数点。所以P2点在图形内

举例说明2:(非零围绕数规则)

从上面方法分析到,不论什么图形都是由点连成线组成的。是具备方向的,看下图:(矩形是顺时针)

  • p1发出的射线与图形相交1个点。矩形的右側线从左边射到右边,围绕数-1,终于围绕数为-1。故p1在图形内部。
  • p2发出的射线与图形相交2个点:矩形的右側边从左边射到右边

    围绕数-1。矩形的下側边从右边射到左边,围绕数+1。终于围绕数为0.故p2在图形外部

3. 详细使用

3.1 对象创建

    // 使用Path首先要new一个Path对象
// Path的起点默觉得坐标为(0,0)
Path path = new Path();
// 特别注意:建全局Path对象,在onDraw()按需改动。尽量不要在onDraw()方法里new对象
// 原因:若View频繁刷新。就会频繁创建对象,拖慢刷新速度。

3.2 详细方法使用

由于path类的方法都是联合使用。所以以下将一组组方法进行介绍。

第一组:设置路径

採用moveTo()、setLastPoint()、lineTo()、close()组合


// 设置当前点位置
// 后面的路径会从该点開始画
moveTo(float x, float y) ; // 当前点(上次操作结束的点)会连接该点
// 假设没有进行过操作则默认点为坐标原点。 lineTo(float x, float y) ; // 闭合路径,即将当前点和起点连在一起
// 注:假设连接了最后一个点和第一个点仍然无法形成封闭图形,则close什么也不做
close() 。
  • 可使用setLastPoint()设置当前点位置(取代moveTo()
  • 二者差别:

实例介绍:(含setLastPoint()moveTo()


// 使用moveTo()
// 起点默认是(0,0)
//连接点(400,500)
path.lineTo(400, 500); // 将当前点移动到(300, 300)
path.moveTo(300, 300) ; //连接点(900, 800)
path.lineTo(900, 800); // 闭合路径,即连接当前点和起点
// 即连接(200,700)与起点2(300, 300)
// 注:此时起点已经进行变换
path.close(); // 画出路径
canvas.drawPath(path, mPaint1); // 使用setLastPoint()
// 起点默认是(0,0)
//连接点(400,500)
path.lineTo(400, 500); // 将当前点移动到(300, 300)
// 会影响之前的操作
// 但不将此设置为新起点
path.setLastPoint(300, 300) ; //连接点(900,800)
path.lineTo(900, 800); //连接点(200,700)
path.lineTo(200, 700); // 闭合路径,即连接当前点和起点
// 即连接(200,700)与起点(0,0)
// 注:起点一直没变化
path.close(); // 画出路径
canvas.drawPath(path, mPaint1);

关于重置路径

  • 重置Path有两个方法:reset()rewind()
  • 两者差别在于:
方法 是否保留FillType设置 是否保留原有数据结构
Path.reset()
Path.rewind()
  1. FillType影响显示效果。数据结构影响重建速度
  2. 所以一般选择Path.reset()

由于较简单,此处不作过多展示。

第二组: 加入路径

採用addXxx()、arcTo()组合

2.1 加入基本图形

  • 作用:在Path路径中加入基本图形

    如圆形路径、圆弧路径等等

  • 详细使用


// 加入圆弧
// 方法1
public void addArc (RectF oval, float startAngle, float sweepAngle) // startAngle:确定角度的起始位置
// sweepAngle : 确定扫过的角度 // 方法2
// 与上面方法唯一不同的是:假设圆弧的起点和上次最后一个坐标点不同样,就连接两个点
public void arcTo (RectF oval, float startAngle, float sweepAngle) // 方法3
// 參数forceMoveTo:是否将之前路径的结束点设置为圆弧起点
// true:在新的起点画圆弧,不连接最后一个点与圆弧起点,即与之前路径没有交集(同addArc())
// false:在新的起点画圆弧。但会连接之前路径的结束点与圆弧起点。即与之前路径有交集(同arcTo(3參数))
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
// 以下会详细说明 // 加入圆形路径
// 起点:x轴正方向的0度
// 当中參数dir:指定绘制时是顺时针还是逆时针:CW为顺时针, CCW为逆时针
// 路径起点变为圆在X轴正方向最大的点
addCircle(float x, float y, float radius, Path.Direction dir) // 加入椭圆形路径
// 当中。參数oval作为椭圆的外切矩形区域
addOval(RectF oval, Path.Direction dir) // 加入矩形路径
// 路径起点变为矩形的左上角顶点
addRect(RectF rect, Path.Direction dir) //加入圆角矩形路径 addRoundRect(RectF rect, float rx, float ry, Path.Direction dir) // 注:加入图形路径后会改变路径的起点

主要说一下dir这个參数:

dir = Direction = 图形的方向。为枚举类型:

  • CW:clockwise。顺时针
  • CCW:counter-clockwise。逆时针

图形的方向影响的是:

  • 加入图形时确定闭合顺序(各个点的记录顺序)
  • 图形的渲染结果(是推断图形渲染的重要条件)

图形绘制的本质:先画点,再将点连接起来。

所以。点与点之间是存在一个先后顺序的;顺时针和逆时针用于确定这些点的顺序。

以下实例将说明:

  // 为了方便观察,平移坐标系
canvas.translate(350, 500);
// 顺时针
path.addRect(0, 0, 400, 400, Path.Direction.CW); // 逆时针
// path.addRect(0,0,400,400, Path.Direction.CCW);
canvas.drawPath(path,mPaint1);

关于加入图形路径后会影响路径的起点,实比例如以下:

  // 轨迹1
// 将Canvas坐标系移到屏幕正中
canvas.translate(400,500); // 起点是(0,0),连接点(-100,0)
path.lineTo(-100,0);
// 连接点(-100,200)
path.lineTo(-100,200);
// 连接点(200,200)
path.lineTo(200,200);
// 闭合路径。即连接当前点和起点
// 即连接(200,200)与起点是(0,0)
path.close(); // 画出路径
canvas.drawPath(path,paint);
// 详细请看下图 // 轨迹2
// 将Canvas坐标系移到屏幕正中
canvas.translate(400,500); // 起点是(0,0)。连接点(-100,0)
path.lineTo(-100,0);
// 画圆:圆心=(0,0),半径=100px
// 此时路径起点改变 = (0,100)(记为起点2)
// 起点改变原则:新绘图形在x轴正方向的最后一个坐标
// 后面路径的变化以这个点继续下去
path.addCircle(0,0,100, Path.Direction.CCW); // 起点为:(0,100),连接 (-100,200)
path.lineTo(-100,200);
// 连接 (200,200)
path.lineTo(200,200); // 闭合路径,即连接当前点和起点(注:闭合的是起点2)
// 即连接(200,200)与起点2(0,100)
path.close(); // 画出路径
canvas.drawPath(path,paint); // // 详细请看下图

这里着重说明:加入圆弧路径(addArc与arcTo)

 // addArc
// 直接加入一个圆弧到path中
// startAngle:确定角度的起始位置
// sweepAngle : 确定扫过的角度
public void addArc (RectF oval, float startAngle, float sweepAngle) // arcTo
// 方法1
// 同样是加入一个圆弧到path
// 与上面方法唯一不同的是:假设圆弧的起点和上次最后一个坐标点不同样,就连接两个点
public void arcTo (RectF oval, float startAngle, float sweepAngle) // 方法2
// 參数forceMoveTo:是否将之前路径的结束点设置为圆弧起点
// true:在新的起点画圆弧,不连接最后一个点与圆弧起点,即与之前路径没有交集(同addArc())
// false:在新的起点画圆弧,但会连接之前路径的结束点与圆弧起点。即与之前路径有交集(同arcTo(3參数))
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

详细请看以下实例


// 将一个圆弧路径加入到一条直线路径里 // 为了方便观察,平移坐标系
canvas.translate(350, 500); // 先将原点(0,0)连接点(100,100)
path.lineTo(50, 200); // 加入圆弧路径(2分之1圆弧) // 不连接最后一个点与圆弧起点
path.addArc(new RectF(200, 200, 300, 300), 0, 180);
// path.arcTo(oval,0,270,true); // 与上面一句作用等价 // 连接之前路径的结束点与圆弧起点
path.arcTo(new RectF(200, 200, 300, 300), 0, 180);
// path.arcTo(oval,0,270,false); // 与上面一句作用等价 // 画出路径
canvas.drawPath(path, mPaint1);

2.2 加入路径

  • 作用:合并路径

    即将路径1加到路径2里

  • 详细使用

    // 方法1
public void addPath (Path src) // 方法2
// 先将src进行(x,y)位移之后再加入到当前path
public void addPath (Path src, float dx, float dy) // 方法3
// 先将src进行Matrix变换再加入到当前path
public void addPath (Path src, Matrix matrix) // 实例:合并矩形路径和圆形路径 // 为了方便观察,平移坐标系
canvas.translate(350, 500);
// 创建路径的对象
Path pathRect = new Path();
Path pathCircle = new Path();
// 画一个矩形路径
pathRect.addRect(-200, -200, 200, 200, Path.Direction.CW);
// 画一个圆形路径
pathCircle.addCircle(0, 0, 100, Path.Direction.CW); // 将圆形路径移动(0,200),再加入到矩形路径里
pathRect.addPath(pathCircle, 0, 200); // 绘制合并后的路径
canvas.drawPath(pathRect,mPaint1);

第三组:推断路径属性

  • 採用isEmpty()、 isRect()、isConvex()、 set() 和 offset()组合

  • 详细使用:

// 推断path中是否包括内容
public boolean isEmpty ()
// 样例:
Path path = new Path();
path.isEmpty(); //返回false path.lineTo(100,100); // 返回true // 推断path是否是一个矩形
// 假设是一个矩形的话,会将矩形的信息存放进參数rect中。
public boolean isRect (RectF rect) // 实例
path.lineTo(0,400);
path.lineTo(400,400);
path.lineTo(400,0);
path.lineTo(0,0); RectF rect = new RectF();
boolean b = path.isRect(rect); // b返回ture,
// rect存放矩形參数,详细例如以下:
// rect.left = 0
// rect.top = 0
// rect.right = 400
// rect.bottom = 400 // 将新的路径替代现有路径
public void set (Path src) // 实例
// 设置一矩形路径
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW); // 设置一圆形路径
Path src = new Path();
src.addCircle(0,0,100, Path.Direction.CW); // 将圆形路径取代矩形路径
path.set(src); // 绘制图形
canvas.drawPath(path,mPaint); // 平移路径
// 与Canvas.translate ()平移画布相似 // 方法1
// 參数x,y:平移位置
public void offset (float dx, float dy) // 方法2
// 參数dst:存储平移后的路径状态,但不影响当前path
// 可通过dst參数绘制存储的路径
public void offset (float dx, float dy, Path dst) // 为了方便观察,平移坐标系
canvas.translate(350, 500); // path中加入一个圆形(圆心在坐标原点)
path = new Path();
path.addCircle(0, 0, 100, Path.Direction.CW); // 平移路径并存储平移后的状态
Path dst = new Path();
path.offset(400, 0, dst); // 平移 canvas.drawPath(path, mPaint1); // 绘制path // 通过dst绘制平移后的图形(红色)
mPaint1.setColor(Color.RED);
canvas.drawPath(dst,mPaint1);

第四组:设置路径填充颜色

  • 在Android中,有四种填充模式。详细例如以下

    均封装在Path类中

填充模式 介绍
EVEN_ODD 奇偶规则
INVERSE_EVEN_ODD 反奇偶规则
WINDING 非零围绕数规则
INVERSE_WINDING 反非零围绕数规则

请记住两个填充规律:

从我之前的文章(1)自己定义View基础 - 最易懂的自己定义View原理系列提到,图形是存在方向的(绘图 = 连接点成的线 = 有连接顺序)。

  • 详细使用
// 设置填充规则
path.setFillType()
// 可填规则
// 1. EVEN_ODD:奇偶规则
// 2. INVERSE_EVEN_ODD:反奇偶规则
// 3. WINDING :非零围绕数规则
// 4. INVERSE_WINDING:反非零围绕数规则 // 理解奇偶规则和反奇偶规则:填充效果相反
// 举例:对于一个矩形而言,使用奇偶规则会填充矩形内部,而使用反奇偶规则会填充矩形外部(以下会举例说明) // 获取当前填充规则
path.getFillType() // 推断是否是反向(INVERSE)规则
path.isInverseFillType() // 切换填充规则(即原有规则与反向规则之间相互切换)
path.toggleInverseFillType()

实例1:(奇偶规则)


// 为了方便观察,平移坐标系
canvas.translate(350, 500); // 在Path中加入一个矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW); // 设置Path填充模式为 奇偶规则
path.setFillType(Path.FillType.EVEN_ODD); // 反奇偶规则
// path.setFillType(Path.FillType.INVERSE_EVEN_ODD); // 画出路径
canvas.drawPath(path, mPaint1);

imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="" title="">

举例2:(非零围绕规则)

    // 为了方便观察,平移坐标系
canvas.translate(550, 550);
// 在路径中加入大正方形
// 逆时针
path.addRect(-400, -400, 400, 400, Path.Direction.CCW); // 在路径中加入小正方形
// 顺时针
// path.addRect(-200, -200, 200, 200, Path.Direction.CW);
// 设置为逆时针
path.addRect(-200, -200, 200, 200, Path.Direction.CCW); // 设置Path填充模式为非零围绕规则
path.setFillType(Path.FillType.WINDING);
// 设置反非零围绕数规则
// path.setFillType(Path.FillType.INVERSE_WINDING); // 绘制Path
canvas.drawPath(path, mPaint1);

第五组:布尔操作

  • 作用:两个路径Path之间的运算
  • 应用场景:用简单的图形通过特定规则合成相对复杂的图形。

  • 详细使用
// 方法1
boolean op (Path path, Path.Op op)
// 举例
// 对 path1 和 path2 运行布尔运算,运算方式由第二个參数指定
// 运算结果存入到path1中。
path1.op(path2, Path.Op.DIFFERENCE); // 方法2
boolean op (Path path1, Path path2, Path.Op op)
// 举例
// 对 path1 和 path2 运行布尔运算,运算方式由第三个參数指定
// 运算结果存入到path3中。 path3.op(path1, path2, Path.Op.DIFFERENCE)

之间的运算方式(即Path.Op參数)例如以下

举例:

   // 为了方便观察,平移坐标系
canvas.translate(550, 550); // 画两个圆
// 圆1:圆心 = (0,0),半径 = 100
// 圆2:圆心 = (50,0),半径 = 100
path1.addCircle(0, 0, 100, Path.Direction.CW);
path2.addCircle(50, 0,100, Path.Direction.CW); // 取两个路径的异或集
path1.op(path2, Path.Op.XOR);
// 画出路径
canvas.drawPath(path1, mPaint1);


4. 贝赛尔曲线

  • 定义:计算曲线的数学公式
  • 作用:计算并表示曲线

    不论什么一条曲线都能够用贝塞尔曲线表示

  • 详细使用:贝塞尔曲线可通过1数据点和若干个控制点描写叙述

  1. 数据点:指路径的起始点和终止点;
  2. 控制点:决定了路径的弯曲轨迹;
  3. n+1阶贝塞尔曲线 = 有n个控制点。
  4. (1阶 = 一条直线,高阶能够拆解为多条低阶曲线)

Canvas提供了画二阶 & 三阶贝塞尔曲线的方法。以下是详细方法:


// 绘制二阶贝塞尔曲线
// (x1,y1)为控制点,(x2,y2)为终点
quadTo(float x1, float y1, float x2, float y2)
// (x1,y1)为控制点距离起点的偏移量。(x2,y2)为终点距离起点的偏移量
rQuadTo(float x1, float y1, float x2, float y2) // 绘制三阶贝塞尔曲线
// (x1,y1),(x2,y2)为控制点,(x3,y3)为终点
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
// (x1,y1)。(x2,y2)为控制点距离起点的偏移量,(x3,y3)为终点距离起点的偏移量
rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)

此处仅仅简单介绍贝塞尔曲线,想详细理解能够參考这篇文章


5. 总结


请帮顶或评论点赞!

由于你们的赞同/鼓舞是我写作的最大动力!

Path类的最全面具体解释 - 自己定义View应用系列的更多相关文章

  1. 使用File类、StreamRead和StreamWrite读写数据、以及Path类操作文件路径和Directory

    1.File类的概念: File类,是一个静态类,主要是来提供一些函数库用的.静态实用类,提供了很多静态的方法,支持对文件的基本操作,包括创建,拷贝,移动,删除和 打开一个文件. File类方法的参量 ...

  2. Android中Path类的lineTo方法和quadTo方法画线的区别

    转载:http://blog.csdn.net/stevenhu_223/article/details/9229337 当我们需要在屏幕上形成画线时,Path类的应用是必不可少的,而Path类的li ...

  3. .net学习之集合、foreach原理、Hashtable、Path类、File类、Directory类、文件流FileStream类、压缩流GZipStream、拷贝大文件、序列化和反序列化

    1.集合(1)ArrayList内部存储数据的是一个object数组,创建这个类的对象的时候,这个对象里的数组的长度为0(2)调用Add方法加元素的时候,如果第一次增加元神,就会将数组的长度变为4往里 ...

  4. Android -- 自定义View小Demo,关于Path类的使用(一)

    1,在我们知道自定义view中onDraw()方法是用于绘制图形的,而Path类则是其中的一个重要的类,如下图效果: 代码也没有什么难度,直接贴出来吧 @Override protected void ...

  5. Java 7 中 NIO.2 的使用——第一节 Path 类的使用

    路径隶属于文件系统,实际上它是存储和组织媒体文件的格式,通常在一块或多块硬盘设备上,以便于非常容易地检索.文件系统可以通过  java.nio.file.FileSystems 这个final 类来访 ...

  6. 文件夹和文件、Path类、流、序列化

    循环访问目录树 参考: http://msdn.microsoft.com/zh-cn/library/bb513869.aspx 循环访问目录树”的意思是在指定的根文件夹下,访问每个嵌套子目录中任意 ...

  7. C# IO操作(一)Path类的常用方法

    1.Path类,查看编译器可知,这个类是个静态的工具类,需要注意的是,这个类是对字符串的操作,与文件无关. 1)ChangeExtension()方法,修改文件的后缀(调用这个方法,如果给第二个参数制 ...

  8. Path类

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.I ...

  9. Android开发之Path类使用详解,自绘各种各样的图形!

    玩过自定义View的小伙伴都知道,在View的绘制过程中,有一个类叫做Path,Path可以帮助我们实现很多自定义形状的View,特别是配合xfermode属性来使用的时候.OK,那我们今天就来看看P ...

随机推荐

  1. ubuntu-软件解压方法(转载)

    一下内容转载自 http://blog.csdn.net/zad522/article/details/2770446 今天用到了ubuntu解压,所以就在网上查找了下方法.自己菜鸟一枚,收录别人的文 ...

  2. android设置Activity背景色为透明的3种方

    方法一:这种方法比较简单,只有一个步骤,只需要在配置文件中把需要设置为透明的activity的样式设置为 Android:theme="@android:style/Theme.Transl ...

  3. iterm恢复默认设置

    命令行执行以下命令即可: defaults delete com.googlecode.iterm2

  4. python登录验证程序

    自己写的一个python登录验证程序: 基础需求: 让用户输入用户名密码 认证成功后显示欢迎信息 输错三次后退出程序 升级需求: 可以支持多个用户登录 (提示,通过列表存多个账户信息) 用户3次认证失 ...

  5. idea 配置文件导出,导入

    俗话说的好,磨刀不误砍柴工.配置好自己的工具,这样撸码就会更爽. 来来来,傻瓜式配图开始. 点击后会出现有一个导出设置框默认为全部导出 点击...处 可设置导出的settings.jar包的位置 在新 ...

  6. [Angular] Progress HTTP Events with 'HttpRequest'

    New use case that is supported by the HTTP client is Progress events. To receive these events, we cr ...

  7. 一个DDOS病毒的分析(一)

    一.基本信息 样本名称:Rub.EXE 样本大小:21504 字节 病毒名称:Trojan.Win32.Rootkit.hv 加壳情况:UPX(3.07) 样本MD5:035C1ADA4BACE78D ...

  8. JS实现放大镜效果(放大图片)

    注意:里边的两张图片(一大一小)可以自己添加,JQ采用jquery-1.11.3.js版,也可自行调换. HTML代码: <!DOCTYPE html> <html> < ...

  9. call.apply.冒充对象继承

    call方法:让调用对象执行,然后第一参数是谁.调用对象的this就改变,指向谁,后边跟参数,依次对应传入 apply方法:让调用对象执行,然后第一参数是谁.调用对象的this就改变指向是谁,后边跟参 ...

  10. JS学习笔记 - fgm练习 - 多按钮控制同个div属性

    总结: 1. 注意body里的结构安排,全部装在大div,避免多次设置不同部分居中. 2. 一排按钮居中:装在大div里,text-align: center; 3. 把相同的部分封装成函数,即 同个 ...