探索 Android 自定義控件:基礎圖形

首圖.png

前言

1. 文章內容

這篇文章分爲下面 5 個部分。html

  1. 繪圖基礎java

    這一節會介紹 Android 中的畫筆 Paint 和畫布 Canvas 的用法。git

  2. 路徑繪製github

    這一節會介紹 Android 中路徑 Path 的用法,包括直線、弧線和雷達圖等圖形的繪製方法。數據結構

  3. 文字繪製app

    這一節會介紹 Android 中繪製文字的方法,包括粗體、斜體、加載字體等。函數

  4. 區域操做post

    這一節會介紹 Android 中區域 Region 的用法,包括區域裁剪、區域合併等操做。字體

  5. 畫布進階3d

    這一節會對畫布的用法進行更詳細的介紹,包括保存、指向恢復和圓形頭像。

2. 注意事項

  • 對象建立

    下面的示例代碼會在 onDraw() 方法中建立 Paint、Path、Rect 和 Region等對象,這在實際開發中是禁止的。

    由於當 View 須要重繪時會調用 onDraw() 函數,每一次調用 onDraw() 函數都會從新建立這些對象。

    這樣會引發頻繁的 GC,嚴重時會致使 App 卡頓。

  • 圖片清晰度

    點擊文章中的圖片能夠查看原圖。

  • 示例代碼

    文中的示例代碼源碼在文章最下方能夠找到。

1. 繪圖基礎

本節包含內容以下。

  • 畫筆用法
  • 畫布用法
  • 顏色構造

1.1 畫筆用法

咱們這一節來看一下畫筆的用法。

本節包含內容以下。

  • 設置畫筆顏色
  • 設置畫筆樣式
  • 設置畫筆寬度
  • 設置畫筆鋸齒

1.1.1 設置畫筆顏色

畫筆顏色.png

Paint 的 setColor(int color) 方法可用於設置畫筆顏色,下面是 color 參數的取值。

一種顏色是由紅綠藍三色合成的,因此 color 只能取 8 位 0xAARRGGBB 樣式顏色值。

  • 透明度

    A 表示透明度 Alpha,取值範圍是 0~255,值越小,圖像越透明

  • 紅色

    R 表示紅色 Red,取值範圍是 0~255,取值越小紅色越少

  • 綠色

    G 表示綠色 Green,取值範圍是 0~255,取值越小綠色越少

  • 藍色

    B 表示藍色 Blue,取值範圍是 0~255,取值越小藍色越少

除了手動組合顏色,系統還提供了一個用於解析顏色的類 Color,關於 Color 在後面會有更詳細的介紹。

1.1.2 設置畫筆樣式

畫筆樣式.png

Paint 的 setStyle(Style style) 方法可用於設置畫筆樣式,下面是 style 參數的取值。

  • 描邊

    • STROKE
  • 填充

    • FILL

    默認樣式。

  • 描邊且填充

    • FILL_AND_STROKE

    描邊與填充疊加在一塊兒顯示的效果,也就是這個值比填充多了一個描邊的寬度。

1.1.3 設置畫筆寬度

Paint 的 setStrokeWidth(width) 方法用於設置描邊寬度,單位是 px。

  • 注意事項

    當畫筆的 Style 是 STROKE 或 FILL_AND_STROKE 時畫筆寬度纔有意義。

1.1.4 設置畫筆鋸齒

畫筆 Paint 繪製圖形時默認不是抗鋸齒的,也就是邊邊會有鋸齒。

Paint 提供了 setAntiAlias() 方法,這個方法能夠開啓畫筆的抗鋸齒功能。

下面是兩個放大後的圓,右邊的圓用的是抗鋸齒的畫筆繪製的。

抗鋸齒.png

1.2 畫布用法

上一節演示畫筆的同時也演示了畫布繪製圓的方法,這一節咱們來看一下畫布的其餘方法。

本節內容以下。

  • 設置背景
  • 繪製直線
  • 繪製點
  • 矩形結構
  • 繪製矩形
  • 圓角矩形

