1. 程式人生 > >Android應用自定義View繪製方法手冊

Android應用自定義View繪製方法手冊

背景

這篇遲遲難產的文章算是對2015前半年的一個交代吧,那時候有一哥們要求來一發Android Canvas相關總結,這哥們還打賞了,實在不好意思,可是這事一放就給放忘了,最近群裡小夥伴催著說沒更新部落格,坐等更新啥的,隨先有這麼一篇Android應用開發超級基礎的文章誕生了(因為這種文章最好寫哈,就是用熟了就行)。不得不說下這麼久為何一直沒更新部落格的原因了,首先遇上了過年,我個人崇尚過節就該放下一切好好陪陪親人,珍惜在一起的時光;其次今年開年很是蛋疼,不是不順當就是深深的覺得被坑,所以心情也就低落那麼一段時間,好在最近調整了一下,所以期待的文章日後還會持續,當年吹過的牛逼還得繼續努力。

提到自定義繪製首先需要知道Canvas(android.graphics.Canvas),其實質就是一塊畫布,我們不僅可以設定畫布的一些屬性,還可以在上面畫想畫的任何東西。記不記得我們在自定義View時會重寫如下方法:

protected void onDraw(Canvas canvas) {
}

該方法有一個牛逼的形參,那就是Canvas,當我們實現自己的自定義繪製時基本都是將內容畫到這個Canvas畫布上以後交給系統框架顯示的;通過之前《Android應用層View繪製流程與原始碼分析》一文我們知道,整個View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法中觸發的,該方法執行過程主要是根據之前設定的狀態,判斷是否重新計算檢視大小(measure)、是否重新放置檢視的位置(layout)、以及是否重繪(draw),在ViewRootImpl中我們有一個Surface成員,當ViewRootImpl觸發performTraversals()進行重繪時會將該Surface的Canvas通過draw方法進行遞迴傳遞,從ViewGroup派發傳遞到最小的View元素的onDraw(Canvas canvas)方法。

這就解釋了View中onDraw(Canvas canvas)形參的由來,所以當我們將自定義邏輯繪製到該Canvas後系統框架會通過呼叫SurfaceFlinger服務把測量、佈局、繪製後的Surface渲染到螢幕上的(關於這個過程是很複雜的,後面會寫文章分析的,這一原理不作為本文重點)。

有了畫布這玩意的背景知識,那我們下面就來開始繪製相關的東東講解;PS一句,Android的所有View控制元件無非都是文中這樣為基石搞出來,搞懂基礎後剩下的就是萬變不離其宗了,掌握本文系列文章你可以隨處裝逼隨處飛了。

這裡寫圖片描述

溫馨提示:文章巨長!!!做好分小節閱讀準備(因為自己不想為了提升PV而搞成多篇,自己比較懶,一篇好管理,回顧時只需要Ctrl+F在這一篇搜尋關鍵字就行了)。

這裡寫圖片描述

Android Paint攻略

上面提到了畫布,那我們就得來一個筆才能畫畫啊,筆就是Paint,所以我們有必要先全面的對Paint進行掃盲。這貨繼承自Object,是graphics家族的東西,他有一個子類TextPaint,這個就不多說了,譬如實現繪製文字時換行就需要使用StaticLayout與TextPaint兩個工具類結合(當年見過一個類似的需求,是用Paint去實現的,看著那一堆計算都蛋疼,為何不用TextPaint呢?)。

Paint的方法使用技巧

Paint的方法主要可以抽象成兩大類,一類負責設定獲取文字相關的東西,一類負責設定獲取圖形繪製相關的東西;其實就像是我們拿到了一張白紙,如何調色及選筆的過程就是Paint方法使用的過程,具體如下:

float getFontSpacing()
獲取字元行間距。

float getLetterSpacing()
void setLetterSpacing(float letterSpacing)
設定和獲取字元間距。

final boolean isUnderlineText()
void setUnderlineText(boolean underlineText)
是否有下劃線和設定下劃線。

final boolean isStrikeThruText()
void setStrikeThruText(boolean strikeThruText)
獲取與設定是否有文字刪除線。

float getTextSize()
void setTextSize(float textSize)
獲取與設定文字大小,注意:Paint.setTextSize傳入的單位是px,TextView.setTextSize傳入的單位是sp,注意使用時不同解析度處理問題。

Typeface getTypeface()
Typeface setTypeface(Typeface typeface)
獲取與設定字型型別。Android預設有四種字型樣式:BOLD(加粗)、BOLD_ITALIC(加粗並傾斜)、ITALIC(傾斜)、NORMAL(正常),我們也可以通過Typeface類來自定義個性化字型。

boolean hasGlyph(String string)
確定Paint設定的Typeface是否支援該字串。

float getTextSkewX()
void setTextSkewX(float skewX)
獲取與設定文字傾斜,引數沒有具體範圍,官方推薦值為-0.25,值為負則右傾,為正則左傾,預設值為0。

float getTextScaleX()
void setTextScaleX(float scaleX)
獲取與設定文字沿X軸水平縮放值,預設為1,當值大於1會沿X軸水平放大文字,當值小於1會沿X軸水平縮小文字,不僅會改變文字寬度,還會拉伸或壓縮字元。

Paint.Align getTextAlign()
void setTextAlign(Paint.Align align)
獲取與設定文字對齊方式,取值為CENTER、LEFT、RIGHT,也就是文字繪製是左邊對齊、右邊還是局中的。

int breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth)
int breakText(CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth)
int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
計算指定引數長度能顯示多少個字元,同時可以獲取指定引數下可顯示字元的真實長度,譬如:

private static final String STR = "我們XXOOCC";
mPaint.setTextSize(50);
float[] value = new float[1];
int ret = mPaint.breakText(STR, true, 200, value);
Log.i("YYYY", "breakText="+ret+", STR="+STR.length()+", value="+value[1]);
//breakText=5, STR=8, value=195.0

float getFontMetrics(Paint.FontMetrics metrics)
Paint.FontMetrics getFontMetrics()
Paint.FontMetricsInt getFontMetricsInt()
int getFontMetricsInt(Paint.FontMetricsInt fmi)
getFontMetrics()返回FontMetrics物件;getFontMetrics(Paint.FontMetrics metrics)返回文字的行間距,metrics的值不為空則返回FontMetrics物件的值;getFontMetricsInt()返回FontMetricsInt物件,FontMetricsInt和FontMetrics物件一樣,只不過FontMetricsInt返回的是int而FontMetrics返回的是float。FontMetrics與FontMetricsInt都有top、ascent、descent、bottom、leading這幾個屬性,具體介紹如下圖所示(此圖來自網路,致謝出處):
這裡寫圖片描述
可以看見,我們使用canvas的drawText繪製文字傳入的y其實是上圖的base線,繪製文字座標是以base基線為參考的;FontMetrics中的top是base到最高字元的最大值(即ascent的最大值),ascent是base到最高字元的推薦值,descent是base到最低字元的推薦值,bottom是base到最低字元的最大值(即decent的最大值),leading是文字行之間推薦的額外高度,單行文字一般為0。所以獲取文字的高就是mPaint.ascent() + mPaint.descent(),獲取文字的寬就是mPaint.measureText(text)。

float ascent()
float descent()
ascent獲取baseline之上至字元最高處的距離,具體參見FontMetrics。descent獲取baseline之下至字元最低處的距離,具體參見FontMetrics。

void getTextBounds(char[] text, int index, int count, Rect bounds)
void getTextBounds(String text, int start, int end, Rect bounds)
獲取文字的寬高,通過bounds的Rect拿到整型。

float measureText(String text)
float measureText(CharSequence text, int start, int end)
float measureText(String text, int start, int end)
float measureText(char[] text, int index, int count)
粗略獲取文字的寬度,和上面的getTextBounds比較類似,返回浮點數。

int getTextWidths(String text, int start, int end, float[] widths)
int getTextWidths(String text, float[] widths)
int getTextWidths(CharSequence text, int start, int end, float[] widths)
int getTextWidths(char[] text, int index, int count, float[] widths)
精確計算文字寬度,與上面兩個類似。

Locale getTextLocale()
void setTextLocale(Locale locale)
獲取與設定地理位置,一般直接傳入Locale.getDefault()即可。用來設定文字的區域比如中文、法文等。

final boolean isSubpixelText()
void setSubpixelText(boolean subpixelText)
獲取與設定文字顯示效果,設定該項為true,將有助於文字在LCD螢幕上的顯示效果。

final boolean isFakeBoldText()
void setFakeBoldText(boolean fakeBoldText)
獲取與設定文字是否仿粗體,小字型設定效果非常差。

final boolean isLinearText()
void setLinearText(boolean linearText)
獲取與設定是否開啟線性文字標識。因為預設Android中文字繪製需要使用一個Bitmap作為單個字元快取,因此會使用一定的空間,為了不使用該空間則設為true即可。

void getTextPath(char[] text, int index, int count, float x, float y, Path path)
void getTextPath(String text, int start, int end, float x, float y, Path path)
獲取文字輪廓的Path值,例子如下:

Path path = new Path();
mPaint.getTextPath(STR, 0, STR.length(), 0, 800, path);
path.close();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(5);
canvas.drawPath(path, mPaint);
//顯示出來的Path路徑繪製就是STR字串,特別注意Path要close,否則無效。

void reset()
清空畫筆復位。

void set(Paint src)
設定一個外來Paint畫筆。

int getFlags()
void setFlags(int flags)
獲取與設定Paint的一些屬性flag,譬如抗鋸齒、防抖等。可取值為ANTI_ALIAS_FLAG、DEV_KERN_TEXT_FLAG、DITHER_FLAG、EMBEDDED_BITMAP_TEXT_FLAG、FAKE_BOLD_TEXT_FLAG、FILTER_BITMAP_FLAG、HINTING_OFF、HINTING_ON、LINEAR_TEXT_FLAG、STRIKE_THRU_TEXT_FLAG、SUBPIXEL_TEXT_FLAG、UNDERLINE_TEXT_FLAG,這些flag大多都有對應的Paint方法設定與獲取判斷,多個設定可以通過按位或操作即可。

void setARGB(int a, int r, int g, int b)
int getAlpha()
void setAlpha(int a)
int getColor()
void setColor(int color)
獲取與設定alpha值、顏色、ARGB等。

final boolean isAntiAlias()
void setAntiAlias(boolean aa)
獲取與設定是否使用抗鋸齒功能,會消耗較大資源,繪製圖形速度會變慢,一般會開啟。

final boolean isDither()
void setDither(boolean dither)
獲取與設定是否使用影象抖動處理,會使繪製出來的圖片顏色更加平滑和飽滿、影象更加清晰。

