Android自定義View工具:Paint&Canvas(一)

_小馬快跑_發表於2017-12-15

安卓的graphics提供了2D圖形各種繪製工具,如Canvas(畫布), color filters(顏色過濾器), points(點), rectangles(矩形)等,利用這些工具可以直接在介面上進行繪製。 本文主要講的是自定義View時我們經常用到的Canvas和Paint,像平時畫畫一樣,我們需要畫布和畫筆,而Canvas就是畫布,Paint就是畫筆.

Canvas官網地址: https://developer.android.com/reference/android/graphics/Canvas.html Paint官網地址: https://developer.android.com/reference/android/graphics/Paint.html

###先來看Paint,Paint常用方法一覽:

Paint.setAntiAlias(boolean flag);//設定抗鋸齒效果 設定true的話邊緣會將鋸齒模糊化
Paint.setDither(boolean flag);//設定防抖動,設定true的話圖片看上去會更柔和點
Paint.setColor(int color);//設定畫筆顏色
###TODO
Paint.setARGB(int a, int r, int g, int b); //設定畫筆的ARGB值
Paint.setAlpha(int alpha);//設定畫筆的Alpha值
Paint.setStyle(); //設定畫筆的style (三種:FILL填充 FILL_AND_STROKE填充加描邊 STROKE描邊 )
Paint.setStrokeWidth(float width);//設定描邊寬度

Paint.setXfermode(Xfermode xfermode);//設定圖形重疊時的處理方式,如合併,取交集或並集,經常用來製作橡皮的擦除效果
Paint.setShader(Shader shader);//設定影像效果,使用Shader可以繪製出各種漸變效果   
Paint.setShadowLayer(float radius ,float dx,float dy,int color);//在圖形下面設定陰影層,產生陰影效果,radius為陰影的半徑,dx和dy為陰影在x軸和y軸上的距離,color為陰影的顏色  
//下面寫文字的時候經常用到的
Paint.setTextSize(float textSize);//設定畫筆文字大小
Paint.measureText(String text);//測試文字的長度
Paint.setTextAlign(Paint.Align align);// CENTER(文字居中) LEFT(文字左對齊) RIGHT(文字右對齊)
複製程式碼

先來看上面Paint的幾個主要方法,結合程式碼和效果圖:

  • ####Paint.setStyle(); //設定畫筆的style
     Paint.Style.FILL //填充 
     Paint.Style.FILL_AND_STROKE //填充加描邊 
     Paint.Style.STROKE //描邊 
    複製程式碼
測試虛擬碼:
複製程式碼

Paint mPaint= new Paint(); mPaint.setColor(Color.RED);//畫筆顏色為紅色 mPaint.setStrokeWidth(80); //描邊寬度為80(為了區分效果,特意設定特別大)

float radius = 100f; // 填充 mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(400, 500, radius, mPaint); // 描邊 mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(400, 200, radius, mPaint); // 描邊加填充 mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawCircle(400, 900, radius, mPaint);

  來看效果圖:  