1.2.1 設置背景

Canvas 提供了三個設置背景的方法。

  • drawColor(int color)
  • drawARGB(int a, int r, int g, int b)
  • drawRGB(int r, int g, int b)

須要注意的是,設置畫布背景要在其餘圖形繪製前設置,不然設置好的背景色會覆蓋原有的圖形。

畫布背景.png

1.2.2 繪製直線

繪製直線.png

Canvas 的 drawLine() 方法能夠繪製直線,直線的粗細取決於 Paint 的 setStrokeWith(width) 中傳入的寬度。

繪製直線須要注意的是,只有當 Style 是 STROKE、FILL_AND_STROKE 時繪製纔有效。

  • drawLine(float startX, float starY, float stopX, float stopY, Paint paint)

1.2.3 繪製點

繪製點.png

Canvas 的 drawPoint() 方法能夠用於繪製點,點的大小取決於 setStrokeWith(width) 中傳入的寬度。

  • drawPoint(float x, float y, Paint paint)

1.2.4 矩形結構

矩形結構在繪製矩形區域的時候須要用到。

Android 提供了 Rect 和 RectF 類用於存儲矩形數據結構,下面是 Rect 和 RectF 的構造函數。

Rect 構造函數.png

Rect 和 RectF 在於存儲的數據類型不一樣。

  • Rect

    用於保存 int 類型數值的矩形結構

  • RectF

    用於保存 float 類型數值的矩形結構

1.2.5 繪製矩形

繪製矩形.png

Canvas 的 drawPoint() 和 drawRect() 方法均可用於繪製矩形。

下面是這兩個方法的區別。

  • 形狀

    • drawPoint()

      只能指定矩形中心的座標,只能畫出正方形。

    • drawRect()

      須要指定矩形左上和右下兩個點的位置,能夠是長方形。

  • 樣式

    • drawPoint()

      只能是填充樣式。

    • drawRect()

      能夠本身選擇樣式,能夠是描邊也能夠是填充。

下面是 Canvas 中提供用於繪製矩形的三個方法。

  • drawRect(float left, float top, float right, float bottom, Paint paint)
  • drawRect(RectF rect, Paint paint)
  • drawRect(Rect r, Paint paint)

1.2.6 圓角矩形

Canvas 中提供了一個 drawRoundRect 方法用於繪製圓角矩形,圓角矩形的四個角是橢圓的一角,下面是它的基本用法。

圓角矩形.png

1.3 顏色構造

Color 類是 Android 中與顏色有關的類。

本節內容以下。

  • 顏色常量
  • 構造顏色

1.3.1 顏色常量

Color 中定義了下面的顏色常量值。

Color 常量.png

1.3.2 構造顏色

除了顏色常量外,Color 還提供了一些構造顏色的方法。

構造顏色

alpha << 就是進 24 位,當咱們調用 Color.argb(255, 0, 0,0 0) 時,轉換後的 16 進制顏色值就是 0xFF000000。

進位.png

2. 路徑繪製

路徑 Path 指的是畫筆畫出來的一條不間斷的曲線,本節會講解直線和弧線路徑,除了這兩種方式外還有不少方法能夠實現不少效果。

drawPath 是 Canvas 繪製路徑的方法。

  • drawPath(Path path, Paint paint)

本節內容以下。

  1. 直線路徑
  2. 弧線路徑
  3. 添加路徑
  4. 填充路徑
  5. 重置路徑
  6. 雷達路徑

2.1 直線路徑

繪製直線路徑.png

上面是一段繪製直線路徑的代碼,這裏須要注意的是,上面代碼中的畫筆樣式設爲描邊,默認是填充的。

也就是在默認狀況下,多條線相連造成閉環後,中間的區域會被填充。