final boolean isFilterBitmap()
void setFilterBitmap(boolean filter)
獲取與設定影象過濾處理,一般設定為true。

Paint.Cap getStrokeCap()
void setStrokeCap(Paint.Cap cap)
當畫筆樣式為STROKE或FILL_OR_STROKE時,獲取與設定畫筆開始與離開時那一點樣式(譬如圓角直線頂點),可取值與效果如下圖:
這裡寫圖片描述
不設定預設值是BUTT。

Paint.Join getStrokeJoin()
void setStrokeJoin(Paint.Join join)
獲取與設定畫筆畫線等連線處的輪廓樣式,可取值與效果如下圖:
這裡寫圖片描述
不設定預設是MITER。

float getStrokeMiter()
void setStrokeMiter(float miter)
獲取與設定畫筆的傾斜度。

Paint.Style getStyle()
void setStyle(Paint.Style style)
獲取與設定畫筆樣式,可取值如下:
FILL:實心。
FILL_OR_STROKE:同時實心和空心。
STROKE:空心。
注意STROKE、FILL_OR_STROKE與FILL模式下外輪廓的位置會擴大。

float getStrokeWidth()
void setStrokeWidth(float width)
獲取與設定畫筆的粗細,在畫筆樣式為STROKE或FILL_OR_STROKE時有效。

void clearShadowLayer()
void setShadowLayer(float radius, float dx, float dy, int shadowColor)
清除與設定陰影層和顏色,譬如繪製一個圓,在圓周給一個陰影擴散效果就可用它。

Shader getShader()
Shader setShader(Shader shader)
非常重要的方法,獲取與設定渲染方法。Shader的直接子類有BitmapShader點陣圖影象渲染、LinearGradient線性渲染、RadialGradient環形渲染、SweepGradient掃描漸變渲染/梯度渲染、ComposeShader組合渲染,我們可以單獨或者組合使用。具體描述如下:
Shader基類有一個Shader.TileMode的列舉類,其值CLAMP是拉伸最後一個畫素鋪滿、REPEAT是類似電腦桌布,橫向縱向不足的重複放置、MIRROR是橫向縱向不足處不斷翻轉映象平鋪;該類還提供boolean getLoaclMatrix(Matrix localM)與void setLocalMatrix(Matrix localM)兩個矩陣變換方法;下面我們來看下他的子類使用。
1、BitmapShader點陣圖影象渲染例項(右側毛爺爺是左側渲染處理過的圖):
這裡寫圖片描述

/**
 * 渲染bitmap圖片為圓角圖片
 * new BitmapShader(bitmap, tileX, tileY)
 * tileX、tileY是點陣圖在XY方向的TileMode模式
 */
Paint mPaint = new Paint();
BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.icon1);
Bitmap bitmap = drawable.getBitmap();

BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
/*如下四行Matrix可以不設定,這裡是為了處理不等寬高圖片渲染後鋪不滿問題
Matrix matrix = new Matrix();
float scale = Math.max(bitmap.getWidth(), bitmap.getHeight())*1.0f / Math.min(bitmap.getWidth(), bitmap.getHeight());
matrix.setScale(scale, scale);
shader.setLocalMatrix(matrix);*/

mPaint.setShader(shader);
canvas.drawCircle(bitmap.getWidth()/2, bitmap.getHeight()/2, bitmap.getHeight()/2, mPaint); //需要啥圖形就怎麼畫

/*如上兩行還可以這麼實現
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
shapeDrawable.getPaint().setShader(shader);
shapeDrawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getWidth());
shapeDrawable.draw(canvas);  */

2、LinearGradient線性渲染:
這個就不用多介紹了,有些文字漸變或者色度條都是通過它實現的,這裡給出它最多引數建構函式的解釋就行了,具體和上面類似用法,如下:

/**
x0為漸變起始點x座標
y0為漸變起始點y座標
x1為漸變結束點x座標
y1為漸變結束點y座標
colors陣列為顏色的int陣列
positions陣列為相對位置的顏色陣列,null則顏色沿漸變線均勻分佈
tile為渲染器平鋪模式Shader.TileMode
*/
public LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions,Shader.TileMode tile) 

3、RadialGradient環形渲染:
廢話不多說,和上面類似,這貨可以直接結合Paint的alpha值來畫畫水波紋啥玩意的,如下:

/**
x為圓心X座標
y為圓心Y座標
radius為圓半徑
colors為渲染顏色陣列
positions為相對位置陣列,可為null,為null則顏色沿漸變線均勻分佈
tile為渲染器平鋪模式
*/
public RadialGradient(float x, float y, float radius, int[] colors, float[] positions,Shader.TileMode tile)

4、SweepGradient掃描漸變渲染/梯度渲染:
同上,不多扯,見過微信雷達新增好友的掃描介面或者各種手機大師的掃描介面就知道這玩意的作用了,一樣給出最核心的建構函式解釋,如下:

/**
cx為渲染中心x座標
cy為渲染中心y座標
color0為起始渲染顏色
color1為結束渲染顏色
*/
public SweepGradient(float cx, float cy, int color0, int color1)

/**
cx為渲染中心x座標
cy為渲染中心y座標
colors為圍繞中心渲染的顏色陣列,最少要有兩種顏色,切記!!!
positions為相對位置的顏色陣列,為null則顏色沿漸變線均勻分佈
*/
public SweepGradient(float cx, float cy, int[] colors, float[] positions)

