1. 程式人生 > >HenCoder Android 開發進階: 自定義 View 1-1 繪製基礎

HenCoder Android 開發進階: 自定義 View 1-1 繪製基礎

自定義繪製概述

二話不說,我反手就是一個視訊:(視訊掛了,先直接點到優酷去看吧:優酷連結

首先總結一下視訊中的關鍵點:

  • 自定義繪製的方式是重寫繪製方法,其中最常用的是 onDraw()
  • 繪製的關鍵是 Canvas 的使用
    • Canvas 的繪製類方法: drawXXX() (關鍵引數:Paint)
    • Canvas 的輔助類方法:範圍裁切和幾何變換
  • 可以使用不同的繪製方法來控制遮蓋關係

概念已經在視訊裡全部講出來了,知識點並不多,但你可能也看出來了,我講得並不細。這是因為知識點雖然不多,但細節還是很多的,僅僅靠一節分享不可能講完。我按照順序把這些知識分成了 4 個級別,拆成幾節來講,你按照這 4 個級別的順序學習下來,就能夠平滑地逐步進階。

自定義繪製知識的四個級別

  1. Canvas 的 drawXXX() 系列方法及 Paint 最常見的使用

    Canvas.drawXXX() 是自定義繪製最基本的操作。掌握了這些方法,你才知道怎麼繪製內容,例如怎麼畫圓、怎麼畫方、怎麼畫影象和文字。組合繪製這些內容,再配合上 Paint 的一些常見方法來對繪製內容的顏色和風格進行簡單的配置,就能夠應付大部分的繪製需求了。

  2. 今天這篇分享我要講的就是這些內容。也就是說,你在看完這篇文章並做完練習之後,上面這幾幅圖你就會繪製出來了。從今以後,你也很少再需要假裝一本正經地對設計師說「不行這個圖技術上實現不了」,也不用心驚膽戰得等待設計師的那句「那 iOS 怎麼可以」了。

  3. Paint 的完全攻略

    Paint 可以做的事,不只是設定顏色,也不只是我在視訊裡講的實心空心、線條粗細、有沒有陰影,它可以做的風格設定真的是非常多、非常細。例如:

    拐角要什麼形狀?

    開不開雙線性過濾?

    加不加特效?

  4. 可以調節的非常多,我就不一一列舉了。當你掌握到這個級別,就真的不會有什麼東西會是 iOS 能做到但你做不到的了。就算設計師再設計出了很難做的東西,做不出來的也不再會是你們 Android 組了。

  5. Canvas 對繪製的輔助——範圍裁切和幾何變換。

    範圍裁切:

    幾何變換:

  6. 大多數時候,它們並不會被用到,但一旦用到,通常都是很炫酷的效果。範圍裁切和幾何變換都是用於輔助的,它們本身並不酷,讓它們變酷的是設計師們的想象力與創造力。而你要做的,是把他們的想象力與創造力變成現實。

  7. 使用不同的繪製方法來控制繪製順序

    控制繪製順序解決的並不是「做不到」的問題,而是效能問題。同樣的一種效果,你不用繪製順序的控制往往也能做到,但需要用多個 View 甚至是多層 View 才能拼湊出來,因此代價是 UI 的效能;而使用繪製順序的控制的話,一個 View 就全部搞定了。

  8. 自定義繪製的知識,大概就分為上面這四個級別。在你把這四個級別依次掌握了之後,你就是一個自定義繪製的高手了。它們具體的細節,我將分成幾篇來講。今天這篇就是第一篇: Canvas.drawXXX() 系列方法及 Paint 最基本的使用。我要正式開始嘍?

    一切的開始:onDraw()

PS : 有興趣的加入Android工程師交流QQ群:752016839 主要針對Android開發人員提升自己,突破瓶頸,相信你來學習,會有提升和收穫。

自定義繪製的上手非常容易:提前建立好 Paint 物件,重寫 onDraw(),把繪製程式碼寫在 onDraw() 裡面,就是自定義繪製最基本的實現。大概就像這樣:

Paint paint = new Paint();

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // 繪製一個圓
    canvas.drawCircle(300, 300, 200, paint);
}複製程式碼

就這麼簡單。所以關於 onDraw() 其實沒什麼好說的,一個很普通的方法重寫,唯一需要注意的是別漏寫了 super.onDraw()

Canvas.drawXXX() 和 Paint 基礎

drawXXX() 系列方法和 Paint 的基礎掌握了,就能夠應付簡單的繪製需求。它們主要包括:

  1. Canvas 類下的所有 draw- 打頭的方法,例如 drawCircle() drawBitmap()

  2. Paint 類的幾個最常用的方法。具體是:
    • Paint.setStyle(Style style) 設定繪製模式
    • Paint.setColor(int color) 設定顏色
    • Paint.setStrokeWidth(float width) 設定線條寬度
    • Paint.setTextSize(float textSize) 設定文字大小
    • Paint.setAntiAlias(boolean aa) 設定抗鋸齒開關

對於比較習慣於自學的人(我就是這樣的人),你看到這裡就已經可以去 Google 的官方文件裡,開啟 CanvasPaint 的頁面,把上面的這兩類方法學習一下,然後今天的內容就算結束了。當然,這篇文章也可以關掉了。

下面的內容就是展開講解上面的這兩類方法。

Canvas.drawColor(@ColorInt int color) 顏色填充

這是最基本的 drawXXX() 方法:在整個繪製區域統一塗上指定的顏色。

例如 drawColor(Color.BLACK) 會把整個區域染成純黑色,覆蓋掉原有內容; drawColor(Color.parse("#88880000") 會在原有的繪製效果上加一層半透明的紅色遮罩。

drawColor(Color.BLACK);  // 純黑複製程式碼

drawColor(Color.parse("#88880000"); // 半透明紅色複製程式碼

類似的方法還有 drawRGB(int r, int g, int b)drawARGB(int a, int r, int g, int b) ,它們和 drawColor(color) 只是使用方式不同,作用都是一樣的。

canvas.drawRGB(100, 200, 100);
canvas.drawARGB(100, 100, 200, 100);複製程式碼

這類顏色填充方法一般用於在繪製之前設定底色,或者在繪製之後為介面設定半透明蒙版。

drawCircle(float centerX, float centerY, float radius, Paint paint) 畫圓

前兩個引數 centerX centerY 是圓心的座標,第三個引數 radius 是圓的半徑,單位都是畫素,它們共同構成了這個圓的基本資訊(即用這幾個資訊可以構建出一個確定的圓);第四個引數 paint 我在視訊裡面已經說過了,它提供基本資訊之外的所有風格資訊,例如顏色、線條粗細、陰影等。

canvas.drawCircle(300, 300, 200, paint);複製程式碼

那位說:「你等會兒!先別往後講,你剛才說圓心的座標,我想問座標系在哪兒呢?沒座標系你跟我聊什麼座標啊。」

我想說:問得好(強行插入劇情)。在 Android 裡,每個 View 都有一個自己的座標系,彼此之間是不影響的。這個座標系的原點是 View 左上角的那個點;水平方向是 x 軸,右正左負;豎直方向是 y 軸,下正上負(注意,是下正上負,不是上正下負,和上學時候學的座標系方向不一樣)。也就是下面這個樣子。

所以一個 View 的座標 (x, y) 處,指的就是相對它的左上角那個點的水平方向 x 畫素、豎直方向 y 畫素的點。例如,(300, 300) 指的就是左上角的點向右 300 、向下 300 的位置; (100, -50) 指的就是左上角的點向右 100 、向上 50 的位置。

也就是說, canvas.drawCircle(300, 300, 200, paint) 這行程式碼繪製出的圓,在 View 中的位置和尺寸應該是這樣的:

圓心座標和半徑,這些都是圓的基本資訊,也是它的獨有資訊。什麼叫獨有資訊?就是隻有它有,別人沒有的資訊。你畫圓有圓心座標和半徑,畫方有嗎?畫橢圓有嗎?這就叫獨有資訊。獨有資訊都是直接作為引數寫進 drawXXX() 方法裡的(比如 drawCircle(centerX, centerY, radius, paint) 的前三個引數)。

而除此之外,其他的都是公有資訊。比如圖形的顏色、空心實心這些,你不管是畫圓還是畫方都有可能用到的,這些資訊則是統一放在 paint 引數裡的。

插播一: Paint.setColor(int color)

例如,你要畫一個紅色的圓,並不是寫成 canvas.drawCircle(300, 300, 200, RED, paint) 這樣,而是像下面這樣:

paint.setColor(Color.RED); // 設定為紅色
canvas.drawCircle(300, 300, 200, paint);複製程式碼

Paint.setColor(int color)Paint 最常用的方法之一,用來設定繪製內容的顏色。你不止可以用它畫紅色的圓,也可以用它來畫紅色的矩形、紅色的五角星、紅色的文字。

插播二: Paint.setStyle(Paint.Style style)

而如果你想畫的不是實心圓,而是空心圓(或者叫環形),也可以使用 paint.setStyle(Paint.Style.STROKE) 來把繪製模式改為畫線模式。

paint.setStyle(Paint.Style.STROKE); // Style 修改為畫線模式
canvas.drawCircle(300, 300, 200, paint);複製程式碼

setStyle(Style style) 這個方法設定的是繪製的 StyleStyle 具體來說有三種: FILL, STROKEFILL_AND_STROKEFILL 是填充模式,STROKE 是畫線模式(即勾邊模式),FILL_AND_STROKE 是兩種模式一併使用:既畫線又填充。它的預設值是 FILL,填充模式。

插播三: Paint.setStrokeWidth(float width)

STROKEFILL_AND_STROKE 下,還可以使用 paint.setStrokeWidth(float width) 來設定線條的寬度:

paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(20); // 線條寬度為 20 畫素
canvas.drawCircle(300, 300, 200, paint);複製程式碼

插播四: 抗鋸齒

在繪製的時候,往往需要開啟抗鋸齒來讓圖形和文字的邊緣更加平滑。開啟抗鋸齒很簡單,只要在 new Paint() 的時候加上一個 ANTI_ALIAS_FLAG 引數就行:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);複製程式碼

另外,你也可以使用 Paint.setAntiAlias(boolean aa) 來動態開關抗鋸齒。

抗鋸齒的效果如下:

可以看出,沒有開啟抗鋸齒的時候,圖形會有毛片現象,啊不,毛邊現象。所以一定記得要開啟抗鋸齒喲!

可以跳過的冷知識

好奇的人可能會問:抗鋸齒既然這麼有用,為什麼不預設開啟,或者乾脆把這個開關取消,自動讓所有繪製都開啟抗鋸齒?

短答案:因為抗鋸齒並不一定適合所有場景。

長答案:所謂的毛邊或者鋸齒,發生的原因並不是很多人所想象的「繪製太粗糙」「畫素計算能力不足」;同樣,抗鋸齒的原理也並不是選擇了更精細的演算法來算出了更平滑的圖形邊緣。 實質上,鋸齒現象的發生,只是由於圖形解析度過低,導致人眼察覺出了畫面中的畫素顆粒而已。換句話說,就算不開啟抗鋸齒,圖形的邊緣也已經是最完美的了,而並不是一個粗略計算的粗糙版本。 那麼,為什麼抗鋸齒開啟之後的圖形邊緣會更加平滑呢?因為抗鋸齒的原理是:修改圖形邊緣處的畫素顏色,從而讓圖形在肉眼看來具有更加平滑的感覺。一圖勝千言,上圖:

上面這個是把前面那兩個圓放大後的區域性效果。看到沒有?未開啟抗鋸齒的圓,所有畫素都是同樣的黑色,而開啟了抗鋸齒的圓,邊緣的顏色被略微改變了。這種改變可以讓人眼有邊緣平滑的感覺,但從某種角度講,它也造成了圖形的顏色失真。 所以,抗鋸齒好不好?好,大多數情況下它都應該是開啟的;但在極少數的某些時候,你還真的需要把它關閉。「某些時候」是什麼時候?到你用到的時候自然就知道了。

除了圓,Canvas 還可以繪製一些別的簡單圖形。它們的使用方法和 drawCircle() 大同小異,我就只對它們的 API 做簡單的介紹,不再做詳細的講解。

drawRect(float left, float top, float right, float bottom, Paint paint) 畫矩形

left, top, right, bottom 是矩形四條邊的座標。

paint.setStyle(Style.FILL);
canvas.drawRect(100, 100, 500, 500, paint);

paint.setStyle(Style.STROKE);
canvas.drawRect(700, 100, 1100, 500, paint);複製程式碼

另外,它還有兩個過載方法 drawRect(RectF rect, Paint paint)drawRect(Rect rect, Paint paint) ,讓你可以直接填寫 RectFRect 物件來繪製矩形。

好吧 暫時休息下 先到這 我們接著下篇來