繪製直線路徑涉及:起點、終點和閉環。

  • 起點

    Path 的 moveTo() 用於指定直線路徑的起點,參數 x1 和 y1 是起點座標值。

    • moveTo(float x1, float y1)
  • 終點

    Path 的 lineTo() 用於指定直線路徑的起點,參數 x2 和 y2 是終點座標值,也是下一次繪製直線的起點。

    • lineTo(float x2, float y2)
  • 閉環

    Path 的 close() 方法用於造成閉環。

    • close()

    若是連續畫了幾條直線,但沒有造成閉環,調用 close() 函數會把路徑首尾鏈接起來造成閉環,至關因而幫咱們畫多一條直線。

    若是隻畫了一條直線,那 close() 方法是不會起做用的。

2.2 弧線路徑

Path 的 arcTo() 方法可用於繪製弧線,弧線在這裏指的是橢圓上截取的一部分。

  • arcTo(RectF oval, float startAngle, sweepAngle)

本節內容以下。

  • 弧線樣式
  • 弧線起點
  • 弧線角度
  • 弧線函數

2.2.1 弧線樣式

這裏的橢圓底色默認是不存在的,在這裏畫出來主要是爲了突出弧線。

在這裏咱們要注意的是,弧線默認是填充的,更準確的來講 drawArc() 方法是切出橢圓中的一塊。

若是咱們只想要一條線的話,就要本身設置描邊樣式和描邊寬度。

弧線樣式.png

2.2.2 弧線起點

除了樣式之外,繪製弧線要注意的另一點就是起點。

  • 改變起點

    若是咱們調用了 moveTo() 改變了路徑的起點,那弧線就會從 moveTo() 接收到的座標開始繪製。

  • 重置起點

    若是咱們想重置起點到弧線正常該開始的位置,咱們能夠把 forceMoveTo 設爲 true。

弧線起點.png

2.2.3 弧線角度

arcTo 中有兩個跟角度有關的參數 startAngle 和 sweepAngle,這兩個參數分別表明起始角和掃描角。

  • 起始角

    起始角指定弧線從哪裏開始畫起。

  • 掃描角

    掃描角能夠當作是弧線的長度。

弧線角度.png

2.2.4 弧線函數

arcTo.png

Path 中定義了的三個 arcTo() 函數,下面是 arcTo() 函數中主要四個參數的含義。

  • oval

    生成橢圓的矩形

  • startAngle

    弧開始的角度,以 X 軸正方向爲 0°

  • sweepAngle

    弧持續的角度

  • forceMoveTo

    重置起點,把繪製弧線的起點從 moveTo 的座標重置到 startAngle 的位置。

2.3 添加路徑

本節內容以下。

  • 路徑添加方法
  • 路徑繪製方向

2.3.1 路徑添加方法

Path 提供了 addXXX 函數用於添加路徑,添加的路徑能夠是不連續的,還能夠是曲線。

下面是 Path 中提供的一些添加路徑的方法。

添加路徑函數.png

下面是添加路徑的示例。

添加路徑.png

2.3.2 路徑繪製方向

在添加路徑的函數中有一些函數有 Direction 參數,這個 Direction 就是繪製的方向。

方向分爲順時針逆時針 CCW(Counter-Clockwise)。

Direction.png

下面是不一樣方向的繪製示例,這個示例中用到了 drawTextOnPath 方法,這個方法在講繪製文字的時候會有更詳細的介紹,如今咱們先看繪製方向對繪製效果的影響。

繪製方向.png

2.4 填充路徑

路徑 Path 的填充模式與畫筆 Paint 的填充模式不一樣,Path 的填充模式是指填充 Path 的哪部分。

在 Path 中有一個 FillType 枚舉類,其中定義了 4 種填充類型。

FillType.png

下面是這四種填充類型的示例。

路徑填充.png

2.5 重置路徑

Android 提供了兩個重置路徑的方法,讓咱們能夠重複使用 Path 對象。

路徑 Path 一旦被重置,其中保存的全部路徑都會被清空,這樣就不須要從新建立一個 Path 對象了。

  • reset()

    reset() 函數相似於新建一個路徑對象,除了填充類型 FillType 之外,Path 的全部數據都會被回收並從新分配。

  • rewind()

    rewind() 函數會清除 FillType 和全部的路徑,保留內部數據結構,以便更快地重用。

    好比咱們須要重複繪製一類線段,它們的點和數量相等,使用 rewind() 函數能夠保留裝載點數據的數據結構,效率更高。

    要注意的是,只有重複繪製相同路徑時,這些數據結構纔是可複用的。