5、ComposeShader組合渲染:
這個略叼!類似Android動畫中的組合動畫,可以整合上面的幾種,先來看下建構函式解釋,如下:

/**
shaderA為Shader渲染器A
shaderB為Shader渲染器B
mode為兩種渲染器組合的模式,使用Xfermode物件
*/
public ComposeShader(Shader shaderA,Shader shaderB, Xfermode mode)

/**
shaderA為Shader渲染器A
shaderB為Shader渲染器B
mode為兩種渲染器組合的模式,使用ProterDuff.Mode物件
*/
public ComposeShader(Shader shaderA,Shader shaderB, PorterDuff.Mode mode)

上面兩種組合構造方法的具體混合模式和Xfermode與ProterDuff.Mode物件有關,他們又可以與我們傳入的兩種shader進行多重組合實現不同的效果;這兩種模式緊接著的下面方法就會說到。

Xfermode getXfermode()
Xfermode setXfermode(Xfermode xfermode)
非常重要的方法設定兩繪製相交時的模式,因為正常的情況下在已有影象上繪圖會完全遮擋住下面已有的圖,所以setXfermode()方法的作用就是來設定疊加時該如何搞的規則,傳遞null可以清除任何以前的xfermode設定。要想徹底說明白這兩個方法的效果首先我們得看看Xfermode是啥玩;為了說明白,這裡先給出一個簡單的不能再簡單的程式碼:

//canvas上原有的圖片叫做dst
//Canvas上新畫上去的圖片叫做src
Canvas canvas = new Canvas(dstBitmap);  
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));    
canvas.drawBitmap(srcBitmap, 0f, 0f, paint);

有了上面這個程式碼,下面我們就來看看和Xfermode這貨相關的一些東西:
這裡寫圖片描述
可以看見,這貨本來有三個子類呢,結果兩個都被Deprecated down了,那我們重點放在第三個上面,前面兩個簡單說下:
1、Xfermode 的AvoidXfermode子類
該類用來指定一個顏色和容差值控制Paint是否在指定容差顏色範圍內繪製或不繪製。建構函式如下:

/**
opColor 要匹配的目標顏色。
tolerance 容差值,0代表最小容差,也就是我們目標相素值和opcolor顏色一樣的點才匹配成功,255代表最大容差,也就是我們目標畫素值只要和opcolor有一點相近就匹配成功。
mode 有兩個可取列舉值,如下:
    Avoid模式:只在目標畫素值和opcolor匹配成功的地方進行繪製。
    Target模式:只在目標畫素值和opcolor匹配沒成功的地方進行繪製。
*/
public AvoidXfermode(int opColor, int tolerance, Mode mode)

下面給出一個例子的核心程式碼段:

//!!!由於高版本API已經Deprecated,所以必須關閉View硬體加速才有效果!!!
//例子:讓紅色的巨形其他背景色的五角星變成保持背景色不變的黑色五角星,我們可以由此啟發做成依據手勢觸發漸變的效果,譬如拿出你的手機看看微信底部Tab上圖示在你滑動切換頁面時的漸變效果(綠色隨手指滑動到灰色)。

//假設我們有一個紅色的正方形五角星,五角星邊緣到正方形邊緣的填充區域不為紅色(譬如透明或者白色)
mBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.red_star)).getBitmap();
//建立一個AvoidXfermode相關的Paint物件
mAvoidPaint = new Paint();
//設定準備將紅色五角星要變成的顏色,黑色
mAvoidPaint.setColor(Color.BLACK);
//建立一個準備對紅色相近的顏色進行替換的Xfermode 
mAvoidXfermode = new AvoidXfermode(Color.RED, 10, Mode.TARGET);
//繪製圖片
canvas.drawBitmap(mBitmap, mLeft, mTop, mAvoidPaint);
//設定圖層混合模式  
mAvoidPaint.setXfermode(mAvoidXfermode);  
//繪製色塊進行混合(讓mBitmap中在mAvoidDestRect區域接近為紅色的畫素點變為黑色),得到最終效果  
canvas.drawRect(mAvoidDestRect, mAvoidPaint);
//此時畫布上的五角星就變成黑色了

2、Xfermode 的PixelXorXfermode子類
該類用來設定在覆蓋已有畫素顏色時進行畫素的異或操作。建構函式如下:

//opColor 目標顏色
PixelXorXfermode(int opColor)

這個就沒必要再給出例子了,上面那個都能看懂的話,這個就沒啥意思了,也過時了,其核心意思就是重疊的畫素進行opPixColor ^ srcPixColor ^ dstPixColor,變成簡單的異或顏色顯示。

