Path是一个工具类,用来记录线条的轨迹路径,然后通过绘制轨迹路径,可以得到各种各样的图案,而PathMeasure是用来对Path进行测量的工具,再Path的运用中,运用最多的就是贝塞尔曲线,也是本文的重点

贝塞尔曲线

贝塞尔曲线就是这样的一条曲线,它是依据四个位置任意的点坐标绘制出的一条光滑曲线

线性公式

给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:



且其等同于线性插值

二次方公式

二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:



TrueType字型就运用了以贝兹样条组成的二次贝兹曲线

三次方公式

P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”

曲线的参数形式为:



现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓

一般参数公式

阶贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即:



如上公式可如下递归表达: 用表示由点P0、P1、…、Pn所决定的贝兹曲线

公式说明

  1. 开始于P0并结束于Pn的曲线,即所谓的端点插值法属性
  2. 曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝塞尔曲线是直线的充分必要条件是控制点共线
  3. 曲线的起始点(结束点)相切于贝塞尔多边形的第一节(最后一节)
  4. 一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝塞尔曲线
  5. 一些看似简单的曲线(如圆)无法以贝塞尔曲线精确的描述,或分段成贝塞尔曲线(虽然当每个内部控制点对单位圆上的外部控制点水平或垂直的的距离为时,分成四段的贝兹曲线,可以小于千分之一的最大半径误差近似于圆)
  6. 位于固定偏移量的曲线(来自给定的贝塞尔曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝兹曲线精确的形成(某些琐屑实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值

Android中的贝塞尔曲线使用

首先得到贝塞尔曲线的图像及要素

然后使用Path绘制

Path工具类

Path path = new Path();

二阶贝塞尔:其参数第一对是控制点,第二对是结束点

path.quadTo();

e.g.

path.moveTo(100, 400);
path.quadTo(200, 0, 500, 400);
path.quadTo(700, 600, 900, 400);
canvas.drawPath(path, paint);

三阶贝塞尔

path.cubicTo();

e.g.

path.moveTo(100, 1000);
path.cubicTo(300, 900, 600, 1200, 900, 1000);
canvas.drawPath(path, paint);

生成二阶及三阶贝塞尔曲线如下图

贝塞尔曲线实现波形图

onDraw()里面绘制

int waveLen = 200;
int originY = 400;
path.moveTo(-waveLen, originY);
for (int i = -waveLen; i < getWidth() + waveLen; i += waveLen) {
//使用quadTo,绝对位置
//path.quadTo(i + waveLen / 4, 600, i + waveLen / 2, originY);
//path.quadTo(i + waveLen * 3 / 4, 200, i + waveLen, originY);
//canvas.drawPath(path, paint);
//使用rQuadTo,相对位置
path.rQuadTo(waveLen / 4, 200, waveLen / 2, 0);
path.rQuadTo(waveLen / 4, -200, waveLen / 2, 0);
}
canvas.drawPath(path, paint);

实现效果如下



最后封闭空间

int waveLen = 200;
int originY = 400;
path.moveTo(-waveLen, originY);
for (int i = -waveLen; i < getWidth() + waveLen; i += waveLen) {
//使用quadTo,绝对位置
//path.quadTo(i + waveLen / 4, 600, i + waveLen / 2, originY);
//path.quadTo(i + waveLen * 3 / 4, 200, i + waveLen, originY);
//canvas.drawPath(path, paint);
//使用rQuadTo,相对位置
path.rQuadTo(waveLen / 4, 200, waveLen / 2, 0);
path.rQuadTo(waveLen / 4, -200, waveLen / 2, 0);
}
path.lineTo(getWidth(), getHeight());
path.lineTo(0, getHeight());
path.close();
canvas.drawPath(path, paint);

得到图像如下



实现将波纹动起来这里贴出整个自定义View代码

public class WaveView extends View {
private static final String TAG = "cj5785";
private Path path;
private Paint paint;
private int waveLen = 200;
private int dx; public WaveView(Context context) {
super(context);
init();
} private void init() {
path = new Path();
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(8);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//波形
int originY = 400;
path.reset();
path.moveTo(-waveLen + dx, originY);
for (int i = -waveLen; i < getWidth() + waveLen; i += waveLen) {
//使用quadTo
// path.quadTo(i + waveLen / 4, 600, i + waveLen / 2, originY);
// path.quadTo(i + waveLen * 3 / 4, 200, i + waveLen, originY);
// canvas.drawPath(path, paint);
//使用rQuadTo
path.rQuadTo(waveLen / 4, 200, waveLen / 2, 0);
path.rQuadTo(waveLen / 4, -200, waveLen / 2, 0);
}
//封闭空间
path.lineTo(getWidth(), getHeight());
path.lineTo(0, getHeight());
path.close();
canvas.drawPath(path, paint);
} public void startAnimation() {
ValueAnimator animator = ValueAnimator.ofInt(0, waveLen);
animator.setDuration(1000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (int)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
}

PathMeasure

顾名思义,PathMeasure是一个用来测量Path的类,主要有以下方法

构造方法

方法名 释义
`PathMeasure()` 创建一个空的`PathMeasure`
`PathMeasure(Path path, boolean forceClosed)` 创建`PathMeasure`并关联一个指定的Path(Path需要已经创建完成)
  • 无参构造函数:PathMeasure ()

    用这个构造函数可创建一个空的PathMeasure,但是使用之前需要先调用setPath方法来与Path进行关联。被关联的Path必须是已经创建好的,如果关联之后Path内容进行了更改,则需要使用setPath方法重新关联

  • 有参构造函数:PathMeasure (Path path, boolean forceClosed)

    用这个构造函数是创建一个PathMeasure并关联一个Path, 其实和创建一个空的PathMeasure后调用setPath进行关联效果是一样的,同样,被关联的Path也必须是已经创建好的,如果关联之后Path内容进行了更改,则需要使用setPath方法重新关联

    该方法有两个参数,第一个参数自然就是被关联的Path了,第二个参数是用来确保Path闭合,如果设置为true,则不论之前Path是否闭合,都会自动闭合该Path(如果Path可以闭合的话)

  • 在这里有两点需要明确

    • 不论forceClosed设置为何种状态(true或者false), 都不会影响原有Path的状态,即PathPathMeasure关联之后,之前的的Path不会有任何改变
    • forceClosed的设置状态可能会影响测量结果,如果Path未闭合但在与PathMeasure关联的时候设置forceClosedtrue时,测量结果可能会比Path实际长度稍长一点,获取到到是该Path闭合时的状态

公共方法

返回值 方法名 释义
`void` `setPath(Path path, boolean forceClosed)` 关联一个Path
`boolean` `isClosed()` 是否闭合
`float` `getLength()` 获取Path的长度
`boolean` `nextContour()` 跳转到下一个轮廓
`boolean` `getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)` 截取片段
`boolean` `getPosTan(float distance, float[] pos, float[] tan)` 获取指定长度的位置坐标及该点切线值
`boolean` `getMatrix(float distance, Matrix matrix, int flags)` 获取指定长度的位置坐标及该点
  • setPathPathMeasurePath关联的重要方法,效果和构造函数中两个参数的作用是一样的

  • isClosed用于判断Path是否闭合,但是如果你在关联Path的时候设置forceClosedtrue的话,这个方法的返回值则一定为true

  • getLength用于获取Path的总长度

  • getSegment用于获取Path的一个片段

参数 作用 备注
`返回值(boolean)` 判断截取是否成功 `true`表示截取成功,结果存入`dst`中,`false`截取失败,不会改变`dst`中内容
`startD` 开始截取位置距离`Path`起点的长度 取值范围:`0 <= startD < stopD <= Path`总长度
`stopD` 结束截取位置距离`Path`起点的长度 取值范围:`0 <= startD < stopD <= Path`总长度
`dst` 截取的 Path 将会添加到`dst`中 注意: 是添加,而不是替换
`startWithMoveTo` 起始点是否使用`moveTo` 用于保证截取的`Path`第一个点位置不变

1. 如果`startD`、`stopD`的数值不在取值范围`[0, getLength]`内,或者`startD == stopD`则返回值为`false`,不会改变`dst`内容
2. 如果在安卓4.4或者之前的版本,在默认开启硬件加速的情况下,更改`dst`的内容后可能绘制会出现问题,请关闭硬件加速或者给`dst`添加一个单个操作,例如:`dst.rLineTo(0, 0)`
3. 可以用以下规则来判断`startWithMoveTo`的取值

取值 主要功用
`true` 保证截取得到的`Path`片段不会发生形变
`false` 保证存储截取片段的`Path(dst)`的连续性
  • nextContour

    我们知道Path可以由多条曲线构成,但不论是getLength,getgetSegment或者是其它方法,都只会在其中第一条线段上运行,而这个nextContour就是用于跳转到下一条曲线到方法,如果跳转成功,则返回true,如果跳转失败,则返回false

  • getPosTan这个方法是用于得到路径上某一长度的位置以及该位置的正切值:

参数 作用 备注
`返回值(boolean)` 判断获取是否成功 `true`表示成功,数据会存入`pos`和`tan`中,`false`表示失败,`pos`和`tan`不会改变
`distance` 距离`Path`起点的长度 取值范围: `0 <= distance <= getLength`
`pos` 该点的坐标值 坐标值: `(x==[0], y==[1])`
`tan` 该点的正切值 正切值:` (x==[0], y==[1])`
  • getMatrix 这个方法是用于得到路径上某一长度的位置以及该位置的正切值的矩阵
参数 作用 备注
`返回值(boolean)` 判断获取是否成功 `true`表示成功,数据会存入`matrix`中,`false`失败,`matrix`内容不会改变
`distance` 距离`Path`起点的长度 取值范围: `0 <= distance <= getLength`
`matrix` 根据`falgs`封装好的`matrix` 会根据`flags`的设置而存入不同的内容
`flags` 规定哪些内容会存入到`matrix`中 可选择`POSITION_MATRIX_FLAG(位置) ` `ANGENT_MATRIX_FLAG(正切)`

使用示例

  • 构造方法
path.reset();
path.lineTo(0, 400);
path.lineTo(400, 400);
path.lineTo(400, 0);
PathMeasure measure1 = new PathMeasure(path, false);
PathMeasure measure2 = new PathMeasure(path, true);
Log.d(TAG, "onDraw: measure1=" + measure1.getLength());
Log.d(TAG, "onDraw: measure2=" + measure2.getLength());
canvas.drawPath(path, paint);

打印

D/cj578: onDraw: measure1=1200.0
D/cj578: onDraw: measure2=1600.0
  • getLength()nextContour()

    多路径需要关闭硬件加速
path.reset();
path.addRect(-300, -300, 300, 300, Path.Direction.CCW);
path.addRect(-200, -200, 200, 200, Path.Direction.CCW);
path.addRect(-100, -100, 100, 100, Path.Direction.CCW);
canvas.drawPath(path, paint);
PathMeasure measure = new PathMeasure(path, false);
float len1 = measure.getLength();
Log.d(TAG, "onDraw: len1=" + len1);
int i = 2;
while (measure.nextContour()) {
float len = measure.getLength();
Log.d(TAG, "onDraw: len" + i + "=" + len);
i++;
}

打印

D/cj578: onDraw: len1=2400.0
D/cj578: onDraw: len2=1600.0
D/cj578: onDraw: len3=800.0
  • getSegment()截取片断
path.reset();
path.addRect(-300, -300, 300, 300, Path.Direction.CCW);
PathMeasure measure = new PathMeasure(path, false);
Path dst = new Path();
measure.getSegment(0, 1600, dst, true);
canvas.drawPath(dst, paint);
  • getPosTan()获取位置和正切
path.reset();
path.addCircle(0, 0, 300, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float[] pos = new float[2];
float[] tan = new float[2];
measure.getPosTan(measure.getLength() / 4, pos, tan);
Log.d(TAG, "onDraw: pos-x:" + pos[0] + ",pos-y:" + pos[1]);
Log.d(TAG, "onDraw: tan-x:" + tan[0] + ",tan-y:" + tan[1]);
canvas.drawPath(path, paint);

打印

D/cj578: onDraw: pos-x:4.2605415E-4,pos-y:300.0
D/cj578: onDraw: tan-x:-1.0,tan-y:1.4448632E-6

高级UI-Path和PathMeasure的更多相关文章

  1. firefox 扩展开发笔记(三):高级ui交互编程

    firefox 扩展开发笔记(三):高级ui交互编程 前言 前两篇链接 1:firefox 扩展开发笔记(一):jpm 使用实践以及调试 2:firefox 扩展开发笔记(二):进阶开发之移动设备模拟 ...

  2. Android 高级UI设计笔记07:RecyclerView 的详解

    1. 使用RecyclerView       在 Android 应用程序中列表是一个非常重要的控件,适用场合非常多,如新闻列表.应用列表.消息列表等等,但是从Android 一出生到现在并没有非常 ...

  3. 高级UI晋升之常用View(三)中篇

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从ViewPager来介绍常用View:文章目录 一.简介 二.基本使用 ...

  4. 高级UI晋升之View渲染机制(二)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 优化性能一般从渲染,运算与内存,电量三个方面进行,今天开始说聊一聊Android ...

  5. iOS开发——高级UI&带你玩转UITableView

    带你玩装UITableView 在实际iOS开发中UITableView是使用最多,也是最重要的一个控件,如果你不会用它,那别说什么大神了,菜鸟都不如. 其实关于UItableView事非常简单的,实 ...

  6. Android 高级UI设计笔记06:仿微信图片选择器(转载)

    仿微信图片选择器: 一.项目整体分析: 1. Android加载图片的3个目标: (1)尽可能的去避免内存溢出. a. 根据图片的显示大小去压缩图片 b. 使用缓存对我们图片进行管理(LruCache ...

  7. 高级UI特效—用SVG码造一个精美的中国地图

    前言 来继续学习SVG,要想深入了解还是要多动手进行实战.关于svg基础可以去看一下我的上一篇文章<SVG前戏—让你的View多姿多彩>,今天就用SVG打造一个精美的UI效果. 正文 先上 ...

  8. 高级UI晋升之自定义View实战(六)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从Android 自定义属性动画&Camera动画来介绍自定义V ...

  9. 高级UI晋升之自定义View实战(五)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从自定义View利器Canvas和Paint来进行详解 一.Canvas ...

  10. 高级UI晋升之布局ViewGroup(四)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从LinearLayout.RelativeLayout.FrameLa ...

随机推荐

  1. StringTokenizer字符串分解器

    示例: StringTokenizer st = new StringTokenizer(key, ",", false); while (st.hasMoreTokens()) ...

  2. Markdown 设置字体大小颜色及背景色

    一.更改字体.大小.颜色 <font face="黑体">我是黑体字</font><font face="微软雅黑">我是微 ...

  3. AtCoder Beginner Contest 132 解题报告

    前四题都好水.后面两道题好难. C Divide the Problems #include <cstdio> #include <algorithm> using names ...

  4. LeetCode 1099. Two Sum Less Than K

    原题链接在这里:https://leetcode.com/problems/two-sum-less-than-k/ 题目: Given an array A of integers and inte ...

  5. codeforces B. Make Them Odd -C++stl之set的使用

    B. Make Them Odd There are nn positive integers a1,a2,…,ana1,a2,…,an. For the one move you can choos ...

  6. java大附件上传,支持断点续传

    一. 功能性需求与非功能性需求 要求操作便利,一次选择多个文件和文件夹进行上传:支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传.刷新页面后继续传输. ...

  7. learning java 重定向标准输入输出

    output redirectionOut: public class RedirectOut { public static void main(String[] args) throws File ...

  8. 集成omnibus-ctl+ chef 制作一个可配置的软件包

    前边有写过使用omnibus-ctl 制作软件包的,但是当时没有集成chef,只有一个空壳子,实际上omnibus-ctl 已经内置 了对于chef 的操作(但是我们还需要在添加一个依赖),以下简单说 ...

  9. graphql-hooks hooks first 的graphql 客户端

    graphql-hooks 是一个hooks first 的graphql 客户端,支持一一些特性 首类hooks api 比较小(5.3Kb) gzip 1.8 kb 完整支持ssr (通过grap ...

  10. 81: luogu3370 hash

    hash 模板题 #include <bits/stdc++.h> using namespace std; #define ULL unsigned long long const UL ...