2.6 雷達路徑

本節包含以下內容。

  1. 建立畫筆
  2. 計算圓心
  3. 畫多邊形
  4. 畫網格線
  5. 畫數據圖

2.6.1 建立畫筆

雷達初始化.png

2.6.2 計算圓心

在控件大小發生變化時,onSizeChanged() 會被回調並獲得最新的控件大小,因此咱們須要重寫這個方法。

網狀路徑的大小佔當前控件的 90%,因此半徑爲:寬高最小值 ÷ 2 × 0.9。

而後根據中心點,分別繪製雷達網格、網格中線、數據圖。

雷達onDraw.png

2.6.3 畫多邊形

drawPolygon 涉及到三角函數,下面是公式計算示意圖。

公式示意圖.png

下面是公式的具體實現。

畫多邊形.png

2.6.4 畫網格線

畫網格線.png

2.6.5 畫數據圖

畫數據.png

3. 文字繪製

本文包含以下內容。

  • 畫筆文字參數
  • 畫布繪製文字
  • 設置字體樣式
  • 獲取文字寬高

3.1 畫筆文字參數

本節包含內容以下。

  • 文字參數
  • 文字樣式
  • 文字對齊
  • 文字變形
  • 其餘設置

3.1.1 文字參數

下面是幾個 Paint 中設置文字繪製效果的方法。

文字畫筆函數.png

3.1.2 文字樣式

文字樣式.png

3.1.2 文字對齊

咱們能夠經過 Paint 的 setTextAlign(align) 方法來設置繪製文字時的對齊方向。

Paint 中有一個枚舉類 Align,在 Align 中定義了三種對齊模式:LEFT、CENTER、RIGHT,這三種模式分別表明左對齊,居中對齊和右對齊。

文字對齊.png

3.1.4 文字變形

文本變形.png

3.1.6 其餘設置

其餘效果.png

3.2 畫布繪製文字

上一節咱們看到了Canvas 的 drawText() 方法,下面咱們來看一下 Canvas 提供的其餘繪製文本的方法。

本節包含內容以下。

  • 逐字繪製文字
  • 路徑繪製文字

3.2.1 逐字繪製文字

逐字繪製.png

3.2.2 路徑繪製文字

在講解路徑繪製方向時咱們就已經用到過 Canvas 提供了 drawTextOnPath() 方法,在這裏咱們看一下這個方法中的偏移參數。

路勁文字.png

3.3 設置字體樣式

Paint 中提供了 setTypeFace(typeFace) 方法同於設置字體樣式。

TypeFace 是專門用於設置字體樣式的,咱們能夠指定系統提供的字體也能夠指定自定義字體。

當建立 TypeFace 類時,能夠指定是正常字體、斜體或者粗體,當指定樣式中沒有相關文字的樣式時,就會用系統默認的樣式顯示,通常默認是宋體。

本節包含以下內容。

  • 系統字體
  • 字體樣式
  • 加載字體

3.3.1 系統字體

TypeFace 中提供了下面三種字體,這三種字體對中文的支持不是很好,當遇到不支持的文字時,會用系統默認字體來寫。

TypeFace.png

下面是這三種字體的顯示效果。

系統字體.png

3.3.2 字體樣式

除了能夠選擇特定字體之外,咱們還能夠經過 defaultFromStyle() 方法選擇特定具體的某種樣式。

TypeFace 中還定義了下面四種字體樣式。

TypeFaceStyle.png

下面是這四種樣式的顯示效果。

字體樣式.png

3.3.3 加載字體

Android 提供了下面三種加載自定義字體的方式。

  • 根據字體名加載
  • 根據資源名加載
  • 根據文件名加載

1. 根據字體名加載

