HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解
這期是 HenCoder 自定義繪制的第二期:?Paint。如果你沒看過第一期,可以先去看一下第一期:
HenCoder Android 開發進階:自定義 View 1-1 繪制基礎
簡介
上一期我已經簡單說過,?Canvas?的?drawXXX()?方法配合?Paint?的幾個常用方法可以實現最常見的繪制需求;而如果你只會基本的繪制,?Paint?的完全功能的掌握,能讓你更進一步,做出一些更加細致、炫酷的效果。把?Paint?掌握之后,你幾乎不再會遇到「iOS 組可以實現,但你卻實現不了」的繪制效果。
由于依然是講繪制的,所以這期就沒有介紹視頻了。繪制的內容一共需要講大概 5~6 期才能講完,也就是說你要看 5~6 期才能成為自定義繪制的高手。相對于上期的內容,這期的內容更為專項、深度更深。對于沒有深入研究過?Paint?的人,這期是一個對?Paint?的詮釋;而對于嘗試過研究?Paint?但仍然對其中一些 API 有疑惑的人,這期也可以幫你解惑。
另外,也正由于這期的內容是更為專項的,所以建議你在看的時候,不必像上期那樣把所有東西都完全記住,而是只要把內容理解了就好。這期的內容,只要做到「知道有這么個東西」,在需要用到的時候能想起來這個功能能不能做、大致用什么做就好,至于具體的實現,到時候拐回來再翻一次就行了。
好,下面進入正題。
Paint?的 API 大致可以分為 4 類:
- 顏色
- 效果
- drawText() 相關
- 初始化
下面我就對這 4 類分別進行介紹:
1 顏色
Canvas?繪制的內容,有三層對顏色的處理:
這圖大概看看就行,不用鉆研明白再往下看,因為等這章講完你就懂了。
1.1 基本顏色
像素的基本顏色,根據繪制內容的不同而有不同的控制方式:?Canvas?的顏色填充類方法?drawColor/RGB/ARGB()?的顏色,是直接寫在方法的參數里,通過參數來設置的(上期講過了);?drawBitmap()?的顏色,是直接由?Bitmap?對象來提供的(上期也講過了);除此之外,是圖形和文字的繪制,它們的顏色就需要使用?paint?參數來額外設置了(下面要講的)。
Paint?設置顏色的方法有兩種:一種是直接用?Paint.setColor/ARGB()?來設置顏色,另一種是使用?Shader?來指定著色方案。
1.1.1 直接設置顏色
1.1.1.1 setColor(int color)
方法名和使用方法都非常簡單直接,而且這個方法在上期已經介紹過了,不再多說。
paint.setColor(Color.parseColor("#009688")); canvas.drawRect(30, 30, 230, 180, paint);paint.setColor(Color.parseColor("#FF9800")); canvas.drawLine(300, 30, 450, 180, paint);paint.setColor(Color.parseColor("#E91E63")); canvas.drawText("HenCoder", 500, 130, paint);setColor()?對應的 get 方法是?getColor()
1.1.1.2 setARGB(int a, int r, int g, int b)
其實和?setColor(color)?都是一樣一樣兒的,只是它的參數用的是更直接的三原色與透明度的值。實際運用中,setColor()?和?setARGB()?哪個方便和順手用哪個吧。
paint.setARGB(100, 255, 0, 0); canvas.drawRect(0, 0, 200, 200, paint); paint.setARGB(100, 0, 0, 0); canvas.drawLine(0, 0, 200, 200, paint);1.1.2 setShader(Shader shader) 設置 Shader
除了直接設置顏色,?Paint?還可以使用?Shader?。
Shader 這個英文單詞很多人沒有見過,它的中文叫做「著色器」,也是用于設置繪制顏色的。「著色器」不是 Android 獨有的,它是圖形領域里一個通用的概念,它和直接設置顏色的區別是,著色器設置的是一個顏色方案,或者說是一套著色規則。當設置了?Shader?之后,Paint?在繪制圖形和文字時就不使用?setColor/ARGB()?設置的顏色了,而是使用?Shader?的方案中的顏色。
在 Android 的繪制里使用?Shader?,并不直接用?Shader?這個類,而是用它的幾個子類。具體來講有?LinearGradient?RadialGradient?SweepGradient?BitmapShader?ComposeShader?這么幾個:
1.1.2.1 LinearGradient 線性漸變
設置兩個點和兩種顏色,以這兩個點作為端點,使用兩種顏色的漸變來繪制顏色。就像這樣:
Shader shader = new LinearGradient(100, 100, 500, 500, Color.parseColor("#E91E63"), Color.parseColor("#2196F3"), Shader.TileMode.CLAMP); paint.setShader(shader);...canvas.drawCircle(300, 300, 200, paint);設置了?Shader?之后,繪制出了漸變顏色的圓。(其他形狀以及文字都可以這樣設置顏色,我只是沒給出圖。)
注意:在設置了?Shader?的情況下,?Paint.setColor/ARGB()?所設置的顏色就不再起作用。
構造方法:?
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)。
參數:?
x0?y0?x1?y1:漸變的兩個端點的位置?
color0?color1?是端點的顏色?
tile:端點范圍之外的著色規則,類型是?TileMode。TileMode?一共有 3 個值可選:?CLAMP,?MIRROR和?REPEAT。CLAMP?(夾子模式???算了這個詞我不會翻)會在端點之外延續端點處的顏色;MIRROR是鏡像模式;REPEAT?是重復模式。具體的看一下例子就明白。
CLAMP:
MIRROR:
REPEAT:
1.1.2.2 RadialGradient 輻射漸變
輻射漸變很好理解,就是從中心向周圍輻射狀的漸變。大概像這樣:
Shader shader = new RadialGradient(300, 300, 200, Color.parseColor("#E91E63"), Color.parseColor("#2196F3"), Shader.TileMode.CLAMP); paint.setShader(shader);...canvas.drawCircle(300, 300, 200, paint);構造方法:?
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, TileMode tileMode)。
參數:?
centerX?centerY:輻射中心的坐標?
radius:輻射半徑?
centerColor:輻射中心的顏色?
edgeColor:輻射邊緣的顏色?
tileMode:輻射范圍之外的著色模式。
CLAMP:
MIRROR:
REPEAT:
1.1.2.3 SweepGradient 掃描漸變
又是一個漸變。「掃描漸變」這個翻譯我也不知道精確不精確。大概是這樣:
Shader shader = new SweepGradient(300, 300, Color.parseColor("#E91E63"), Color.parseColor("#2196F3")); paint.setShader(shader);...canvas.drawCircle(300, 300, 200, paint);構造方法:?
SweepGradient(float cx, float cy, int color0, int color1)
參數:?
cx?cy?:掃描的中心?
color0:掃描的起始顏色?
color1:掃描的終止顏色
1.1.2.4 BitmapShader
用?Bitmap?來著色(終于不是漸變了)。其實也就是用?Bitmap?的像素來作為圖形或文字的填充。大概像這樣:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.batman); Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); paint.setShader(shader);...canvas.drawCircle(300, 300, 200, paint);嗯,看著跟?Canvas.drawBitmap()?好像啊?事實上也是一樣的效果。如果你想繪制圓形的?Bitmap,就別用?drawBitmap()?了,改用?drawCircle()?+?BitmapShader?就可以了(其他形狀同理)。
構造方法:?
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
參數:?
bitmap:用來做模板的?Bitmap?對象?
tileX:橫向的?TileMode?
tileY:縱向的?TileMode。
CLAMP:
MIRROR:
REPEAT:
1.1.2.5 ComposeShader 混合著色器
所謂混合,就是把兩個?Shader?一起使用。
// 第一個 Shader:頭像的 Bitmap Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.batman); Shader shader1 = new BitmapShader(bitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);// 第二個 Shader:從上到下的線性漸變(由透明到黑色) Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.batman_logo); Shader shader2 = new BitmapShader(bitmap2, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);// ComposeShader:結合兩個 Shader Shader shader = new ComposeShader(shader1, shader2, PorterDuff.Mode.SRC_OVER); paint.setShader(shader);...canvas.drawCircle(300, 300, 300, paint);注意:上面這段代碼中我使用了兩個?BitmapShader?來作為?ComposeShader()?的參數,而?ComposeShader()?在硬件加速下是不支持兩個相同類型的?Shader?的,所以這里也需要關閉硬件加速才能看到效果。
構造方法:ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
參數:?
shaderA,?shaderB:兩個相繼使用的?Shader?
mode: 兩個?Shader?的疊加模式,即?shaderA?和?shaderB?應該怎樣共同繪制。它的類型是?PorterDuff.Mode?。
PorterDuff.Mode
PorterDuff.Mode?是用來指定兩個圖像共同繪制時的顏色策略的。它是一個 enum,不同的?Mode?可以指定不同的策略?!割伾呗浴沟囊馑?#xff0c;就是說把源圖像繪制到目標圖像處時應該怎樣確定二者結合后的顏色,而對于?ComposeShader(shaderA, shaderB, mode)?這個具體的方法,就是指應該怎樣把?shaderB?繪制在?shaderA?上來得到一個結合后的?Shader。
沒有聽說過?PorterDuff.Mode?的人,看到這里很可能依然會一頭霧水:「什么怎么結合?就……兩個圖像一疊加,結合唄?還能怎么結合?」你還別說,還真的是有很多種策略來結合。
最符合直覺的結合策略,就是我在上面這個例子中使用的?Mode:?SRC_OVER。它的算法非常直觀:就像上面圖中的那樣,把源圖像直接鋪在目標圖像上。不過,除了這種,其實還有一些其他的結合方式。例如如果我把上面例子中的參數?mode?改為?PorterDuff.Mode.DST_OUT,就會變成挖空效果:
而如果再把?mode?改為?PorterDuff.Mode.DST_IN,就會變成蒙版摳圖效果:
這下明白了吧?
具體來說,?PorterDuff.Mode?一共有 17 個,可以分為兩類:
第一類,Alpha 合成,其實就是 「PorterDuff」 這個詞所指代的算法。 「PorterDuff」 并不是一個具有實際意義的詞組,而是兩個人的名字(準確講是姓)。這兩個人當年共同發表了一篇論文,描述了 12 種將兩個圖像共同繪制的操作(即算法)。而這篇論文所論述的操作,都是關于 Alpha 通道(也就是我們通俗理解的「透明度」)的計算的,后來人們就把這類計算稱為Alpha 合成?( Alpha Compositing ) 。
看下效果吧。效果直接盜 Google 的官方文檔了。
源圖像和目標圖像:
Alpha 合成:
第二類,混合,也就是 Photoshop 等制圖軟件里都有的那些混合模式(multiply?darken?lighten之類的)。這一類操作的是顏色本身而不是?Alpha?通道,并不屬于?Alpha?合成,所以和 Porter 與 Duff 這兩個人也沒什么關系,不過為了使用的方便,它們同樣也被 Google 加進了?PorterDuff.Mode里。
效果依然盜?官方文檔。
結論
從效果圖可以看出,Alpha 合成類的效果都比較直觀,基本上可以使用簡單的口頭表達來描述它們的算法(起碼對于不透明的源圖像和目標圖像來說是可以的),例如?SRC_OVER?表示「二者都繪制,但要源圖像放在目標圖像的上面」,DST_IN?表示「只繪制目標圖像,并且只繪制它和源圖像重合的區域」。
而混合類的效果就相對抽象一些,只從效果圖不太能看得出它們的著色算法,更看不出來它們有什么用。不過沒關系,你如果拿著這些名詞去問你司的設計師,他們八成都能給你說出來個 123。
所以對于這些?Mode,正確的做法是:對于 Alpha 合成類的操作,掌握他們,并在實際開發中靈活運用;而對于混合類的,你只要把它們的名字記住就好了,這樣當某一天設計師告訴你「我要做這種混合效果」的時候,你可以馬上知道自己能不能做,怎么做。
另外:PorterDuff.Mode?建議你動手用一下試試,對加深理解有幫助。
好了,這些就是幾個?Shader?的具體介紹。
除了使用?setColor/ARGB()?和?setShader()?來設置基本顏色,?Paint?還可以來設置?ColorFilter,來對顏色進行第二層處理。
1.2 setColorFilter(ColorFilter colorFilter)
ColorFilter?這個類,它的名字已經足夠解釋它的作用:為繪制設置顏色過濾。顏色過濾的意思,就是為繪制的內容設置一個統一的過濾策略,然后?Canvas.drawXXX()?方法會對每個像素都進行過濾后再繪制出來。舉幾個現實中比較常見的顏色過濾的例子:
-
有色光照射:
-
有色玻璃透視:
-
膠卷:
在?Paint?里設置?ColorFilter?,使用的是?Paint.setColorFilter(ColorFilter filter)?方法。?ColorFilter?并不直接使用,而是使用它的子類。它共有三個子類:LightingColorFilterPorterDuffColorFilter?和?ColorMatrixColorFilter。
1.2.1 LightingColorFilter
這個?LightingColorFilter?是用來模擬簡單的光照效果的。
LightingColorFilter?的構造方法是?LightingColorFilter(int mul, int add)?,參數里的?mul?和?add?都是和顏色值格式相同的 int 值,其中?mul?用來和目標像素相乘,add?用來和目標像素相加:
R' = R * mul.R / 0xff + add.R G' = G * mul.G / 0xff + add.G B' = B * mul.B / 0xff + add.B一個「保持原樣」的「基本?LightingColorFilter?」,mul?為?0xffffff,add?為?0x000000(也就是0),那么對于一個像素,它的計算過程就是:
R' = R * 0xff / 0xff + 0x0 = R // R' = R G' = G * 0xff / 0xff + 0x0 = G // G' = G B' = B * 0xff / 0xff + 0x0 = B // B' = B基于這個「基本?LightingColorFilter?」,你就可以修改一下做出其他的 filter。比如,如果你想去掉原像素中的紅色,可以把它的?mul?改為?0x00ffff?(紅色部分為 0 ) ,那么它的計算過程就是:
R' = R * 0x0 / 0xff + 0x0 = 0 // 紅色被移除 G' = G * 0xff / 0xff + 0x0 = G B' = B * 0xff / 0xff + 0x0 = B具體效果是這樣的:
ColorFilter lightingColorFilter = new LightingColorFilter(0x00ffff, 0x000000); paint.setColorFilter(lightingColorFilter);表情忽然變得陰郁了
或者,如果你想讓它的綠色更亮一些,就可以把它的?add?改為?0x003000?(綠色部分為 0x30 ),那么它的計算過程就是:
R' = R * 0xff / 0xff + 0x0 = R G' = G * 0xff / 0xff + 0x30 = G + 0x30 // 綠色被加強 B' = B * 0xff / 0xff + 0x0 = B效果是這樣:
ColorFilter lightingColorFilter = new LightingColorFilter(0xffffff, 0x003000); paint.setColorFilter(lightingColorFilter);這樣的表情才陽光
至于怎么修改參數來模擬你想要的某種具體光照效果,你就別問我了,還是跟你司設計師討論吧,這個我不專業……
1.2.2 PorterDuffColorFilter
這個?PorterDuffColorFilter?的作用是使用一個指定的顏色和一種指定的?PorterDuff.Mode?來與繪制對象進行合成。它的構造方法是?PorterDuffColorFilter(int color, PorterDuff.Mode mode)?其中的?color?參數是指定的顏色,?mode?參數是指定的?Mode。同樣也是?PorterDuff.Mode?,不過和?ComposeShader?不同的是,PorterDuffColorFilter?作為一個?ColorFilter,只能指定一種顏色作為源,而不是一個?Bitmap。
PorterDuff.Mode?前面已經講過了,而?PorterDuffColorFilter?本身的使用是非常簡單的,所以不再展開講。
1.2.3 ColorMatrixColorFilter
這個就厲害了。ColorMatrixColorFilter?使用一個?ColorMatrix?來對顏色進行處理。?ColorMatrix這個類,內部是一個 4x5 的矩陣:
[ a, b, c, d, e,f, g, h, i, j,k, l, m, n, o,p, q, r, s, t ]通過計算,?ColorMatrix?可以把要繪制的像素進行轉換。對于顏色 [R, G, B, A] ,轉換算法是這樣的:
R’ = a*R + b*G + c*B + d*A + e; G’ = f*R + g*G + h*B + i*A + j; B’ = k*R + l*G + m*B + n*A + o; A’ = p*R + q*G + r*B + s*A + t;ColorMatrix?有一些自帶的方法可以做簡單的轉換,例如可以使用?setSaturation(float sat)?來設置飽和度;另外你也可以自己去設置它的每一個元素來對轉換效果做精細調整。具體怎樣設置會有怎樣的效果,我就不講了(其實是我也不太會
總結
以上是生活随笔為你收集整理的HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 06-Flutter移动电商实战-dio
- 下一篇: 67-Flutter中高德地图插件的使用