Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android應用自定義View繪制方法手冊

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

編輯:關於android開發

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在這一篇搜索關鍵字就行了)。

這裡寫圖片描述

【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】

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

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

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()方法的通用格式,也稱為更加通用版,其依賴於Canvas.VertexMode的設置,其他和drawBitmapMesh()類似。這個效果網上一堆,也不怎麼常用,我以前也只研究了APIDemo中的例子,不再介紹,基礎例子參見APIDemo。

drawPicture(Picture picture)
drawPicture(Picture picture, RectF dst)
drawPicture(Picture picture, Rect dst)
記錄切高效繪制canvas的輔助工具方法。其中Picture在android.graphics.Picture包中,相對於Drawable和Bitmap而言小巧很多,因為它存儲的不是實際像素,而僅僅記錄了每個繪制的過程。關於Picture具體如下(特別注意:硬件加速開啟後失效):

//開始記錄繪制過程
Canvas beginRecording(int width, int height)
//靜態方法,從輸入流創建一個Pictrue對象
static Picture createFromStream(InputStream stream)
//在canvas上畫這個picture對象
void draw(Canvas canvas)
//結束錄制繪制過程
void endRecording()
//獲取寬高
int getHeight() 
int getWidth()
//將繪制結果寫到輸出流中
void writeToStream(OutputStream stream)
......

PictureDrawable在android.graphics.drawable.PictureDrawable中,它是從Drawable類繼承而來的,具體如下(特別注意:硬件加速開啟後失效):

//構造方法從Picture對象中實例化
PictureDrawable(Picture picture)
//繪制到Canvas
void draw(Canvas canvas)
//獲取透明度級別
int getOpacity()
//從PictureDrawable轉為Picture
Picture getPicture()
//設置透明級別
void setAlpha(int alpha)
......

介紹完了drawPicture()方法中的Picture與PictureDrawable,下面給一個簡單例子代碼段,如下:

//一個高效繪制自定義View的例子模板:
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(300, 500);
//canvas.drawBitmap();
//等等一堆畫線畫字畫圖操作
//這些操作都是基於Picture返回的canvas的,切記!!!!
picture.endRecording();

PictureDrawable pictureDrawable = new PictureDrawable(picture);
//viewCanvas是自定義View中onDraw方法的形參,切記!!!
pictureDrawable.draw(viewCanvas) ;  

可以看見,Picture可以記錄我們Canvas上每個繪制操作,最後統一回放每個繪圖操作。這些功能Bitmap也能實現,但是Picture只是記錄了我們繪圖得操作而不是繪制後的像素結果而不是Bitmap等渲染結果,所以存儲大小比Bitmap要小得多,同時渲染速度也比Bitmap要快。

getMaximumBitmapHeight()
getMaximumBitmapWidth()
獲取Bitmap的最大寬高。有了這組方法的限定就可以避免出現如下錯誤:

Bitmap too large to be uploaded into a texture (4405x9705, max=4096x4096).

上面這個錯誤是因為開啟硬件加速時GPU對openglRender有一個限制,不同手機限制不同,而這個限制阈值就是通過這個兩個方法來獲取的。

可以看見,上面這些方法都是Canvas繪制相關的方法,沒有啥懸念的,各種特性及注意事項都已經介紹了,用熟練就行了。

Canvas切割區域相關方法使用技巧

這裡我們主要來看Canvas區域相關的方法怎麼用(特別注意:這裡所有區域切割相關的方法被調運後除過調用save、restore處理可以恢復原樣Canvas以外該操作是不可逆的!),這些方法非常非常重要,因為合理的使用這些方法可以避免Android的Overdraw性能問題,具體不明白的看完這一小節的使用方法後建議再看下我的《Android應用開發性能優化完全分析》一文相關內容就徹底明白了;簡單和沒有坑的方法就不給出例子了,復雜或者有坑的會給出詳細例子,具體如下:

clipPath(Path path)
clipPath(Path path, Region.Op op)
依據路徑和區域方式切割畫布。抗鋸齒等屬性操作需要Paint配合(特別注意:該方法開啟硬件加速無效),Path路徑的構成下面會詳細介紹,這裡我們重點關注一下Region.Op,在介紹它之前我們先看下Region類的介紹,具體如下:

/** 
Region區域構造方法詳解:
*/
//構造方法,創建一個空區域。
public Region()
//構造方法,復制一個region區域。
public Region(Region region)
//構造方法,創建一個指定范圍的矩形區域。  
public Region(Rect r)
//構造方法,創建一個指定頂點的矩形區域。
public Region(int left, int top, int right, int bottom)