![Paint.Style.png](http://upload-images.jianshu.io/upload_images/587163-1c7e24d128c6e2bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
如圖,根據測試,**設定FILL_AND_STROKE模式時在其圓的外圍的描邊寬度並不是StrokeWidth的寬度,而是StrokeWidth/2的寬度.**

* ####Paint.setShader(Shader shader)//設定影像效果
[Shader](https://developer.android.com/reference/android/graphics/Shader.html)是著色器,用來給影像著色,Shader 是基類基類,它有5個已知的子類:
[BitmapShader](https://developer.android.com/reference/android/graphics/BitmapShader.html),
 [ComposeShader](https://developer.android.com/reference/android/graphics/ComposeShader.html),
 [LinearGradient](https://developer.android.com/reference/android/graphics/LinearGradient.html), 
[RadialGradient](https://developer.android.com/reference/android/graphics/RadialGradient.html),
 [SweepGradient](https://developer.android.com/reference/android/graphics/SweepGradient.html)

在講這5個子類之前,先了解一個列舉Shader.TileMode,它裡面有三個值:{CLAMP,REPEAT,MIRROR}:
######Shader.TileMode.CLAMP:
如果shader繪製範圍大於原有的範圍時,會用原有影像四邊的顏色填充剩餘空間。
######Shader.TileMode.REPEAT:
在水平和豎直方向重複shader影像。
######Shader.TileMode.MIRROR:
在水平和豎直方向重複shader影像,這一點和REPEAT相似,不同的是MIRROR模式下相鄰的兩個影像互為映象。
接下來結合例子分別來看一下Shader的5個子類和Shader.TileMode的使用姿勢。

先來看下原圖(**用我家兩隻貓咪鎮樓!**)

![cat.png](http://upload-images.jianshu.io/upload_images/587163-efb85e77723ed2b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#####BitmapShader:
BitmapShader本質上就是繪製一個bitmap,並用這個bitmap對需要繪製的圖形進行填充。
複製程式碼

BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

BitmapShader初始化時的三個引數:

BitmapShader引數| 備註
:----:|:----:
bitmap| 用來填充圖形的Bitmap 
tileX| X軸Bitmap用Shader.TileMode模式填充
tileY| Y軸Bitmap用Shader.TileMode模式填充

示例:
複製程式碼

BitmapShader shader = new BitmapShader(bitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.MIRROR); mPaint.setShader(shader); canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

效果圖:

![catb.png](http://upload-images.jianshu.io/upload_images/587163-f26bbac2ac0642fb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
X軸用TileMode.CLAMP模式,即用bitmap的右邊緣去填充X軸其餘空間,
Y軸用TileMode.MIRROR模式,即在用相鄰兩張影像互為映象的方式填充整個Y軸其餘空間。
X軸和Y軸分別換一下引數模式:
複製程式碼

BitmapShader shader = new BitmapShader(bitmap, BitmapShader.TileMode.MIRROR, BitmapShader.TileMode.REPEAT); mPaint.setShader(shader); canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

效果圖:
![cat2.png](http://upload-images.jianshu.io/upload_images/587163-17349b18c6f3e163.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

X軸用TileMode.MIRROR模式,即用相鄰兩張影像互為映象的方式填充整個X軸其餘空間,
Y軸用TileMode.REPEAT模式,即用相同的影像重複填充整個Y軸其餘空間。
#####LinearGradient:
複製程式碼

LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,TileMode tile)

LinearGradient是沿一條直線用來建立線性漸變效果,(x0,y0),(x1,y1)分別是起始座標和終止座標,color0,color1分別是起始顏色和終止顏色,tile為
Shader.TileMode(CLAMP,REPEAT,MIRROR)模式中的一個。

LinearGradient引數| 備註
:----:|:----:
x0| 漸變線起始座標的X座標 
y0| 漸變線起始座標的Y座標 
x1| 漸變線終止座標的X座標 
y1| 漸變線終止座標的Y座標
color0| 漸變線起始顏色
color1| 漸變線終止顏色
tile| 漸變線用Shader.TileMode模式填充
示例:
複製程式碼

LinearGradient linearGradient = new LinearGradient(200, 200, 600, 600, Color.GREEN, Color.YELLOW, Shader.TileMode.MIRROR); mPaint.setShader(linearGradient); canvas.drawRect(200, 200, 600, 600, mPaint);

效果圖:
![LinearGradient.png](http://upload-images.jianshu.io/upload_images/587163-cdf2b6a2fca6e528.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
下面修改一下程式碼,擴大一下繪製範圍:
複製程式碼

canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

並且分別在LinearGradient建構函式中設定Shader.TileMode為CLAMP,REPEAT,MIRROR:
效果圖:

![threeMode.png](http://upload-images.jianshu.io/upload_images/587163-d31e20aaa68bf22d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

LinearGradient還有另外一個建構函式:
複製程式碼

LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],TileMode tile)

和第一個建構函式不同的是colors和positions,可以傳多個color及對應的position進行線性漸變。

LinearGradient引數| 備註
:----:|:----:
colors| 用colors陣列線性填充
positions|每個position取值範圍[0,1],並且和colors陣列中對應位置的color一一對應
複製程式碼

int[] colors = {Color.GREEN, Color.GRAY, Color.RED, Color.BLUE}; float[] positions = {0f, 0.5f, 0.75f, 1f}; LinearGradient linearGradient = new LinearGradient(200, 200, 600, 600, colors, positions, Shader.TileMode.CLAMP); mPaint.setShader(linearGradient); canvas.drawRect(200, 200, 600, 600, mPaint);

效果圖:
![muticolor.png](http://upload-images.jianshu.io/upload_images/587163-97a5f3893d9d3849.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
同樣修改程式碼擴大一下範圍並且修改Shader.TileMode模式:
複製程式碼

canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

效果圖:

![muticolor (2).png](http://upload-images.jianshu.io/upload_images/587163-31529483ebb8ee2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#####RadialGradient:
RadialGradient也用來建立漸變效果,和LinearGradient 不同的是,LinearGradient 是線性漸變,而RadialGradient是徑向漸變,也就是從中心向四周發散漸變,RadialGradient也有兩個建構函式,先看第一個:
複製程式碼

RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, TileMode tileMode)

(centerX,centerY)是圓心座標,radius是圓半徑,centerColor是圓中心顏色,edgeColor是圓邊緣顏色。

RadialGradient引數| 備註
:----:|:----:
centerX| 圓中心的X軸座標
centerY|圓中心的Y軸座標
radius| 圓半徑
centerColor|圓中心顏色
edgeColor| 圓邊緣顏色
tileMode|徑向漸變Shader.TileMode模式填充

示例:
複製程式碼

RadialGradient gradient = new RadialGradient(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, Color.GREEN, Color.BLACK, Shader.TileMode.CLAMP); mPaint.setShader(gradient); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);

效果圖:
![radial.png](http://upload-images.jianshu.io/upload_images/587163-ae8e6331c75f1498.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
因為RadialGradient範圍和canvas範圍是一樣大小,所以RadialGradient建構函式最後一個引數Shader.TileMode不起作用,同樣的,我們來擴大canvas範圍:
複製程式碼

canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2, mPaint);

我們將圓的半徑擴大至螢幕寬度的一半,然後看效果圖:
![radialMode.png](http://upload-images.jianshu.io/upload_images/587163-492925d9aadd2e0e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
再來看RadialGradient的另一個建構函式:
複製程式碼

RadialGradient(float centerX, float centerY, float radius,int colors[], float stops[],TileMode tileMode)

和上一個不同的是colors和stops,單獨列一下:

RadialGradient引數| 備註
:----:|:----:
colors| color陣列分佈在圓的中心和邊緣之間
stops|取值範圍在[0.0f,1.0f],並且和colors陣列中對應位置的color一一對應,如果為null,顏色均勻的分佈在中心和邊緣之間

#####SweepGradient:
SweepGradient用來建立圍繞一箇中心點360度沿順時針旋轉漸變效果:
複製程式碼

SweepGradient(float cx, float cy, int color0, int color1)

SweepGradient引數| 備註
:----:|:----:
cx| 圓中心的X軸座標
cy|圓中心的Y軸座標
color0| 開始旋轉起始顏色,起始點在3點鐘方向,順時針
color1| 結束旋轉終止顏色,終止點也在3點鐘方向
示例:
複製程式碼

SweepGradient gradient = new SweepGradient(getMeasuredWidth() / 2, getMeasuredHeight() / 2, Color.GREEN, Color.RED); mPaint.setShader(gradient); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2, mPaint);

效果圖:
![sweepCircle.png](http://upload-images.jianshu.io/upload_images/587163-ec56fa4e8dd1c308.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

修改一下canvas形狀
複製程式碼

canvas.drawRect(0, (getMeasuredHeight() - getMeasuredWidth()) / 2, getMeasuredWidth(), (getMeasuredHeight() + getMeasuredWidth()) / 2, mPaint);

效果圖:
![sweepSquare.png](http://upload-images.jianshu.io/upload_images/587163-72952753319ffa62.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

SweepGradient另一個建構函式:
複製程式碼

SweepGradient(float cx, float cy, int colors[], float positions[])

和前面不同的是colors和positions:

SweepGradient引數| 備註
:----:|:----:
colors| color陣列順時針分佈
positions|每個position取值範圍[0,1],並且和colors陣列中對應位置的color一一對應
示例:
複製程式碼

int[] colors = {Color.GREEN, Color.YELLOW, Color.BLACK, Color.BLUE, Color.RED}; float[] positions = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f}; SweepGradient gradient = new SweepGradient(getMeasuredWidth() / 2, getMeasuredHeight() / 2, colors, positions); mPaint.setShader(gradient); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2, mPaint);

效果圖:
![sweepMultiColor.png](http://upload-images.jianshu.io/upload_images/587163-58646075f04b495e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#####ComposeShader:
ComposeShader結合Xfermode模式,是兩個Shader的組合模式,ComposeShader有兩個建構函式:
複製程式碼

ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)

Xfermode可以用於實現新繪製的畫素與Canvas上對應位置已有的畫素按照混合規則進行顏色混合,Xfermode 有三個子類:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,前兩個已廢棄,PorterDuffXfermode初始化時需要傳入PorterDuff.Mode即:PorterDuffXfermode(PorterDuff.Mode mode),所以上面第一個建構函式是第二個建構函式的一種情況,我們只看第一個建構函式就可以了:

ComposeShader引數| 備註
:----:|:----:
shaderA| 目標畫素DST
shaderB|源畫素SRC
mode|新繪製的畫素與Canvas上對應位置已有的畫素按照混合規則進行顏色混合

* ####Paint.setShadowLayer(float radius ,float dx,float dy,int color);
//在圖形下面設定陰影層,產生陰影效果:

setShadowLayer引數| 備註
:----:|:----:
radius | radius為陰影半徑,半徑越大,陰影面積越大,越模糊;反之,半徑越小,陰影面積越小,也越清晰,radius=0時,陰影消失
dx|dx為陰影在x軸上的偏移值
dy|dy為陰影在y軸上的偏移值
color|color為陰影的顏色 

示例:
複製程式碼

Paint paint = new Paint(); paint.setColor(Color.RED); paint.setShadowLayer(20, 0, 0, Color.YELLOW); paint.setTextSize(200); canvas.drawText("Hello World", 200, 300, paint);

效果圖:

![B78480E8AC4E55D310BB0469B05D49E1.jpg](http://upload-images.jianshu.io/upload_images/587163-433815d1b96a5c81.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

修改一下:
複製程式碼

paint.setShadowLayer(20,50, 50, Color.YELLOW);

其他程式碼不變,效果圖:
![3978CA920B5314397C82FE8FC931C421.jpg](http://upload-images.jianshu.io/upload_images/587163-156be8d59c3e49fe.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
可以看到陰影偏移量起始座標(x,y)從(0,0)變成了(50,50),即陰影位置從(0,0)移動到了(50,50)的位置,再改一下:
複製程式碼

paint.setShadowLayer(1,50, 50, Color.YELLOW);

其他程式碼不變,效果圖:
![08352B8C321173BEB880E2E763265A00.jpg](http://upload-images.jianshu.io/upload_images/587163-5496184009d39cfa.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
陰影半徑radius從20變成1,可以看到陰影清晰了很多,再來改一下:
複製程式碼

paint.setShadowLayer(0,50, 50, Color.YELLOW);

其他程式碼不變,效果圖:
![C9B902E437521FA902C514527C1694F2.jpg](http://upload-images.jianshu.io/upload_images/587163-1eb64fe0f06ce1f6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
陰影半徑radius變成0時,陰影消失,接著看下面程式碼:
複製程式碼

Paint paint = new Paint(); paint.setColor(Color.GREEN); paint.setShadowLayer(30, 0, 0, Color.BLACK); canvas.drawCircle(400, 800, 100, paint);

效果圖:
![4675614E0EC4D773515187BD45E6D9DD.jpg](http://upload-images.jianshu.io/upload_images/587163-1397b74c5d5c2cc7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**納尼?我們預期的黑邊陰影腫麼沒有出現?What a fucking day!別急,只要給paint加一句:**
複製程式碼

setLayerType(LAYER_TYPE_SOFTWARE, paint);

然後來看下效果圖:
![72CC47BD0C72C1BD7A6F89A251EAF3BC.jpg](http://upload-images.jianshu.io/upload_images/587163-07b8a5d2a377b1fc.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
終於看到黑色陰影了,為毛要加setLayerType呢,google工程師給出解釋,連結:
http://stackoverflow.com/questions/17410195/setshadowlayer-android-api-differences

Paint先介紹到這裡,接下篇:
[Android自定義View工具:Paint&Canvas(二)](http://www.jianshu.com/p/adbe33e887be)
複製程式碼

相關文章