1. 程式人生 > >安卓自定義View進階-Path之基本操作

安卓自定義View進階-Path之基本操作

在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(zhuang)酷(B)的東東了。


一.Path常用方法表

為了相容性(偷懶) 本表格中去除了部分API21(即安卓版本5.0)以上才新增的方法。

作用 相關方法 備註
移動起點 moveTo 移動下一次操作的起點位置
設定終點 setLastPoint 重置當前path中最後一個點位置,如果在繪製之前呼叫,效果和moveTo相同
連線直線 lineTo 新增上一個點到當前點之間的直線到Path
閉合路徑 close 連線第一個點連線到最後一個點,形成一個閉合區域
新增內容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 新增(矩形, 圓角矩形, 橢圓, 圓, 路徑, 圓弧) 到當前Path (注意addArc和arcTo的區別)
是否為空 isEmpty 判斷Path是否為空
是否為矩形 isRect 判斷path是否是一個矩形
替換路徑 set 用新的路徑替換到當前路徑所有內容
偏移路徑 offset 對當前路徑之前的操作進行偏移(不會影響之後的操作)
貝塞爾曲線 quadTo, cubicTo 分別為二次和三次貝塞爾曲線的方法
rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不帶r的方法是基於原點的座標系(偏移量), rXxx方法是基於當前點座標系(偏移量)
填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 設定,獲取,判斷和切換填充模式
提示方法 incReserve 提示Path還有多少個點等待加入(這個方法貌似會讓Path優化儲存結構)
布林操作(API19) op 對兩個Path進行布林運算(即取交集、並集等操作)
計算邊界 computeBounds 計算Path的邊界
重置路徑 reset, rewind 清除Path中的內容
reset不保留內部資料結構,但會保留FillType.
rewind會保留內部的資料結構,但不保留FillType
矩陣操作 transform 矩陣變換

二.Path詳解

請關閉硬體加速,以免引起不必要的問題!
請關閉硬體加速,以免引起不必要的問題!
請關閉硬體加速,以免引起不必要的問題!

在AndroidMenifest檔案中application節點下添上 android:hardwareAccelerated=”false”以關閉整個應用的硬體加速。
更多請參考這裡:Android的硬體加速及可能導致的問題

Path作用

本次特地開了一篇詳細講解Path,為什麼要單獨摘出來呢,這是因為Path在2D繪圖中是一個很重要的東西。

在前面我們講解的所有繪製都是簡單圖形(如 矩形 圓 圓弧等),而對於那些複雜一點的圖形則沒法去繪製(如繪製一個心形 正多邊形 五角星等),而使用Path不僅能夠繪製簡單圖形,也可以繪製這些比較複雜的圖形。另外,根據路徑繪製文字和剪裁畫布都會用到Path。

關於Path的作用先簡單地說這麼多,具體的我們接下來慢慢研究。

Path含義

官方介紹:

The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves. It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint’s Style), or it can be used for clipping or to draw text on a path.

嗯,沒錯依舊是拿來裝逼的,如果你看不懂的話,不用擔心,其實並沒有什麼卵用。

通俗解釋(sloop個人版):

Path封裝了由直線和曲線(二次,三次貝塞爾曲線)構成的幾何路徑。你能用Canvas中的drawPath來把這條路徑畫出來(同樣支援Paint的不同繪製模式),也可以用於剪裁畫布和根據路徑繪製文字。我們有時會用Path來描述一個影象的輪廓,所以也會稱為輪廓線(輪廓線僅是Path的一種使用方法,兩者並不等價)

另外路徑有開放和封閉的區別。

影象 名稱 備註
封閉路徑 首尾相接形成了一個封閉區域
開放路徑 沒有首位相接形成封閉區域

這個是我隨便畫的,僅為展示一下區別,請無視我靈魂畫師一般的繪圖水準。

與Path相關的還有一些比較神奇的概念,不過暫且不說,等接下來需要用到的時候再詳細說明。

Path使用方法詳解

前面扯了一大堆概念性的東西。接下來就開始實戰了,請諸位看官准備好瓜子、花生、爆米花,坐下來慢慢觀看。

第1組: moveTo、 setLastPoint、 lineTo 和 close

由於Path的有些知識點無法單獨來講,所以本次採取了一次講一組方法。

按照慣例,先建立畫筆:

Paint mPaint = new Paint();             // 建立畫筆
mPaint.setColor(Color.BLACK);           // 畫筆顏色 - 黑色
mPaint.setStyle(Paint.Style.STROKE);    // 填充模式 - 描邊
mPaint.setStrokeWidth(10);              // 邊框寬度 - 10

lineTo:

方法預覽:

public void lineTo (float x, float y)

首先講解的的LineTo,為啥先講解這個呢?

是因為moveTo、 setLastPoint、 close都無法直接看到效果,藉助有具現化效果的lineTo才能讓這些方法現出原形。

lineTo很簡單,只有一個方法,作用也很容易理解,line嘛,顧名思義就是一條線。

俗話(數學書上)說,兩點確定一條直線,但是看引數明顯只給了一個點的座標吧(這不按常理出牌啊)。

再仔細一看,這個lineTo除了line外還有一個to呢,to翻譯過來就是“至”,到某個地方的意思,lineTo難道是指從某個點到引數座標點之間連一條線?

沒錯,你猜對了,但是這某個點又是哪裡呢?

前面我們提到過Path可以用來描述一個影象的輪廓,影象的輪廓通常都是用一條線構成的,所以這裡的某個點就是上次操作結束的點,如果沒有進行過操作則預設點為座標原點。

那麼我們就來試一下:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心(寬高資料在onSizeChanged中獲取)

Path path = new Path();                     // 建立Path

path.lineTo(200, 200);                      // lineTo
path.lineTo(200,0);

canvas.drawPath(path, mPaint);              // 繪製Path

在示例中我們呼叫了兩次lineTo,第一次由於之前沒有過操作,所以預設點就是座標原點O,結果就是座標原點O到A(200,200)之間連直線(用藍色圈1標註)。

第二次lineTo的時候,由於上次的結束位置是A(200,200),所以就是A(200,200)到B(200,0)之間的連線(用藍色圈2標註)。

moveTo 和 setLastPoint:

方法預覽:

// moveTo
public void moveTo (float x, float y)

// setLastPoint
public void setLastPoint (float dx, float dy)

這兩個方法雖然在作用上有相似之處,但實際上卻是完全不同的兩個東東,具體參照下表:

方法名 簡介 是否影響之前的操作 是否影響之後操作
moveTo 移動下一次操作的起點位置
setLastPoint 設定之前操作的最後一個點位置

廢話不多說,直接上程式碼:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心

Path path = new Path();                     // 建立Path

path.lineTo(200, 200);                      // lineTo

path.moveTo(200,100);                       // moveTo

path.lineTo(200,0);                         // lineTo

canvas.drawPath(path, mPaint);              // 繪製Path

這個和上面演示lineTo的方法類似,只不過在兩個lineTo之間添加了一個moveTo。

moveTo只改變下次操作的起點,在執行完第一次LineTo的時候,本來的預設點位置是A(200,200),但是moveTo將其改變成為了C(200,100),所以在第二次呼叫lineTo的時候就是連線C(200,100) 到 B(200,0) 之間的直線(用藍色圈2標註)。

下面是setLastPoint的示例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心

Path path = new Path();                     // 建立Path

path.lineTo(200, 200);                      // lineTo

path.setLastPoint(200,100);                 // setLastPoint

path.lineTo(200,0);                         // lineTo

canvas.drawPath(path, mPaint);              // 繪製Path

setLastPoint是重置上一次操作的最後一個點,在執行完第一次的lineTo的時候,最後一個點是A(200,200),而setLastPoint更改最後一個點為C(200,100),所以在實際執行的時候,第一次的lineTo就不是從原點O到A(200,200)的連線了,而變成了從原點O到C(200,100)之間的連線了。

在執行完第一次lineTo和setLastPoint後,最後一個點的位置是C(200,100),所以在第二次呼叫lineTo的時候就是C(200,100) 到 B(200,0) 之間的連線(用藍色圈2標註)。

close

方法預覽:

public void close ()

close方法用於連線當前最後一個點和最初的一個點(如果兩個點不重合的話),最終形成一個封閉的圖形。

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心

Path path = new Path();                     // 建立Path

path.lineTo(200, 200);                      // lineTo

path.lineTo(200,0);                         // lineTo

path.close();                               // close

canvas.drawPath(path, mPaint);              // 繪製Path

很明顯,兩個lineTo分別代表第1和第2條線,而close在此處的作用就算連線了B(200,0)點和原點O之間的第3條線,使之形成一個封閉的圖形。

注意:close的作用是封閉路徑,與連線當前最後一個點和第一個點並不等價。如果連線了最後一個點和第一個點仍然無法形成封閉圖形,則close什麼 也不做。

第2組: addXxx與arcTo

這次內容主要是在Path中新增基本圖形,重點區分addArc與arcTo。

第一類(基本形狀)

方法預覽:

// 第一類(基本形狀)

// 圓形
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中新增一個基本形狀,基本形狀部分和前面所講的繪製基本形狀並無太大差別,詳情參考Canvas之繪製圖形, 本次只將其中不同的部分摘出來詳細講解一下。

仔細觀察一下第一類是方法,無一例外,在最後都有一個 Path.Direction,這是一個什麼神奇的東東?

Direction的意思是 方向,趨勢。 點進去看一下會發現Direction是一個列舉(Enum)型別,裡面只有兩個列舉常量,如下:

型別 解釋 翻譯
CW clockwise 順時針
CCW counter-clockwise 逆時針

瞬間懵逼,我只是想新增一個基本的形狀啊,搞什麼順時針和逆時針, (╯‵□′)╯︵┻━┻

稍安勿躁,┬─┬ ノ( ‘ - ‘ノ) {擺好擺好) 既然存在肯定是有用的,先偷偷劇透一下這個順時針和逆時針的作用。

序號 作用
1 在新增圖形時確定閉合順序(各個點的記錄順序)
2 對圖形的渲染結果有影響(是判斷圖形渲染的重要條件)

這個先劇透這麼多,至於對閉合順序有啥影響,圖形的渲染等問題等請慢慢看下去

咱們先研究確定閉合順序的問題,新增一個矩形試試看:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心

Path path = new Path();

path.addRect(-200,-200,200,200, Path.Direction.CW);

canvas.drawPath(path,mPaint);

將上面程式碼的CW改為CCW再執行一次。接下來就是見證奇蹟的時刻,兩次執行結果一模一樣,有木有很神奇!

(╯°Д°)╯︵ ┻━┻(再TM掀一次) 坑人也不帶這樣的啊,一毛一樣要它幹嘛。

其實啊,這個東東是自帶隱身技能的,想要讓它現出原形,就要用到咱們剛剛學到的setLastPoint(重置當前最後一個點的位置)。

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心

Path path = new Path();

path.addRect(-200,-200,200,200, Path.Direction.CW);

path.setLastPoint(-300,300);                // <-- 重置最後一個點的位置

canvas.drawPath(path,mPaint);

可以明顯看到,圖形發生了奇怪的變化。為何會如此呢?

我們先分析一下,繪製一個矩形(僅繪製邊線),實際上只需要進行四次lineTo操作就行了,也就是說,只需要知道4個點的座標,然後使用moveTo到第一個點,之後依次lineTo就行了(從上面的測試可以看出,在實際繪製中也確實是這麼幹的)。

可是為什麼要這麼做呢?確定一個矩形最少需要兩個點(對角線的兩個點),根據這兩個點的座標直接算出四條邊然後畫出來不就行了,幹嘛還要先計算出四個點座標,之後再連直線呢?

這個就要涉及一些path的儲存問題了,前面在path中的定義中說過,Path是封裝了由直線和曲線(二次,三次貝塞爾曲線)構成的幾何路徑。其中曲線部分用的是貝塞爾曲線,稍後再講。 然而除了曲線部分就只剩下直線了,對於直線的儲存最簡單的就是記錄座標點,然後直接連線各個點就行了。雖然記錄矩形只需要兩個點,但是如果只用兩個點來記錄一個矩形的話,就要額外增加一個標誌位來記錄這是一個矩形,顯然對於儲存和解析都是很不划算的事情,將矩形轉換為直線,為的就是儲存記錄方便。

扯了這麼多,該回歸正題了,就是我們的順時針和逆時針在這裡是幹啥的?

圖形在實際記錄中就是記錄各個的點,對於一個圖形來說肯定有多個點,既然有這麼多的點,肯定就需要一個先後順序,這裡順時針和逆時針就是用來確定記錄這些點的順序的。

對於上面這個矩形來說,我們採用的是順時針(CW),所以記錄的點的順序就是 A -> B -> C -> D. 最後一個點就是D,我們這裡使用setLastPoint改變最後一個點的位置實際上是改變了D的位置。

理解了上面的原理之後,設想如果我們將順時針改為逆時針(CCW),則記錄點的順序應該就是 A -> D -> C -> B, 再使用setLastPoint則改變的是B的位置,我們試試看結果和我們的猜想是否一致:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心

Path path = new Path();

path.addRect(-200,-200,200,200, Path.Direction.CCW);

path.setLastPoint(-300,300);                // <-- 重置最後一個點的位置

canvas.drawPath(path,mPaint);

通過驗證發現,發現結果和我們猜想的一樣,但是還有一個潛藏的問題不曉得大家可否注意到。我們用兩個點的座標確定了一個矩形,矩形起始點(A)就是我們指定的第一個點的座標。

需要注意的是,交換座標點的順序可能就會影響到某些繪製內容哦,例如上面的例子,你可以嘗試交換兩個座標點,或者指定另外兩個點來作為引數,雖然指定的是同一個矩形,但實際繪製出來是不同的哦。

引數中點的順序很重要!
引數中點的順序很重要!
引數中點的順序很重要!

重要的話說三遍,本次是用矩形作為例子的,其他的幾個圖形基本上都包含了曲線,詳情參見後續的貝塞爾曲線部分。

關於順時針和逆時針對圖形填充結果的影響請參考 Path之完結篇,雖然只講了一個Path,但也是內容頗多,放進一篇中就太長了,請見諒。

第二類(Path)

方法預覽:

// 第二類(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之前先使用Matrix進行變換。

第二個方法比第一個方法多出來的兩個引數是將src進行了位移之後再新增進當前path中。

示例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到螢幕中心
canvas.scale(1,-1);                         // <-- 注意 翻轉y座標軸

Path path = new Path();
Path src = new Path();

path.addRect(-200,-200,200,200, Path.Direction.CW);
src.addCircle(0,0,100, Path.Direction.CW);

path.addPath(src,0,200);

mPaint.setColor(Color.BLACK);           // 繪製合併後的路徑
canvas.drawPath(path,mPaint);

首先我們新建地方兩個Path(矩形和圓形)中心都是座標原點,我們在將包含圓形的path新增到包含矩形的path之前將其進行移動了一段距離,最終繪製出來的效果就如上面所示。

第三類(addArc與arcTo)

方法預覽:

// 第三類(addArc與arcTo)

// addArc
public void addArc (RectF oval, float startAngle, float sweepAngle)
// arcTo
public void arcTo (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle
            
           

相關推薦

定義View-Path基本操作

在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(zhu

定義View-Path貝塞爾曲線

在上一篇文章Path之基本操作中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上

定義View-Path完結篇

經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除

定義View-Canvas畫布操作

Canvas之畫布操作 上一篇Canvas之繪製基本形狀中我們瞭解瞭如何使用Canvas繪製基本圖形,本次瞭解一些基本的畫布操作。 本來想把畫布操作放到後面部分的,但是發現很多圖形繪製都離不開畫布操作,於是先講解一下畫布的基本操作方法。

定義View-Canvas圖片文字

在上一篇文章Canvas之畫布操作中我們瞭解了畫布的一些基本操作方法,本次瞭解一些繪製圖片文字相關的內容。如果你對前幾篇文章講述的內容熟練掌握的話,那麼恭喜你,本篇結束之後,大部分的自定義View已經難不倒你了,當然了,這並不是終點,接下來還會有更加炫酷的技能。 一.Canva

定義ViewPath基本操作

在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(

定義 View Path 完結篇(偽)

經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現吧,嗯,應該會的ˊ_>ˋ 一.Path常用方法表 為了相容性

定義ViewPath玩出花樣(PathMeasure)

PS:不要問我為什麼不講 PathEffect,因為這個方法在後面的Paint系列中。 先放一個圖鎮樓,省的下面無聊的內容把你們都嚇跑了Σ( ̄。 ̄ノ)ノ Path & PathMeasure 顧名思義,PathMeasure是一個用來測量Path的類,主要有以下方法: 構造方法 方法名 釋

定義View-手勢檢測(GestureDecetor)

Android 手勢檢測,主要是 GestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,部分內容會涉及到之前文章提及過的知識點,如果你沒看過之前的文章,可以到 自定義 View 系列 來檢視這些內容。 在開發 Android 手機應用過程中,可

定義View-多點觸控詳解

Android 多點觸控詳解,在前面的幾篇文章中我們大致瞭解了 Android 中的事件處理流程和一些簡單的處理方案,本次帶大家瞭解 Android 多點觸控相關的一些知識。 多點觸控 ( Multitouch,也稱 Multi-touch ),即同時接受螢幕上多個點的人機互動

定義View-特殊控制元件的事件處理方案

本文帶大家瞭解 Android 特殊形狀控制元件的事件處理方式,主要是利用了 Region 和 Matrix 的一些方法,超級實用的事件處理方案,相信看完本篇之後,任何奇葩控制元件的事件處理都會變得十分簡單。 不得不說,Android 對事件體系封裝的非常棒,即便對事件體系不太

定義View-MotionEvent詳解

Android MotionEvent 詳解,之前用了兩篇文章 事件分發機制原理 和 事件分發機制詳解 來講解事件分發,而作為事件分發主角之一的 MotionEvent 並沒有過多的說明,本文就帶大家瞭解 MotionEvent 的相關內容,簡要介紹觸控事件,主要包括 單點觸控、多點

定義View-事件分發機制詳解

Android 事件分發機制詳解,在上一篇文章 事件分發機制原理 中簡要分析了一下事件分發機制的原理,原理是十分簡單的,一句話就能總結:責任鏈模式,事件層層傳遞,直到被消費。 雖然原理簡單,但是隨著 Android 不斷的發展,實際運用場景也越來越複雜,所以想要徹底玩轉事件分發機制還

定義View-Matrix Camera

本篇依舊屬於Matrix,主要講解Camera,Android下有很多相機應用,其中的美顏相機更是不少,不過今天這個Camera可不是我們平時拍照的那個相機,而是graphic包下的Camera,專業給View拍照的相機,不過既然是相機,作用都是類似的,主要是將3D的內容拍扁變成2D

定義View-Matrix詳解

這應該是目前最詳細的一篇講解Matrix的中文文章了,在上一篇文章Matrix原理中,我們對Matrix做了一個簡單的瞭解,偏向理論,在本文中則會詳細的講解Matrix的具體用法,以及與Matrix相關的一些實用技巧。 ⚠️ 警告:測試本文章示例之前請關閉硬體加速。

定義View-Matrix原理

本文內容偏向理論,和 畫布操作 有重疊的部分,本文會讓你更加深入的瞭解其中的原理。 本篇的主角Matrix,是一個一直在後臺默默工作的勞動模範,雖然我們所有看到View背後都有著Matrix的功勞,但我們卻很少見到它,本篇我們就看看它是何方神聖吧。 由於Goog

定義View-PathMeasure

可以看到,在經過 Path之基本操作 Path之貝塞爾曲線 和 Path之完結篇 後, Path中各類方法基本上都講完了,表格中還沒有講解到到方法就是矩陣變換了,難道本篇終於要講矩陣了? 非也,矩陣這一部分仍在後面單獨講解,本篇主要講解 PathMeasure 這個類與 Path 的

定義View-分類與流程

本章節為什麼要叫進階篇?(雖然講的是基礎內容),因為從本篇開始,將會逐漸揭開自定義View的神祕面紗,每一篇都將比上一篇內容更加深入,利用所學的知識能夠製作更加炫酷自定義View,就像在臺階上一樣,每一篇都更上一層,幫助大家一步步走向人生巔峰,出任CEO,迎娶白富美。 誤

定義View-縮放手勢檢測(ScaleGestureDecetor)

0. 前言 Android 縮放手勢檢測,ScaleGestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,在大多數的情況下,縮放手勢都不是單獨存在的,需要配合其它的手勢來使用,所以推薦配合 手勢檢測(GestureDetector) 一

定義 View :貝塞爾曲線

在上一篇文章Path之基本圖形中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上才新增的方法。忍不住吐槽一下,為啥看起來有些順手就能寫的