/** 
Region區域set方法詳解:
注意:調運set系列方法後原來Region的區域范圍會被該set沖掉。
*/
//設空Region區域,類似reset操作。
public void setEmpty()
//用新區域來填充原來的區域。
public boolean set(Region region)
//利用矩形區域填充區域。
public boolean set(Rect r)
//利用矩形區域填充區域。
public boolean set(int left, int top, int right, int bottom)
//根據路徑的區域與某區域的交集構造出新的不規則區域。
public boolean setPath(Path path, Region clip)

/**
Region區域判斷方法詳解:
*/
//判斷該區域是否為空
public native boolean isEmpty()
//判斷區域是否是一個矩陣  
public native boolean isRect()
//判斷區域是否是多個矩陣組合
public native boolean isComplex() 

/**
Region區域邊界獲取方法詳解:
*/  
//各種方式的邊界獲取
public Rect getBounds()   
public boolean getBounds(Rect r)   
public Path getBoundaryPath()   
public boolean getBoundaryPath(Path path)   

/**
Region區域是否包含某點和是否相交判斷方法詳解:
*/
//是否包含某點
public native boolean contains(int x, int y)
//是否包含某矩陣  
public boolean quickContains(Rect r) 
//是否沒有包含某矩陣 
public native boolean quickContains(int left, int top, int right, int bottom)  
//是否沒和該矩陣相交
public boolean quickReject(Rect r)  
public native boolean quickReject(int left, int top, int right, int bottom)  
public native boolean quickReject(Region rgn)

/**
Region區域平移變換方法詳解:
*/  
public void translate(int dx, int dy)   
public native void translate(int dx, int dy, Region dst)

