上一章讲解了如何通过多个 Path 叠加形成笔锋效果,还有另外的方式实现笔锋,并且只需要一条Path就可以了。在讲解具体方案之前,我们需要了解一个有意思的工具 PathMeasure ,这是一个非常强大且实用的工具,常用于高级动画和路径绘制。

一、PathMeasure 介绍

它的主要作用是对一个已有的 Path 对象进行测量,从而获取该路径的详细信息,例如:

  • 路径的总长度

  • 路径上任意位置(从起点开始的距离)的坐标点 (x, y) 和切线角度 (tangent)

  • 截取原始路径的某一段,生成一个新的 Path 片段。

正因为能获取到路径上每个点的精确位置和方向,它成为了实现各类轨迹动画(如飞机沿航线飞行、箭头沿曲线移动)的核心组件。网上的很多案例,可以搜索看看,我在这里对其中的方法简单介绍下。

(1)构造方法

方法名释义
PathMeasure()创建一个空的PathMeasure
PathMeasure(Path path, boolean forceClosed)创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。
  • 无参构造函数:可创建一个空的 PathMeasure,但是使用之前需要先调用 setPath 方法来与 Path 进行关联。被关联的 Path 必须是已经创建好的。如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联。
  • 有参构造函数: forceClosed:用来确保 Path 闭合,如果设置为 true, 则不论之前Path是否闭合,都会自动闭合该 Path(如果Path可以闭合的话);

(2)setPath方法

void setPath(Path path, boolean forceClosed)

作用:此方法是 PathMeasure 与 Path 关联的重要方法,效果和构造函数中两个参数的作用是一样的。

(3)isClosed方法

boolean isClosed()

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

(4)getLength方法

float getLength()

作用:此方法用于获取 Path 路径的总长度

(5)nextContour方法

boolean nextContour()

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

(6)getSegment方法

boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
  • 返回值boolean:判断截取是否成功(true 表示截取成功,结果存入dst中,false 截取失败,不会改变dst中内容);
  • float startD:开始截取位置距离 Path 起点的长度(取值范围: 0 <= startD < stopD <= Path总长度);
  • float stopD:结束截取位置距离 Path 起点的长度(取值范围: 0 <= startD < stopD <= Path总长度);
  • Path dst:截取的 Path 将会添加到 dst 中(注意: 是添加,而不是替换);
  • boolean startWithMoveTo:起始点是否使用 moveTo,用于保证截取的 Path 第一个点位置不变(true表示保证截取得到的 Path 片段不会发生形变,false表示保证存储截取片段的 Path(dst) 的连续性);

作用:用于获取Path路径的一个片段。(如果 startD、stopD 的数值不在取值范围 [0, getLength] 内,或者 startD == stopD 则返回值为 false,不会改变 dst 内容)。


(7)getPosTan方法

boolean getPosTan(float distance, float[] pos, float[] tan)
  • 返回值(boolean):判断获取是否成功(true表示成功,数据会存入 pos 和 tan 中,false 表示失败,pos 和 tan 不会改变);
  • float distance:距离 Path 起点的长度 取值范围: 0 <= distance <= getLength
  • float[] pos:该点的坐标值,坐标值: (x==[0], y==[1])
  • float[] tan:该点的正切值,正切值: (x==[0], y==[1])

作用:用于获取路径上某点的坐标以及该位置的正切值,即切线的坐标。相当于是getPosgetTan两个API的集合。

(8)getMatrix方法

boolean getMatrix(float distance, Matrix matrix, int flags) 
  • 返回值(boolean):判断获取是否成功(true表示成功,数据会存入matrix中,false 失败,matrix内容不会改变);
  • float distance:距离 Path 起点的长度(取值范围: 0 <= distance <= getLength);
  • Matrix matrix:根据 falgs 封装好的matrix,会根据 flags 的设置而存入不同的内容;
  • int flags:规定哪些内容会存入到matrix中(可选择POSITION_MATRIX_FLAG位置 、ANGENT_MATRIX_FLAG正切 );

作用:用于得到路径上某一长度的位置以及该位置的正切值的矩阵

二、单 Path 笔锋(画点成线)

        这个要使用上面的所提到的工具 PathMeasure ,接下来用一个简单的例子看看如何使用。

(1)效果图1

这是一个宽度渐变的曲线,同时通过画点成线绘制曲线

(2)代码1