TypeFace 中提供了一個用於根據字體名加載字體的 create() 方法,好比下面這行代碼這樣。

因爲模擬器上沒有別的字體,在這裏就不演示了,你們有興趣的能夠本身嘗試下。

createFace.png

2. 根據資源名加載

假如咱們從網上下了個 ttf 字體文件,想放在包中直接使用,咱們能夠在 app/src/main 目錄下建一個 assets 目錄,再建一個 fonts 目錄,而後把 ttf 文件放到裏面。

而根據資源名加載字體的方式就是 TypeFace 的 createFromAsset() 方法。

加載字體資源.png

3. 根據文件名加載字體

從資源中加載字體的弊端就是會讓 APK 包變大,若是咱們把字體下載到本地的話就能夠避免這個問題。

加載字體文件.png

3.4 獲取文字寬高

Paint 提供了三種獲取文字寬高的方法,下面咱們來看一下這幾個方法的用法。

本節內容以下。

  • 獲取字體寬度
  • 獲取字體寬高

3.4.1 獲取字體寬度

文字寬度.png

3.4.2 獲取字體寬高

文字寬高.png

4. 區域操做

區域指的是 Region 類,Region 是一塊任意形狀的封閉圖形,這一節咱們來看一下 Region 提供的一些操做區域的方法。

本節包含以下內容。

  1. 建立區域
  2. 合併區域
  3. 裁剪區域
  4. 其餘操做

4.1 建立區域

直接構造區域指的是用 Region 的構造函數建立區域,下面是 Region 的四個構造函數。

Region 構造函數.png

下面是一個繪製 Region 的簡單示例,因爲 Canvas 中沒有繪製 Region 的方法,因此咱們在這裏本身定義一個 drawRegion() 方法來繪製 Region。

Region 可用於繪製各類各樣的形狀,咱們先看一下怎麼繪製一個簡單的正方形 Region。

直接構造區域.png

4.2 合併區域

Region 提供了一個 union(rect) 函數,傳入目標矩陣到 union 函數中就能實現合併區域。

合併區域.png

4.3 裁剪區域

Region 中聲明瞭下面的 5 個設置區域的方法。

設置區域方法.png

裁剪區域須要先建立一個空 Region,而後調用 setPath 方法,下面是裁剪區域的操做,橢圓路徑和裁剪區域相交的區域就是裁剪結果。

裁剪區域.png

4.4 其餘操做

本節以下內容。

  • 集合運算方法
  • 集合運算類型
  • 集合運算示例

1. 集合運算方法

除了 union 之外,Region 還提供了下面幾個區域操做方法,並且 union 自己也是調用了 op 方法。

op.png

2. 集合運算類型

在上面這些方法中比較重要的是 Op 參數,Op 是 Region 中定義的枚舉類,是集合運算操做。

在這裏,集合中的元素就是 Region 矩陣範圍中的子矩陣。

opEnum.png

**3. 集合運算示例 **

下面是這些集合運算的示例。

集合運算.png

5. 畫布

上一節咱們講了怎麼用畫布 Canvas 繪製各類圖形,畫布除了能用來繪製各類圖形之外,咱們還能對畫筆進行變換和裁剪。

本節內容以下。

  1. 畫布平移
  2. 畫布合成
  3. 畫布裁剪
  4. 畫布保存

4.1 畫布平移

畫布 Canvas 提供了一個可用於平移的方法 translate(),畫布的原始狀態是以左上角爲圓點,向右是 X 軸正方向,向下是 Y 軸正方向。

因爲畫布的左上角是座標軸的原點(0, 0),因此平移畫布後,座標系也會被平移。

平移後的畫筆的左上角是新的座標原點。

畫布平移.png

4.2 畫布合成

合成畫布與屏幕的操做是由系統進行的,這一節咱們來看一下這個操做的簡單介紹。

本節內容以下。

  • 合成
  • 平移
  • 小結

4.2.1 合成

咱們每次在畫布上畫圖時,系統會先產生一個透明圖層,在這個圖層上畫圖,花完後再覆蓋在屏幕上。