/**
Region區域組合方法詳解:
*/
//各種組合方法,都與Op有關,該Op下面有介紹 
public final boolean union(Rect r)   
public boolean op(Rect r, Op op) {  
public boolean op(int left, int top, int right, int bottom, Op op)   
public boolean op(Region region, Op op)   
public boolean op(Rect rect, Region region, Op op)

可以看見,Region區域類的方法都很容易理解與使用,只是最後有一個Region.Op的參數還沒解釋,該參數含義如下:

//Region.Op枚舉介紹(我們假設用region1去和region2組合):

//最終組合區域為region1與region2不同的區域
Region.Op.DIFFERENCE
//最終組合區域為region1與region2相交的區域    
Region.Op.INTERSECT
//最終組合區域為region1與region2組合在一起的所有區域
Region.Op.UNION  
//最終組合區域為region1與region2相交以外的區域
Region.Op.XOR  
//最終組合區域為region2與region1不同的區域  
Region.Op.REVERSE_DIFFERENCE
//最終組合區域為region2的區域  
Region.Op.REPLACE 

驗證實例就不介紹了,這就是標准的APIDemo,官方原來的ProcessBar我猜就是這麼干的,還沒來得及看源碼。不過要注意:被該操作的canvas可以通過save和restore復原但不影響已經的效果。

clipRect(Rect rect, Region.Op op)
clipRect(RectF rect, Region.Op op)
clipRect(int left, int top, int right, int bottom)
clipRect(float left, float top, float right, float bottom)
clipRect(RectF rect)
clipRect(float left, float top, float right, float bottom, Region.Op op)
clipRect(Rect rect)
以矩形方式切割畫布。抗鋸齒等屬性操作需要Paint配合,其他的和上面類似,已經介紹了,沒必要再多說,只是同樣注意被該操作的canvas可以通過save和restore復原但不影響已經的效果就行了。

clipRegion(Region region)
clipRegion(Region region, Region.Op op)
以Region的區域方式切割畫布。抗鋸齒等屬性操作需要Paint配合,其他的和上面類似,已經介紹了,沒必要再多說,只是同樣注意被該操作的canvas可以通過save和restore復原但不影響已經的效果就行了。

getClipBounds()
getClipBounds(Rect bounds)
獲取邊界寬高等數據,第一種默認是整個Canvas的范圍,第二個是Rect指定范圍的數據,這個沒啥介紹的。

quickReject(float left, float top, float right, float bottom, Canvas.EdgeType type)
quickReject(Path path, Canvas.EdgeType type)
quickReject(RectF rect, Canvas.EdgeType type)
判斷是否沒和某個指定區域相交,用來作為重復優化判斷。這個其實在Region中也有類似方法,只是這裡涉及Canvas.EdgeType介紹,如下:

//Canvas.EdgeType值介紹:

//考慮抗鋸齒時邊緣處類型四捨五入。
Canvas.EdgeType.AA
//邊緣處類型為四捨五入最接近黑白即可。
Canvas.EdgeType.BW

這組方法在考慮過度繪制性能時比較常用。

上面這些方法在繪制疊加等處理和繪制性能等處理時十分有用,大家知道就行了。

Canvas變換方法使用技巧

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

translate(float dx, float dy)
平移畫布,默認不可逆。參數解釋如下:

//dx    水平平移距離,正數向右平移,負數向左平移。
//dy    垂直平移距離,正數向下平移,負數向上平移。

Canvas上平移超出屏幕范圍的地方不會顯示,上面也說了Canvas調運繪制相關方法其實都相當於圖層的疊加,所以平移前後如果進行兩次同樣位置坐標的繪制其實得到的是兩個圖,因為兩側參考點坐標被平移了,而兩次繪制卻疊加處理了。

scale(float sx, float sy)
scale(float sx, float sy, float px, float py)
縮放畫布,默認不可逆。參數解釋如下:

//sx    水平伸縮比例。
//sy    垂直伸縮比例。
//px    水平伸縮參考點,注意:沒有該參數的默認為Canvas的0點。
//py    垂直伸縮參考點,注意:沒有該參數的默認為Canvas的0點。

這和上面平移類似,不多說。

rotate(float degrees)
rotate(float degrees, float px, float py)
旋轉畫布,默認不可逆。參數解釋如下:

//degrees   旋轉度數,正數順時針旋轉,負數逆時針旋轉。
//px    旋轉中心x坐標,特別注意:沒有該參數的默認為Canvas的0點。
//py    旋轉中心y坐標,特別注意:沒有該參數的默認為Canvas的0點。

skew(float sx, float sy)
斜拉畫布,默認不可逆。參數解釋如下:

//sx    x方向傾斜的角度,sx為傾斜角度的tan值。
//sy    y方向傾斜的角度,sy為傾斜角度的tan值。

concat(Matrix matrix)
getMatrix(Matrix ctm)
getMatrix()
setMatrix(Matrix matrix)

可以看見,這些方法也都很基礎,沒啥特別介紹的。

Canvas圖層與狀態方法使用技巧

這玩意算是Canvas的重點,使用率非常高,地圖層級等玩意都是這麼來的,具體介紹使用前還是先看一張圖片,如下:
這裡寫圖片描述
怎麼樣?夠形象了吧,相信結合小標題你已經領悟該圖和我們Canvas的各種繪制方法的關系了吧(其實這個層級概念上面Canvas變換小節已經提到過了)。

save()
save(int saveFlags)
getSaveCount()
保存Canvas到棧和獲取棧中存在多少次保存(前面不可逆操作前都應該做此操作),特別注意他們返回值的含義,可能和你想的有差距。返回值可以傳入到restoreToCount()方法,以返回到某個save狀態之前。特別注意有參數的save方法的參數,它用來表示當restore的時候哪些參數需要還原,參數定義在Canvas中。
譬如先通過save方法保存目前Canvas的位置,然後旋轉移動後畫一些圖形,畫完後調用restore方法返回到剛才保存的位置。具體參見上圖,很直觀了。

restore()
restoreToCount(int saveCount)
恢復Canvas到之前狀態(恢復到前面不可逆操作前)。具體參見上圖,很直觀了。

saveLayer(RectF bounds, Paint paint, int saveFlags)
saveLayer(RectF bounds, Paint paint)
saveLayer(float left, float top, float right, float bottom, Paint paint)
saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags)
saveLayerAlpha(RectF bounds, int alpha, int saveFlags)
saveLayerAlpha(RectF bounds, int alpha)
saveLayerAlpha(float left, float top, float right, float bottom, int alpha, int saveFlags)
saveLayerAlpha(float left, float top, float right, float bottom, int alpha)
和save()類似,不過這組方法具備離屏緩沖能力,也就是說通過它save後相當於新起一個offscreen bitmap,當我們在該offscreen bitmap上繪制一堆後調運restore方法時才會將我們在offscreen bitmap上繪制的東西畫回Canvas上。這組方法使用要特別小心,一般場景下使用它的性能代價是比較高的,因為從Android3.0開始我們可以通View的setLayerType()方法來控制layer了,該方法模式如下:

//LAYER_TYPE_NONE   View以一般方式繪制,不使用離屏緩沖,默認方式。
//LAYER_TYPE_HARDWARE   View如果開啟了硬件加速則會被繪制到一個硬件紋理中,否則類同LAYER_TYPE_SOFTWARE模式。
//LAYER_TYPE_SOFTWARE   View被繪制到一個Bitmap中。