3、Xfermode 的PorterDuffXfermode子類
該類是一個非常強大的轉換模式,我們可以通過設定它PorterDuff規則的任意一條來控制Paint如何與已有的Canvas影象進行圖層重疊處理。簡直就是圖層混合處理界的busybox瑞士軍刀,哈哈,我調皮了,老想著老本行。既然這樣,那我們就先看下PorterDuff.Mode(特別注意:這些模式不僅僅應用於圖形疊加裁剪混合,還應用於影象色彩混合,後面ColorFilter會說到。)列舉類,該列舉類提供了十六種列舉值(你可以進去看看那些列舉值時怎麼算的,自己也可以模仿搞幾個,其中Sa是源alpha值的意思,同理Da是目標alpha值、Sc是源色值、Dc是目標色值,他們進行畫素運算搞出來的效果),其值與作用效果如下圖:
這裡寫圖片描述
(宣告:為了節省成本,此圖來源自來自ApiDemos的XferModes例項,不過蛋疼的時官方竟然只實現了前16種列舉的效果,最後兩種鬼知道為啥一直沒更新,自己去試吧!)
可以看見這些Xfermode的類最終都可以被PorterDuffXfermode終結掉,我們可以通過搞出各種腦洞大開的花樣,譬如獵豹大師那個圓形水波紋盪漾上升的進度條啥玩意的,還有就是那種文字進度條啥玩意的,還有就是各種秀秀那種橡皮擦哇、刮刮樂等等的東東。。。

MaskFilter getMaskFilter()
MaskFilter setMaskFilter(MaskFilter maskfilter)
非常重要的方法,該方法可以用來對影象進行一定的Alpha濾鏡處理(ColorFilter是對RGB進行濾鏡處理,下面會講),MaskFilter類中沒有任何實現方法,我們一般使用的時它的兩個子類BlurMaskFilter和EmbossMaskFilter,前者為模糊遮罩濾鏡,後者為浮雕遮罩濾鏡。具體如下:
1、BlurMaskFilter濾鏡
該類構造方法如下:

//radius 陰影範圍
//style 模糊型別
BlurMaskFilter(float radius, Blur style)

用法也比較簡單,直接給paint設定setMaskFilter後進行繪製即可,不過要特別注意必須關閉硬體加速才有效果,下面給出一個簡單例子:

//必須關閉硬體加速才有效果
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//具備alpha通道的顏色
paint.setColor(Color.RED);
paint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.NORMAL));
canvas.drawRect(rect, paint);

執行效果圖:這裡寫圖片描述

//圖片提取Bitmap的Alpha通道做法
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
//提取Alpha通道
Bitmap bitmapAlpha = bitmap.extractAlpha();
paint.setColor(Color.RED);
paint.setMaskFilter(new BlurMaskFilter(30, BlurMaskFilter.Blur.SOLID));
//繪製模糊背景
canvas.drawBitmap(bitmapAlpha, 300, 500, paint);
//繪製原圖
canvas.drawBitmap(bitmap, 300, 500, paint);

執行效果圖:這裡寫圖片描述

關於建構函式裡第二個引數BlurMaskFilter.Blur的值其實有四個列舉可取,具體效果如下圖:
這裡寫圖片描述
四個列舉值解釋如下:

列舉 含義
NORMAL 整個影象被模糊掉。
SOLID 在影象的Alpha邊界外產生一層與Paint顏色一致的陰影效果而不影響影象本身。
OUTER 在影象的Alpha邊界外產生一層陰影且會將原影象變為透明效果。
INNER 在影象內部邊沿產生模糊效果。


2、EmbossMaskFilter濾鏡
該類主要實現凸起立體感的浮雕效果,也需要關閉硬體加速才有效果,其構造方法如下:

/**
direction 指定長度為3的陣列標量[x,y,z],用來指定光源的方向
ambient 指定周邊背景光,取值為0到1之間
specular 指定鏡面反射係數
blurRadius 指定模糊半徑
*/
public EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)

使用流程和上面差不多,這裡給出一個簡單的例子就行了,如下:

paint.setMaskFilter(new EmbossMaskFilter(new float[]{20, 20, 20}, 0.4f, 10, 15));
canvas.drawRect(rect5, paint);

效果如圖:這裡寫圖片描述

PathEffect getPathEffect()
PathEffect setPathEffect(PathEffect effect)
非常重要的方法,設定Paint繪製路徑的效果,效果來自PathEffect的子類,預設的PathEffect沒有啥方法,他們關係如下:
這裡寫圖片描述
其六個子類的作用及效果比較簡單,例子如下直接說明:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);

PathEffect[] pathEffects = new PathEffect[7];
//不設定效果,預設轉角處帶有毛刺
pathEffects[0] = null;
/**
 * 用平滑的圓角代替尖角
 * radius 轉角處的圓滑程度
 */
pathEffects[1] = new CornerPathEffect(100);
/**
 * 建立一個虛線的輪廓(短橫線或者小圓點)
 * intervals[] 陣列中第一個引數定義第一個實線的長度,第二個引數為虛線長度,以此類推或者重複
 * phase 偏移值,動態改變其值會讓路徑產生動畫的效果,就像進度條往前走一樣
 */
pathEffects[2] = new DashPathEffect(new float[]{50, 20, 5, 10}, 0);
/**
 * 添加了隨機性,類似生鏽鐵絲的效果
 * segmentLength 指定突出雜點的密度,值越小雜點越密集
 * deviation 指定雜點突出的大小,值越大突出的距離越大
 */
pathEffects[3] = new DiscretePathEffect(5f, 5f);
/**
 * 和DashPathEffect類似,只不過可以自定義路徑虛線的樣式
 * shape 自定義的路徑樣式,這裡定義為方形
 * advance 每個shape間的距離
 * phase 偏移值,動態改變其值會讓路徑產生動畫的效果,就像進度條往前走一樣
 * style 設定連線處的樣式,取值如下:
 *      ROTATE 線段連線處的shape進行適當角度旋轉連線
 *      MORPH 線段連線處的shape以拉伸或者壓縮形變連線
 *      TRANSLATE 線段連線處的shape平行平移連線
 */