合成

4.2.2 平移

當咱們繪製紅色矩形時,會產生另外一個新的 Canvas 透明圖層,此時畫布座標改變了,因此繪圖方式以下圖所示。

因爲 Canvas 已經平移了 350 像素,因此畫圖時是以新原點來產生視圖的,而後再合成到屏幕上。

當屏幕移動後,有一部分超出屏幕的範圍,超出範圍的圖像是不顯示的。

再繪製.png

4.2.3 小結

使用 Canvas 的繪製方法有下面三個須要注意的點。

  1. 生成新圖層

    每次調用繪製方法 drawXXX 時,都會產生一個新的 Canvas 透明圖層。

  2. 操做不可逆

    調用了繪製方法前,平移和旋轉等函數對 Canvas 進行了操做,那麼這個操做是不可逆的,每次產生的畫布的最新位置都是這些操做後的位置。

  3. 超出不顯示

    在 Canvas 圖層與屏幕合成時,超出屏幕範圍的圖像是不會顯示出來的

4.3 畫布裁剪

畫布裁剪是指用 Canvas 的 clipXXX 函數與矩形、路徑和區域取交、並、差等集合運算來得到最新的畫布形狀。

除非使用了保存和恢復函數,不然裁剪操做是不可逆的,也就是一旦裁剪就沒法恢復。

在 Canvas 中定義了下面幾個裁剪函數。

裁剪函數.png

下面是一個裁剪示例。

畫布裁剪.png

4.4 畫布保存

在前面咱們說到了畫布的操做是不可逆的,這會形成不少麻煩。

好比爲了實現一些效果須要對畫布進行操做,但畫布狀態改變後又影響了後面的繪製效果。

由於這個緣由,畫布提供了保存和恢復功能,這兩個功能對應的方法是 save() 和 restore(),每調用一次 save() 就會把當前畫布狀態保存到棧中。

本節內容以下。

  • 屢次保存
  • 指向恢復
  • 圓形頭像

4.4.1 屢次保存

畫布單次保存恢復的操做比較簡單,咱們來看一下畫布屢次保存和屢次恢復的效果。

畫布屢次保存.png

4.4.2 指向恢復

指向恢復指的是恢復到特定的狀態,好比咱們上面這個例子,假如咱們要恢復到藍色400,那咱們要調用三次 resoter()。

爲了解決這個問題,Canvas 提供了另外一個恢復畫布狀態的函數 restoreToCount(count),下面咱們來看一下這個函數的用法。

指向恢復.png

4.4.3 圓形頭像

下面咱們來看一下怎麼用畫布的保存和恢復功能實現繪製圓形頭像後,再把畫布恢復回來。

本節包含以下內容。

  • 初始化
  • 繪製頭像
  • 去除鋸齒

1. 初始化

這裏之因此要禁用硬件加速,是由於硬件加速是使用的 OpenGL 函數完成實際繪製,而 OpenGL 並不能徹底支持原始繪製函數,好比 clipPath() 在開啓硬件加速的狀況下只有在 API 18 以上的系統纔會生效。

圓形頭像初始化.png

2. 繪製頭像

圓形頭像.png

3. 去除鋸齒

clipPath 實現的圓形圖像是有鋸齒的,咱們能夠用 PorterDuffXfermode 來實現無鋸齒的圓形圖片。

混合模式.png

參考資料

1. 書籍

  1. 《Android 自定義控件開發與實戰》
  2. 《Android 自定義控件開發與實戰》—下載資源

2. 文章

  1. Android Path 最佳實踐之繪製雷達圖
  2. Android自定義View(八) -- 硬件加速
  3. Android Bitmap 常見的幾個操做:縮放,裁剪,旋轉,偏移
  4. Android中測量文字的寬度和高度
  5. 使用PorterDuff解決clipPath沒法抗鋸齒問題
  6. Android Bitmap 圖片裁剪成圓形Raw]

其餘

  1. 示例代碼源碼
相關文章
相關標籤/搜索