所以我們在直接或者間接使用saveLayerXXX()相關方法時務必注意給View選擇合適的LayerType,否則會造成性能問題,因為開啟硬件加速模式下Canvas的drawXXX方法在Layer(offscreen bitmap)上的繪制操作效果是在代碼執行View的invalidate()方法時生效的,所以當我們做一些動畫或者漸變操作時如果開啟硬件加速使用GPU來操作是非常高效的操作,可以避免不必要的繪制次數。

所以這裡給出和我之前Android性能優化詳解博文一樣的建議:
當我們自定義View或者繪制通過已存在View進行動畫或者漸變等特效操作時建議通過View的setLayerType()方法設置為View.LAYER_TYPE_HARDWARE,操作完成後再設置為View.LAYER_TYPE_NONE,因為這樣可以提升繪制效率,不會再次進行無用繪制。特別像多次改變View的alpha、x、y、translationX、translationY、scaleX、scaleY、pivotX、pivotY、rotation、rotationX、rotationY等屬性值時其實就是間接操作layer,所以你會覺得動效卡頓。

到此關於Canvas的東西全部就介紹OK了,還是老話,實戰熟練大於一切,其實這些方法都不難,組合起來確實十分的牛逼強大。

Android Path相關攻略

提到Path相關攻略其實更加簡單,它只能算是一種輔助工具類吧,關於PathEffect的效果前面已經有介紹了,這裡我們只是單純的針對Path來進行總結(簡單的一筆帶過,有坑和難以理解的會給出說明),如下:

public Path()
public Path(Path src)
set(Path src)
構造或者設置一個Path對象。

moveTo(float x, float y)
路徑繪制的起點,從點(x, y)開始進行繪制。

lineTo(float x, float y)
直線的結束點,又是下一次繪制直線路徑的開始點,lineTo()方法可以一直用。

rMoveTo(float dx, float dy)
在繪制線終點的基礎上移動(dx, dy)得到新的起始點。

rLineTo(float dx, float dy)
與lineTo功能類似,但這個方法是在上個線的終點基礎上進行繪制,不是(0, 0)點開始。

close()
將連續幾條直線首尾點連接形成閉環。

addArc(RectF oval, float startAngle, float sweepAngle)
addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)
addCircle(float x, float y, float radius, Path.Direction dir)
addOval(float left, float top, float right, float bottom, Path.Direction dir)
addOval(RectF oval, Path.Direction dir)
addRect(float left, float top, float right, float bottom, Path.Direction dir)
addRect(RectF rect, Path.Direction dir)
addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir)
addRoundRect(float left, float top, float right, float bottom, float[] radii, Path.Direction dir)
addRoundRect(RectF rect, float[] radii, Path.Direction dir)
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)
arcTo(RectF oval, float startAngle, float sweepAngle)
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
類似Canvas的繪制圖形方法,一堆圖形路徑的方法,大多數構造參數和前面Canvas的類似,所以這裡不多說了,唯一不同的是Path.Direction參數,該參數取值如下:

//Path.Direction.CCW    創建逆時針方向的圖形路徑。
//Path.Direction.CW     創建順時針方向的矩形路徑。

Path.Direction的設置主要用在一些動畫路徑的方向上,單純的繪制Path的Line是看不出來的。

offset(float dx, float dy, Path dst)
offset(float dx, float dy)
移動Path或者指定的Path dst間距為dx、dy。

reset()
清空Path路徑中的信息。

rewind()
清除所有直線、曲線,但是保留內部數據結構,以便更好的重新使用。

setFillType(Path.FillType ft)
getFillType()
設置與獲取Path的填充模式。填充模式FillType有四個枚舉值,具體如下,使用和Paint的類似:

//FillType.WINDING  默認值,當兩個圖形相交則正常相交情況顯示。
//FillType.EVEN_ODD 取Path所在且不相交的區域。
//FillType.INVERSE_WINDING  取Path的外部區域。
//FillType.INVERSE_EVEN_ODD 取Path外部和相交的區域。

哎呀,比較簡單,自己試試吧。

isInverseFillType()
是否逆填充模式,主要針對setFillType的FillType.INVERSE_XXX參數判斷。

toggleInverseFillType()
切換FillType逆狀態。

isEmpty()
判斷Path是否為空,如果Path不包含任何線條和曲線則返回true,否則返回false。

isRect(RectF rect)
判斷Path指定的是否一個Rect,如果Path指定的是一個Rect則返回true,否則返回false,如果返回true且rect參數不為null則將該rect參數設置為Path的區域。