Path pathShape = new Path();
pathShape.addRect(0, 0, 10, 10, Path.Direction.CCW);
pathEffects[4] = new PathDashPathEffect(pathShape, 20, 0, PathDashPathEffect.Style.ROTATE);
/**
 * 組合兩種路徑效果,先將路徑變成innerpe的效果,再去複合outerpe的路徑效果
 * outerpe PathEffect效果A
 * innerpe PathEffect效果B
 */
pathEffects[5] = new ComposePathEffect(pathEffects[3], pathEffects[4]);
/**
 * 組合兩種路徑效果,把兩種路徑效果加起來再作用於路徑
 * first PathEffect效果A
 * second PathEffect效果B
 */
pathEffects[6] = new SumPathEffect(pathEffects[2], pathEffects[4]);

Path path = new Path();
path.moveTo(0, 0);
for (int index=1; index<20; index++){
    path.lineTo(index*40, (float)Math.random()*150);
}

for (int index=0; index<pathEffects.length; index++){
    paint.setPathEffect(pathEffects[index]);
    canvas.drawPath(path, paint);
    canvas.translate(0, 200);
}

效果如圖:這裡寫圖片描述

ColorFilter getColorFilter()
ColorFilter setColorFilter(ColorFilter filter)
非常重要的方法,上面講了MaskFilter是對Alpha通道過濾,這個ColorFilter是對RGB(A)進行過濾。這貨強大的不能再強大,各種秀秀都是這貨功不可沒,下面看下他的類關係:
這裡寫圖片描述
下面就開始裝逼唄(小插曲:起初自己接觸Android學到這玩意時那個鬱悶啊,真是日了狗了,當初大學《數字影象處理理論》課程老師教的Low,自己學的更Low,但是後來還是硬著頭皮打完了自己約的炮。),一個一個來看:

1、ColorMatrixColorFilter
顏色過濾,該類通過顏色矩陣(ColorMatrix)對影象中的畫素色值(不包含Alpha)進行改變,由於圖片的每個畫素是以RGBA的形式載入到記憶體中的,所以改變圖片的顏色需要Android ColorMatrix顏色矩陣類支援,而顏色矩陣是一個以一維陣列儲存在程式碼中的5x4矩陣(每一行代表RGBA中一個),如下:
這裡寫圖片描述
但是我們影象中每個畫素的展示效果卻取決於儲存在一個5x1顏色分量矩陣中,如下:
這裡寫圖片描述
所以為了修改我們影象每個畫素點的效果,我們只需修改ColorMatrix的顏色矩陣值,然後與分量矩陣做運算即可,運算結果如下:

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的引數即可得到各種變換,下面我們來看下它的構造方法:

//通過傳入ColorMatrix矩陣進行使用
public ColorMatrixColorFilter(ColorMatrix matrix)

//通過傳入一個自定義的矩陣,上面講到了,這個矩陣實質是一維陣列
public ColorMatrixColorFilter(float[] array)

現在給出一個使用它的例子,如下:

public void setImageArgb(float alpha, float red, float green, float blue) {    
    mColorMatrix = new ColorMatrix(new float[] {  
            red, 0, 0, 0, 0,  
            0, green, 0, 0, 0,  
            0, 0, blue, 0, 0,  
            0, 0, 0, alpha, 0,  
    });  
    mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));  
    postInvalidate();  
}  

通過上面方法我們傳入不同的引數就能得到各種過濾處理的圖片,譬如老照片、QQ線上離線頭像圖示顏色變換啥玩意的。

2、LightingColorFilter
曝光顏色過濾,多說無用,來看它的構造方法:

/**
mul (colorMultiply)色彩倍增,16進位制的色彩值0xAARRGGBB。
add (colorAdd)色彩新增,16進位制的色彩值0xAARRGGBB。
*/
public LightingColorFilter(int mul, int add)

這構造方法引數這麼奇葩?來看個例子就明白了,如下:

//mul=0xFF00FFFF
//add=0x0000FF00
mPaint.setColorFilter(new LightingColorFilter(0xFF00FFFF, 0x0000FF00));

上面這段程式碼應用在一個Bitmap上之後會過濾掉圖片裡的紅色,加強圖片裡的綠色,自行感受效果,哈哈,其實質計算方式就是(mul * 原色值 + add)% 255,不過一定要注意,該過濾器是不處理Alpha的,也就是說只對圖片裡的RGB有效,A無效。

3、PorterDuffColorFilter
圖層混合顏色過濾,依舊多說無用,先看構造方法,如下:

//color 16進位制的顏色值。
//mode PorterDuff.Mode的混合模式,上面有介紹。
public PorterDuffColorFilter(int color, PorterDuff.Mode mode)

通過該類構造方法可以看出來,混合模式過濾其實就是將畫布上的畫素和我們設定的color以mode方式進行疊加產生的效果(特別注意:這些模式不僅僅應用於影象色彩混合,上面我們還將他用運在了圖形疊加裁剪混合上面,可自行上翻檢視)。
這個例子就不給了,自行鬧著玩玩就行,沒啥可說的,自己腦補對比上面換值玩玩吧。

到此關於Android Paint相關的東東就介紹完了,沒啥懸念的。

Android Canvas攻略

上面我們已經買到了一板非常牛叉的彩筆,也嘗試完了那些彩筆能畫出來的各種色彩特性,下面我們就該搞一張畫紙來畫畫了,也就是該Canvas上場了,那我們下面就來依次展示出這些Canvas的牛逼大招吧,下面我們以Canvas的方法大類來介紹他們的技巧。

Canvas基礎通用方法使用技巧

Canvas()
Canvas(Bitmap bitmap)
Canvas構造方法,一般自定義View都由上層傳入,單獨new的Canvas()構造方法通常與setBitmap()配合使用,Canvas(Bitmap bitmap)有參構造方法的引數Bitmap必須是mutable可變化的!

setBitmap(Bitmap bitmap)
設定可變化的點陣圖,與上面Canvas(Bitmap bitmap)比較類似。通常用在獲取一張Bitmap後通過Canvas處理一下。

getDensity()
setDensity(int density)
獲取與設定畫布密度,預設為Bitmap的密度或者DENSITY_NONE。

getHeight()
getWidth()
獲取Canvas的高與寬。

isHardwareAccelerated()
判斷當前Canvas是否開啟了硬體加速。可以在View或者Activity中開啟關閉。

isOpaque()
判斷是否支援透明度。

getDrawFilter()
setDrawFilter(DrawFilter filter)
獲取與設定DrawFilter。DrawFilter為何物呢,如下:

//PaintFlagsDrawFilter介紹(DrawFilter預設啥都沒有,其預設有一個子類就是PaintFlagsDrawFilter):

//唯一的方法
//clearBits 清除Paint已經存在的指定flag
//setBits 設定Paint的flag
public PaintFlagsDrawFilter(int clearBits, int setBits)

例子如下:

//抗鋸齒寫法一
paint.setAntiAlias(true);
//抗鋸齒寫法二,按位操作就行
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));

上面這些方法都很基礎,不管拿Canvas繪製任何東西都可能用到,而且用法相同。

Canvas繪製相關方法使用技巧

這裡我們主要來看Canvas繪製相關的方法怎麼用,簡單和沒有坑的方法就不給出例子了,複雜或者有坑的會給出詳細例子,具體如下:

drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
drawLines(float[] pts, Paint paint)
drawLines(float[] pts, int offset, int count, Paint paint)
繪製線,線的拐角處及頂點樣式或者顏色及寬度都可有上面的Paint控制,該方法只是指定你多彩的線該畫在哪。構造方法解釋如下:

/**
startX  開始點X座標。
startY  開始點Y座標。
stopX   結束點X座標。
stopY   結束點Y座標。
將兩點連線成一條線。
*/
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
/**
pts 點集合。
offset  繪製開始跳過pts集合前面多少個數值。
count   繪製從offset開始的count個數值點組成的座標,count必須大於2。
預設將每兩個點形成一條直線,譬如pts={0,0,100,100,200,200,400,400}就是兩段沒連在一起的直線段。
*/
drawLines(float[] pts, Paint paint)
drawLines(float[] pts, int offset, int count, Paint paint)

drawPoint(float x, float y, Paint paint)
drawPoints(float[] pts, int offset, int count, Paint paint)
drawPoints(float[] pts, Paint paint)
繪製點。點的形狀樣式或者顏色及大小都可有上面的Paint控制,該方法只是指定你的點該畫在哪。構造方法解釋如下:

/**
x   點X座標。
y   點Y座標。
繪製一個點。
*/
drawPoint(float x, float y, Paint paint)
/**
pts 點集合。
offset  繪製開始跳過pts集合前面多少個數值。
count   繪製從offset開始的count個數值點組成的座標,count必須大於2。
*/
drawPoints(float[] pts, int offset, int count, Paint paint)
drawPoints(float[] pts, Paint paint)

drawRect(float left, float top, float right, float bottom, Paint paint)
drawRect(RectF rect, Paint paint)
drawRect(Rect r, Paint paint)
繪製矩形。矩形的填充樣式或者顏色及線寬都可有上面的Paint控制,該方法只是指定你多大的矩形該畫在哪。構造方法解釋如下:

/**
傳入矩形的四個點(其實就是矩形的左上角和右下角座標)繪製一個指定樣式的矩形
*/
drawRect(float left, float top, float right, float bottom, Paint paint)
/**
通過一個RectF或者Rect的物件確定矩形的四個點然後繪製一個矩形
*/
drawRect(RectF rect, Paint paint)
drawRect(Rect r, Paint paint)

RectF與Rect是矩形的輔助類,區別不大,其根據四個點構建一個矩形結構,只是兩個精度不同而已。

drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
drawRoundRect(RectF rect, float rx, float ry, Paint paint)
繪製圓角矩形。圓角矩形的填充樣式或者顏色及線寬都可有上面的Paint控制,該方法只是指定你多大的圓角矩形該畫在哪。構造方法解釋如下:

/**
rx  生成圓角的橢圓的X軸半徑
ry  生成圓角的橢圓的Y軸半徑
其他引數與繪製矩形類似。
*/
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
drawRoundRect(RectF rect, float rx, float ry, Paint paint)

drawCircle(float cx, float cy, float radius, Paint paint)
繪製圓形。圓形的填充樣式或者顏色及線寬都可有上面的Paint控制,該方法只是指定你多大的圓和該畫在哪。構造方法解釋如下:

/**
cx  圓心點X軸座標
cy  圓心點y軸座標
radius  圓半徑
*/
drawCircle(float cx, float cy, float radius, Paint paint)

drawOval(float left, float top, float right, float bottom, Paint paint)
drawOval(RectF oval, Paint paint)
繪製橢圓。類比同上,這個都一樣沒啥技術含量。

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
繪製圓弧。弧形的填充樣式或者顏色及線寬都可有上面的Paint控制,構造方法解釋如下:

/**
startAngle  弧開始角度,X軸正方向為0度。
sweepAngle  弧經歷角度。
useCenter   是否有弧的兩邊,True有兩邊,False只有一條弧。
其他引數不做介紹。
*/
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

drawPath(Path path, Paint paint)
繪製路徑。路徑的樣式或者顏色及線寬都可有上面的Paint控制(譬如平滑還是折線等)。該方法涉及一個Path物件,該物件的使用後面會有詳細介紹,該方法的使用Paint中已經有簡單的例項了。

drawText(String text, float x, float y, Paint paint)
drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
drawText(char[] text, int index, int count, float x, float y, Paint paint)
drawText(String text, int start, int end, float x, float y, Paint paint)
繪製水平方向文字。文字的填充樣式或者顏色及線寬和文字大小等都可有上面的Paint控制,構造方法解釋如下:

//從指定點開始橫向繪製一段預設文字(或者是指定字串中的一段文字)。
drawText(String text, float x, float y, Paint paint)
drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
drawText(char[] text, int index, int count, float x, float y, Paint paint)
drawText(String text, int start, int end, float x, float y, Paint paint)

drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)
沿路徑繪製文字。文字的填充樣式或者顏色及線寬和文字大小等都可有上面的Paint控制,構造方法特殊引數解釋如下:

//hOffset   代表與路徑起始點的水平偏移距離,也就是向上或者向下偏移。
//vOffset   代表與路徑中心的垂直偏移量,譬如在圓內偏移到圓外輪廓繪製文字等。

drawPosText(char[] text, int index, int count, float[] pos, Paint paint)
drawPosText(String text, float[] pos, Paint paint)
依據每個座標一一繪製每個文字。文字的填充樣式或者顏色及線寬和文字大小等都可有上面的Paint控制,構造方法特殊引數解釋如下:

//pos   每個字型的位置,每兩個數字一組確定一個文字的座標點。

drawRGB(int r, int g, int b)
drawARGB(int a, int r, int g, int b)
drawColor(int color)
drawColor(int color, PorterDuff.Mode mode)
繪製顏色,單純的對畫布進行顏色處理,預設PorterDuff.Mode為SRC_OVER模式,不過可以通過drawColor(int color, PorterDuff.Mode mode)進行指定操作。

drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
繪製各種點陣圖,圖片的視覺色彩形狀等處理需要Paint引數的配合,具體前面已經介紹了,這裡重點看下drawBitmap的一些特殊引數:

/**
指定圖片左上角的座標開始繪製一幅圖片。
*/
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
/**
src 指定bitmap裁截區域,null則不裁剪bitmap。
dst 裁剪後的bitmap在canvas中顯示的區域大小,src通過放大縮小適應dst區域。
對圖片裁剪後放大縮小顯示在指定的區域中。
*/
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
/**
對一個bitmap進行矩陣變換繪製。
*/
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)

上面介紹Paint的ColorFilter方法時介紹了ColorMatrix顏色矩陣,我們可以通過它進行圖片的色度處理,而這裡的Matrix就是用來處理影象形狀變換的,簡單介紹Matrix如下:

//Matrix類常用方法介紹:

//Matrix平移,座標即位置。
setTranslate(float dx, float dy)
//Matrix旋轉,degrees為旋轉角度,px、py為旋轉軸心位置。
setRotate(float degrees, float px, float py)
//Matrix縮放,sx、sy為X和Y軸上的縮放比例,px、py為縮放的軸心位置。
setScale(float sx, float sy, float px, float py)
//Matrix傾斜,kx、ky為X和Y軸上的縮放比例。
setSkew(float kx, float ky)

drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
繪製扭曲點陣圖,圖片的視覺色彩形狀等處理需要Paint引數的配合,具體前面已經介紹了。這個方法還是很牛逼的,譬如拉窗簾、Mac關閉網頁吸入效果等都可以靠他來搞,其原理就是按照網格來重新拉伸我們的影象。這裡重點看下_drawBitmapMesh的一些特殊引數:

/**
bitmap  原點陣圖。
meshWidth   橫向上把原點陣圖劃分為多少格。
meshHeight  縱向上把原點陣圖劃分為多少格。
verts   長度為 (meshWidth+1) * (meshHeight+1) * 2 + vertOffset的陣列,記錄了扭曲後點陣圖各頂點(網格線交點)位置,每兩個數值表示一個座標。
vertOffset  控制verts陣列從第幾個陣列元素開始對bitmap進行扭曲。
colors  作用於上面的顏色陣列,一般為null,長度為(meshWidth+1) * (meshHeight+1) + colorOffset。
colorOffset 控制colors陣列從第幾個數值開始。
*/
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)

這個效果網上一堆,也不怎麼常用,我以前也只研究了APIDemo中的例子,不再介紹,基礎例子參見APIDemo。

drawVertices(Canvas.VertexMode mode, int vertexCount, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, Paint paint)
繪製扭曲點陣圖。它是drawBitmapMesh()方法的通用格