public class GradientPointLineView extends View {private Paint paint;private Path path;private List<PointF> points;public GradientPointLineView(Context context) {super(context);init();}public GradientPointLineView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}private void init() {paint = new Paint(Paint.ANTI_ALIAS_FLAG);paint.setStyle(Paint.Style.FILL);path = new Path();points = new ArrayList<>();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (points.size() < 2) return;// 创建路径path.reset();path.moveTo(points.get(0).x, points.get(0).y);for (int i = 1; i < points.size(); i++) {path.lineTo(points.get(i).x, points.get(i).y);}//绘制所有的点,然后再每个点渐变// 使用 PathMeasure 计算路径上的点PathMeasure pathMeasure = new PathMeasure(path, false);//float pathLength = pathMeasure.getLength();// 设置最大和最小点的大小float maxRadius = 20f;float minRadius = 5f;// 沿路径绘制渐变大小的点float distance = 0;float step = 5f; // 点之间的间隔(像素)while (distance < pathLength) {float[] pos = new float[2];float[] tan = new float[2];pathMeasure.getPosTan(distance, pos, tan);// 计算当前点的半径(根据距离起点位置渐变)float progress = distance / pathLength;float radius = maxRadius - (maxRadius - minRadius) * progress;// 绘制圆点canvas.drawCircle(pos[0], pos[1], radius, paint);distance += step;}// 确保终点被绘制float[] endPos = new float[2];pathMeasure.getPosTan(pathLength, endPos, null);canvas.drawCircle(endPos[0], endPos[1], minRadius, paint);}public void addPoint(float x, float y) {points.add(new PointF(x, y));invalidate();}public void clearPoints() {points.clear();invalidate();}
}
    • 通过PathMeasure计算整个曲线的长度,每过 5 像素就画一个点

    (3)效果图2

    之前的效果还可以得到提升,增加贝赛尔曲线和颜色渐变的操作。

    (4)代码2

    public class SmoothQuadBezierView extends View {private Paint paint;private Path path;private List<PointF> points;private float maxRadius = 20f;private float minRadius = 2f;private int startColor = Color.RED;private int endColor = Color.BLUE;public SmoothQuadBezierView(Context context) {super(context);init();}public SmoothQuadBezierView(Context context, AttributeSet attrs) {super(context, attrs);init();}private void init() {paint = new Paint(Paint.ANTI_ALIAS_FLAG);paint.setStyle(Paint.Style.FILL);path = new Path();points = new ArrayList<>();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (points.size() < 2) return;// 创建平滑的二次贝塞尔曲线路径path.reset();createSmoothQuadPath();// 测量路径长度PathMeasure pathMeasure = new PathMeasure(path, false);float pathLength = pathMeasure.getLength();// 沿路径绘制渐变点drawGradientPoints(canvas, pathMeasure, pathLength);// 绘制明显的起点和终点//drawEndPoints(canvas);}/*** 创建更平滑的二次贝塞尔曲线路径*/private void createSmoothQuadPath() {path.moveTo(points.get(0).x, points.get(0).y);if (points.size() == 2) {// 两个点:使用中间控制点PointF p0 = points.get(0);PointF p1 = points.get(1);float controlX = (p0.x + p1.x) / 2;float controlY = (p0.y + p1.y) / 2;path.quadTo(controlX, controlY, p1.x, p1.y);} else {// 多个点:创建连续平滑曲线for (int i = 1; i < points.size(); i++) {PointF prev = points.get(i - 1);PointF current = points.get(i);if (i == 1) {// 第一个线段float controlX = prev.x + (current.x - prev.x) * 0.5f;float controlY = prev.y + (current.y - prev.y) * 0.5f;path.quadTo(controlX, controlY, current.x, current.y);} else if (i == points.size() - 1) {// 最后一个线段PointF prevPrev = points.get(i - 2);float controlX = prev.x + (prev.x - prevPrev.x) * 0.25f;float controlY = prev.y + (prev.y - prevPrev.y) * 0.25f;path.quadTo(controlX, controlY, current.x, current.y);} else {// 中间线段:使用前后点计算更平滑的控制点PointF next = points.get(i + 1);float controlX = current.x - (next.x - prev.x) * 0.25f;float controlY = current.y - (next.y - prev.y) * 0.25f;path.quadTo(controlX, controlY, current.x, current.y);}}}}/*** 沿路径绘制渐变点*/private void drawGradientPoints(Canvas canvas, PathMeasure pathMeasure, float pathLength) {float distance = 0;float step = 3f;while (distance < pathLength) {float[] pos = new float[2];pathMeasure.getPosTan(distance, pos, null);float progress = distance / pathLength;float radius = calculateRadius(progress);int color = calculateColor(progress);paint.setColor(color);canvas.drawCircle(pos[0], pos[1], radius, paint);distance += step;}}/*** 绘制起点和终点*/private void drawEndPoints(Canvas canvas) {// 起点paint.setColor(startColor);canvas.drawCircle(points.get(0).x, points.get(0).y, maxRadius + 5, paint);// 终点paint.setColor(endColor);canvas.drawCircle(points.get(points.size() - 1).x,points.get(points.size() - 1).y,minRadius + 2,paint);}private float calculateRadius(float progress) {// 使用缓动函数float easedProgress = (float) (1 - Math.pow(1 - progress, 1.5));return maxRadius - (maxRadius - minRadius) * easedProgress;}private int calculateColor(float progress) {return interpolateColor(startColor, endColor, progress);}private int interpolateColor(int color1, int color2, float factor) {float inverseFactor = 1 - factor;return Color.argb((int) (Color.alpha(color1) * inverseFactor + Color.alpha(color2) * factor),(int) (Color.red(color1) * inverseFactor + Color.red(color2) * factor),(int) (Color.green(color1) * inverseFactor + Color.green(color2) * factor),(int) (Color.blue(color1) * inverseFactor + Color.blue(color2) * factor));}public void addPoint(float x, float y) {points.add(new PointF(x, y));invalidate();}public void clearPoints() {points.clear();path.reset();invalidate();}public void setColors(int startColor, int endColor) {this.startColor = startColor;this.endColor = endColor;}
    }

    看着效果还可以,我在这里画的是圆,当然也可以绘制其他的形状来实现笔锋效果,比如说线和图片。

    画线效果:

    画图片效果:可以达到类似水彩笔的效果

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    如若转载,请注明出处:http://www.pswp.cn/news/921833.shtml
    繁体地址,请注明出处:http://hk.pswp.cn/news/921833.shtml
    英文地址,请注明出处:http://en.pswp.cn/news/921833.shtml

    如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    从C++开始的编程生活(7)——取地址运算符重载、类型转换、static成员和友元

    前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第7篇主要讲的是有关于C的取地址运算符重载、类型转换、static成员和友元。 C才起步&#xff0c;都很简单 目录 前言 取地址运算符重载 const成员函数 基本语法 特点 取地址运算符重载 类型转换…

    SQL 入门指南:排序与分页查询(ORDER BY 多字段排序、LIMIT 分页实战)

    在 SQL 查询中&#xff0c;我们常需要 “按报名时间先后看活动名单”“只看第 2 页的活动报名数据”—— 这些需求靠基础查询无法实现&#xff0c;而ORDER BY&#xff08;排序&#xff09; 和LIMIT&#xff08;分页&#xff09; 就是解决这类问题的核心工具。今天我们用 “校园…

    jodconverter将word转pdf底层libreoffice的问题

    近期项目中使用了word转pdf的功能&#xff0c;其中借助了远程服务的jodconverter来处理。 <dependency><groupId>org.jodconverter</groupId><artifactId>jodconverter-remote</artifactId><version>4.4.2</version> </dependen…

    【为YOLOv11Seg添加MFC界面】详细指南

    要将现有的YOLOv11Seg代码集成到MFC界面中,我们需要创建一个MFC应用程序框架,并将现有的检测逻辑封装到其中。以下是详细步骤: 1. 创建MFC应用程序框架 1.1 使用Visual Studio创建MFC项目 打开Visual Studio,选择"创建新项目" 选择"MFC应用程序"模板…

    机器学习03——线性模型(多元线性回归、对数线性回归、二分类、线性判别分析)

    上一章&#xff1a;机器学习02——模型评估与选择 下一章&#xff1a;机器学习04——决策树 机器学习实战项目&#xff1a;【从 0 到 1 落地】机器学习实操项目目录&#xff1a;覆盖入门到进阶&#xff0c;大学生就业 / 竞赛必备 文章目录一、线性模型的基本形式&#xff08;一…

    qt QLineSeries详解

    1、概述QLineSeries是Qt Charts模块中的一个重要类&#xff0c;用于绘制折线图。它是QXYSeries的实现类&#xff0c;将信息显示为由直线连接的一系列数据点。该类为QAbstractSeries的子类&#xff0c;因此可以通过该类来访问QAbstractSeries的所有公共方法和属性。2、重要方法c…

    你再也找不到更详细的3DGS教程了 —— 一万九千字长文解析3DGS

    参考: https://www.bilibili.com/video/BV1MF4m1V7e3/ https://blog.csdn.net/2401_86810419/article/details/148811121 https://www.bilibili.com/video/BV1cz421872F?t=233.9 https://wuli.wiki/online/SphHar.html https://zhuanlan.zhihu.com/p/467466131 特别指出…

    Python,遗传算法与神经网络架构搜索:基于DEAP的自动模型设计

    引言&#xff1a;当进化论遇见深度学习——自动化的黎明在深度学习的蛮荒时代&#xff0c;我们是“手工匠人”。我们依靠直觉、前辈的经验&#xff08;ResNet 为什么是152层而不是153层&#xff1f;&#xff09;、大量的试错以及那么一点点玄学&#xff0c;在架构的黑暗森林中摸…

    Vue框架技术详解——项目驱动概念理解【前端】【Vue】

    Vue3框架 是前端渲染框架浏览器向服务器第一次发送请求&#xff0c;就会将所有页面的样式全部返回到浏览器vue中会将所有js文件最后打包成一个js文件&#xff0c;当前访问其中一个页面时&#xff0c;其他页面的样式也已经返回到浏览器中了&#xff0c;下次切换页面时&#xff…

    HTML 网页静态托管 API 接口文档(可集成到智能体Agent)

    HTML 网页静态托管 API 接口文档&#xff08;可集成到智能体Agent&#xff09; 接口概述 本接口用于将HTML代码转换为可访问的网页&#xff0c;支持通过API密钥进行身份验证。 API 密钥申请地址&#xff1a; https://www.cuobiezi.net/user/api_keys/apply API接口信息 接…

    springboot vue sse消息推送,封装系统公共消息推送前后端方法

    概述 1、封装springboot全局的消息推送接口&#xff1b; 注&#xff1a;1&#xff09;由于原生HTML5 EventSource 不支持添加header&#xff0c;所以要把连接创建接口加入身份验证白名单&#xff0c;并在接口内添加自己校验token2&#xff09;后台需定时心跳&#xff0c;保证链…

    LeetCode 每日一题 2025/9/1-2025/9/7

    记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录9/1 1792. 最大平均通过率9/2 3025. 人员站位的方案数 I9/3 3027. 人员站位的方案数 II9/4 3516. 找到最近的人9/5 2749. 得到整数零需要执行的最少操作数9/6 3495. 使数组元…

    小迪安全v2023学习笔记(八十讲)—— 中间件安全WPS分析WeblogicJenkinsJettyCVE

    文章目录前记服务攻防——第八十天中间件安全&HW2023-WPS分析&Weblogic&Jetty&Jenkins&CVE应用WPS - HW2023-RCE&复现&上线CS介绍漏洞复现中间件 - Weblogic-CVE&反序列化&RCE介绍利用中间件 - Jenkins-CVE&RCE执行介绍漏洞复现CVE-20…

    各webshell管理工具流量分析

    哥斯拉哥斯拉是一个基于流量、HTTP全加密的webshell管理工具 特点 1.内置了3种Payload以及6种加密器&#xff0c;6种支持脚本后缀&#xff0c;20个内置插件 2.基于java&#xff0c;可以跨平台使用 3.可以自己生成webshell&#xff0c;根据管理来生成一些payload&#xff0c;然后…

    pytest(1):fixture从入门到精通

    pytest&#xff08;1&#xff09;&#xff1a;fixture从入门到精通前言1. Fixture 是什么&#xff1f;为什么我们需要它&#xff1f;2. 快速上手&#xff1a;第一个 Fixture 与基本用法3. 作用域 (Scope)&#xff1a;控制 Fixture 的生命周期4. 资源管理&#xff1a;Setup/Tear…

    Java17 LTS 新特性用例

    基于 Java 17 LTS 的 实用示例 以下是基于 Java 17 LTS 的 30 个实用示例,涵盖语言新特性、API 改进及常见场景。所有代码均兼容 Java 17 语法规范。 文本块(Text Blocks) String json = """{"name": "Java 17","type": &qu…

    SpringBoot-Web开发-内容协商——多端内容适配内容协商原理HttpMessageConverter

    其它篇章&#xff1a; 一&#xff1a;SpringBoot3-日志——日志原理&日志格式&日志级别&日志分组&文件输出&文件归档&滚动切割 二&#xff1a;SpringBoot3-Web开发-静态资源——WebMvcAutoConfiguration原理&资源映射&资源缓存&欢迎页&…

    Spring MVC 类型转换与参数绑定:从架构到实战

    在 Spring MVC 开发中&#xff0c;“前端请求数据” 与 “后端 Java 对象” 的格式差异是高频痛点 —— 比如前端传的String类型日期&#xff08;2025-09-08&#xff09;要转成后端的LocalDate&#xff0c;或者字符串male要转成GenderEnum.MALE枚举。Spring 并非通过零散工具解…

    Spark提交任务的资源配置和优化

    Spark 提交任务时主要可调的资源配置参数包括 Driver 资源&#xff08;内存、CPU&#xff09;、Executor 资源&#xff08;数量、内存、CPU&#xff09;以及 集群管理相关参数。配置和优化时一般结合集群硬件资源、数据规模、作业类型和作业复杂度&#xff08;SQL / 机器学习&a…

    机器学习06——支持向量机(SVM核心思想与求解、核函数、软间隔与正则化、支持向量回归、核方法)

    上一章&#xff1a;机器学习05——多分类学习与类别不平衡 下一章&#xff1a;机器学习07——贝叶斯分类器 机器学习实战项目&#xff1a;【从 0 到 1 落地】机器学习实操项目目录&#xff1a;覆盖入门到进阶&#xff0c;大学生就业 / 竞赛必备 文章目录一、间隔与支持向量&…