computeBounds(RectF bounds, boolean exact)
計算Path所在區域並將結果寫入bounds,如果整個Path只包含0或者1個點則返回(0, 0, 0, 0)。exact參數現在沒意義了,隨意傳值。

incReserve(int extraPtCount)
提示路徑准備加入更多的點,可以讓Path對象更有效地分配存儲,差不多了就可以調用一把。

setLastPoint(float dx, float dy)
設置當前路徑的最後一個點。

addPath(Path src, float dx, float dy)
addPath(Path src)
addPath(Path src, Matrix matrix)
追加路徑,src為另一個已經存在的路徑,追加到該路徑中來。第一個方法將路徑src平移dx、dy後拼接,第三個方法將路徑src進行矩陣變換後添加。

transform(Matrix matrix, Path dst)
transform(Matrix matrix)
將Path進行matrix矩陣變換後保存到dst中,如果dst為null則將結果保存到當前Path中。

quadTo(float x1, float y1, float x2, float y2)
二階貝塞爾曲線,只有一個控制點,(x1, y1)是控制點,(x2, y2)是結束點;控制點就像下圖的P1坐標;如果沒有使用moveTo指定起點,起點默認為(0, 0),即(0, 0)到(x1, y1)之間繪制貝塞爾曲線,(x1, y1)到(x2, y2)之間繪制貝塞爾曲線。
這裡寫圖片描述
例子如下:

//平滑曲線
mPath.quadTo(100,500,300,800);
canvas.drawPath(mPath,mPaint);

rQuadTo(float dx1, float dy1, float dx2, float dy2)
和上面quadTo一樣,只是追在屁股後面,比較簡單,不多說了。

cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
三階貝塞爾曲線,有兩個控制點,(x1, y1)是第一控制點,(x2, y2)是第二控制點,(x3, y3)是結束點;控制點就像下圖的P1、P2坐標。
這裡寫圖片描述
例子如下:

//平滑曲線
mPath.cubicTo(100, 80, 200, 200, 300, 300);
canvas.drawPath(mPath,mPaint);

rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
和上面CubicTo一樣,只是追在屁股後面,比較簡單,不多說了。

可以看見,Path其實就是一個工具類,一個用來封裝路徑數值的工具類,單獨的Path沒啥技術含量的,用熟就行。

Android PathMeasure相關攻略

Paint也吹了、Canvas也玩了、Path也搞了,是不是這時候你已經覺得自己漸漸揭開了Android控件繪制的一些面紗呢?哈哈,其實是遠遠不夠的,不過掌握上面這些對於百分之七十的場合是夠用的了,尤其是Path可以做出很炫的動畫。為了對上面Path再來一個升級版,我們有必要再羅嗦幾句說說PathMeasure這個牛叉的輔助類,因為不管多復雜的Path對象在PathMeasure看來就是一條線,所以它能很方便的在Path上取位置計算對應坐標。具體如下:

PathMeasure()
PathMeasure(Path path, boolean forceClosed)
構造方法;一個構造空的PathMeasure對象,一個通過一個已存在的path構造PathMeasure對象,forceClosed決定是否close()這個path。

setPath(Path path, boolean forceClosed)
配合PathMeasure()使用效果與PathMeasure(Path path, boolean forceClosed)一樣。

getLength()
測量Path的長度。

isClosed()
判斷輪廓路徑是否是閉環的。

nextContour()
移動到下一個輪廓,如果你的Path是由多個輪廓組成的話,那麼就可以使用這個方法,有下一個輪廓存在就返回true。

getMatrix(float distance, Matrix matrix, int flags)

getPosTan(float distance, float[] pos, float[] tan)
傳入一個距離distance(0<=distance<=getLength()),然後該方法會自動計算當前距離的坐標點和切線值存入pos與tan。

getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
傳入一個開始和結束距離(0..getLength()),然後會返回介於這之間的Path值dst,最後那個參數決定開始距離是否從MoveTo坐標為參照物。

好了,PathMeasure略顯抽象,這裡給出一段核心例子代碼吧,如下:

public void goPathAnim(long duration) {  
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());  
    valueAnimator.setDuration(duration);  
    valueAnimator.addUpdateListener(new AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            float value = (Float) animation.getAnimatedValue();  
            // 獲取當前Path上指定距離的坐標點到mCurrentPosition後供onDraw()方法使用  
            mPathMeasure.getPosTan(value, mCurrentPosition, null);  
            postInvalidate();  
        }  
    });  
    valueAnimator.start();  
}  

通過上面核心代碼就能實現一個沿著路徑以動畫方式移動的動效。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved