Android 項目:畫圖白板APP開發(四)——筆鋒(單 Path)

????????上一章講解了如何通過多個 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;大學生就業 / 競賽必備 文章目錄一、間隔與支持向量&…