Android的Paint、Canvas和Matrix講解
Paint類介紹
Paint即畫筆,在繪圖過程中起到了極其重要的作用,畫筆主要儲存了顏色, 樣式等繪製資訊,指定了如何繪製文字和圖形,畫筆物件有很多設定方法,大體上可以分為兩類,一類與圖形繪製相關,一類與文字繪製相關。
1.圖形繪製
* setARGB(int a,int r,int g,int b); 設定繪製的顏色,a代表透明度,r,g,b代表顏色值。 * setAlpha(int a); 設定繪製圖形的透明度。 * setColor(int color); 設定繪製的顏色,使用顏色值來表示,該顏色值包括透明度和RGB顏色。 * setAntiAlias(boolean aa); 設定是否使用抗鋸齒功能,會消耗較大資源,繪製圖形速度會變慢。 * setDither(boolean dither); 設定是否使用影象抖動處理,會使繪製出來的圖片顏色更加平滑和飽滿,影象更加清晰 * setFilterBitmap(boolean filter); 如果該項設定為true,則影象在動畫進行中會濾掉對Bitmap影象的優化操作,加快顯示速度,本設定項依賴於dither和xfermode的設定 * setMaskFilter(MaskFilter maskfilter); 設定MaskFilter,可以用不同的MaskFilter實現濾鏡的效果,如濾化,立體等 * setColorFilter(ColorFilter colorfilter); 設定顏色過濾器,可以在繪製顏色時實現不用顏色的變換效果 * setPathEffect(PathEffect effect); 設定繪製路徑的效果,如點畫線等 * setShader(Shader shader); 設定影象效果,使用Shader可以繪製出各種漸變效果 * setShadowLayer(float radius ,float dx,float dy,int color); 在圖形下面設定陰影層,產生陰影效果,radius為陰影的角度,dx和dy為陰影在x軸和y軸上的距離,color為陰影的顏色 * setStyle(Paint.Style style); 設定畫筆的樣式,為FILL,FILL_AND_STROKE,或STROKE * setStrokeCap(Paint.Cap cap); 當畫筆樣式為STROKE或FILL_AND_STROKE時,設定筆刷的圖形樣式,如圓形樣式 Cap.ROUND,或方形樣式Cap.SQUARE * setSrokeJoin(Paint.Join join); 設定繪製時各圖形的結合方式,如平滑效果等 * setStrokeWidth(float width); 當畫筆樣式為STROKE或FILL_AND_STROKE時,設定筆刷的粗細度 * setXfermode(Xfermode xfermode); 設定圖形重疊時的處理方式,如合併,取交集或並集,經常用來製作橡皮的擦除效果
2.文字繪製
* setFakeBoldText(boolean fakeBoldText); 模擬實現粗體文字,設定在小字型上效果會非常差 * setSubpixelText(boolean subpixelText); 設定該項為true,將有助於文字在LCD螢幕上的顯示效果 * setTextAlign(Paint.Align align); 設定繪製文字的對齊方向 * setTextScaleX(float scaleX); 設定繪製文字x軸的縮放比例,可以實現文字的拉伸的效果 * setTextSize(float textSize); 設定繪製文字的字號大小 * setTextSkewX(float skewX); 設定斜體文字,skewX為傾斜弧度 * setTypeface(Typeface typeface); 設定Typeface物件,即字型風格,包括粗體,斜體以及襯線體,非襯線體等 * setUnderlineText(boolean underlineText); 設定帶有下劃線的文字效果 * setStrikeThruText(boolean strikeThruText); 設定帶有刪除線的效果
Canvas類介紹
當我們調整好畫筆之後,現在需要繪製到畫布上,這就得用Canvas類了。在android中既然把Canvas當做畫布,那麼就可以在畫布上繪製我們想要的任何東西。除了在畫布上繪製之外,還需要設定一些關於畫布的屬性,比如,畫布的顏色、尺寸等。下面來分析Android中Canvas有哪些功能,Canvas提供瞭如下一些方法:
1. 設定屬性
* Canvas(Bitmap bitmap): 以bitmap物件建立一個畫布,則將內容都繪製在bitmap上,因此bitmap不得為null。 * Canvas(GL gl): 在繪製3D效果時使用,與OpenGL相關。 * isOpaque(boolean isOpaque):檢測是否支援透明。 * setViewport(int left, int top, int right, int bottom, int clipflag): 設定畫布中顯示視窗。 * drawColor(int color): 設定Canvas的背景顏色。 * setBitmap(Bitmap mBitmap): 設定具體畫布,畫的內容,儲存為一個Bitmap。 * clipRect(float left, float top, float right, float bottom): 設定顯示區域,即設定裁剪區。 * translate(float x, float y): 平移畫布。 * rotate(float degree, float px, float py): 旋轉畫布 。 * skew(float sx, float sy): 設定偏移量。 * save(): 將Canvas當前狀態儲存在堆疊,save之後可以呼叫Canvas的平移、旋轉、錯切、剪裁等操作。 * restore(): 恢復為之前堆疊儲存的Canvas狀態,防止save後對Canvas執行的操作對後續的繪製有影響。restore和save要配對使用,restore可以比save少,但不能比save多,否則會引發error。save和restore之間,往往夾雜的是對Canvas的特殊操作。 * save(int num):將Canvas當前狀態儲存在堆疊,並予以編號int * restoreToCount(int num):恢復為之前堆疊儲存的編號為int的Canvas狀態 * concat(Matrix matrix):畫布關聯矩陣,畫出來的內容按矩陣改變,而不是畫布改變。 * Drawable.draw(Canvas canvas):將Drawable畫到Canvas中 注:這種方式畫Drawable怎麼設定透明度呢? ((BitmapDrawable)Drawable).getPaint().setAlpha(mBgAlpha);
2. 畫圖
* canvas.drawPaint(Paint paint)
將畫筆設定的顏色和透明度鋪滿畫布
* drawRect(RectF rect, Paint paint)
繪製矩形,引數一為RectF一個區域
* drawRect(float left, float top, float right, float bottom, Paint paint)
繪製矩形,left:矩形left的x座標,top:矩形top的y座標,right:矩形right的x座標,bottom:矩形bottom的y座標
* drawRoundRect(RectF rect, float rx, float ry, Paint paint)
繪製圓角矩形, rx:x方向的圓角半徑,ry:y方向的圓角半徑
* drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
* drawPath(Path path, Paint paint)
繪製一個路徑,引數一為Path路徑物件
* drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
貼圖,引數一就是我們常規的Bitmap物件,引數二是源區域(這裡是bitmap),引數三是目標區域(應該在canvas的位置和大小),引數四是Paint畫刷物件,因為用到了縮放和拉伸的可能,當原始Rect不等於目標Rect時效能將會有大幅損失。
* drawBitmap (Bitmap bitmap, float left, float top, Paint paint)
* drawLine(float startX, float startY, float stopX, float stopY, Paintpaint)
畫線,引數一起始點的x軸位置,引數二起始點的y軸位置,引數三終點的x軸水平位置,引數四y軸垂直位置,最後一個引數為Paint 畫刷物件。
* drawPoint(float x, float y, Paint paint)
畫點,引數一水平x軸,引數二垂直y軸,第三個引數為Paint物件。
* drawText(String text, float x, floaty, Paint paint)
渲染文字,Canvas類除了上面的還可以描繪文字,引數一是String型別的文字,引數二文字左側到x軸距離,引數三文字BaseLine到y軸距離,引數四是Paint物件。
* drawOval(RectF oval, Paint paint)
繪製橢圓,引數一是掃描區域,引數二為paint物件
* drawOval(float left, float top, float right, float bottom, Paint paint)
* drawCircle(float cx, float cy, float radius,Paint paint)
繪製圓,引數一是中心點的x軸,引數二是中心點的y軸,引數三是半徑,引數四是paint物件;
* drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
畫弧,引數一是RectF物件,指定圓弧的外輪廓矩形區域,引數二是起始角(度)在電弧的開始,引數三掃描角(度)開始順時針測量的,引數四是如果這是真的話,包括橢圓中心的電弧,並關閉它,如果它是假這將是一個弧線,引數五是Paint物件;
Canvas物件的獲取方式有兩種:一種我們通過重寫View.onDraw方法,View中的Canvas物件會被當做引數傳遞過來,我們操作這個Canvas,效果會直接反應在View中。
@Override
protected void onDraw(Canvas canvas) {
}
另一種就是當你想建立一個Canvas物件時使用的方法:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
上面程式碼建立了一個尺寸是100*100的Bitmap,使用它作為Canvas操作的物件,這時候的Canvas就是使用建立的方式。當你使用建立的Canvas在bitmap上執行繪製方法後,你還可以將繪製的結果提交給另外一個Canvas,這樣就可以達到兩個Canvas協作完成的效果,簡化邏輯。
從上面方法的名字看來我們可以知道Canvas可以繪製的物件有:弧線(arcs)、填充顏色(argb和color)、Bitmap、圓(circle和oval)、點(point)、線(line)、矩形(Rect)、圖片(Picture)、圓角矩形(RoundRect)、文字(text)、頂點(Vertices)、路徑(path)。下面我們就演示下canvas的一些簡單用法:
繪製圓、橢圓
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint=new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.blue));
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,200,100,paint);
canvas.drawOval(500, 100, 800, 300, paint);
//上面程式碼等同於
//RectF rel=new RectF(500,100,800,300);
//canvas.drawOval(rel, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(20);
canvas.drawCircle(200,500,90,paint);
canvas.drawOval(500,400,800,600, paint);
//上面程式碼等同於
//RectF rel2=new RectF(500,400,800,600);
//canvas.drawOval(rel2, paint);
}
繪製矩形、圓角矩形
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.red));
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(100, 100, 300, 300, paint);
canvas.drawRoundRect(400, 100, 600, 300, 30, 30, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(20);
canvas.drawRect(100, 400, 300, 600, paint);
canvas.drawRoundRect(400, 400, 600, 600, 30, 30, paint);
}
繪製弧形、封閉弧形
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.orange));
RectF rel = new RectF(100, 100, 300, 300);
//實心圓弧
canvas.drawArc(rel, 0, 270, false, paint);
//實心圓弧 將圓心包含在內
RectF rel2 = new RectF(100, 400, 300, 600);
canvas.drawArc(rel2, 0, 270, true, paint);
//設定空心Style
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(20);
RectF rel3 = new RectF(100, 700, 300, 900);
canvas.drawArc(rel3, 0, 270, false, paint);
RectF rel4 = new RectF(100, 1000, 300, 1200);
canvas.drawArc(rel4, 0, 270, true, paint);
}
繪製文字
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.orange));
paint.setTextSize(100);
canvas.drawText("jEh", 80, 150, paint);
}
繪製圖片
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.music);
canvas.drawBitmap(bitmap, 100, 100, mPaint);
//上面程式碼等同於
//Rect mSrc = new Rect(0, 0, mBitWidth, mBitHeight);
//Rect mDest = new Rect(100,100,100+mBitWidth,100+mBitHeight);
//canvas.drawBitmap(bitmap, mSrc, mDest, mPaint);
}
繪製Path
通過Path這個類,我們可以畫出三角形,梯形等多邊形。
常用方法:
moveTo();設定地點
lineTo();連線兩點
close();連線起點和終點
Path angle = new Path();
angle.moveTo(250, 0);//設定起點
angle.lineTo(0, 500);
angle.lineTo(500, 500);
angle.close();//閉合路徑
canvas.drawPath(angle, mPaint);
Canvas位置轉換
通過組合這些物件我們可以畫出一些簡單有趣的介面出來,但是光有這些功能還是不夠的,如果我要畫一個儀表盤(數字圍繞顯示在一個圓圈中)呢? 幸好Android還提供了一些對Canvas位置轉換的方法:rorate、scale、translate、skew(扭曲)等,而且它允許你通過獲得它的轉換矩陣物件(getMatrix方法) 直接操作它。這些操作就像是雖然你的筆還是原來的地方畫,但是畫紙旋轉或者移動了,所以你畫的東西的方位就產生變化。為了方便一些轉換操作,Canvas 還提供了儲存和回滾屬性的方法(save和restore),比如你可以先儲存目前畫紙的位置(save),然後旋轉90度,向下移動100畫素後畫一些圖形,畫完後呼叫restore方法返回到剛才儲存的位置。
canvas.translate() - 畫布的平移
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
canvas.translate(100, 100);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
}
canvas.scale( ) - 畫布的縮放
關於scale,Android 提供了以下兩個介面:
/**
* Preconcat the current matrix with the specified scale.
* @param sx The amount to scale in X
* @param sy The amount to scale in Y
*/
public native void scale(float sx, float sy);
/**
* Preconcat the current matrix with the specified scale.
* @param sx The amount to scale in X
* @param sy The amount to scale in Y
* @param px The x-coord for the pivot point (unchanged by the scale)
* @param py The y-coord for the pivot point (unchanged by the scale)
*/
public final void scale(float sx, float sy, float px, float py) {
translate(px, py);
scale(sx, sy);
translate(-px, -py);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
// 儲存畫布狀態
canvas.save();
canvas.scale(0.5f, 0.5f);
mPaint.setColor(Color.YELLOW);
canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
// 畫布狀態回滾
canvas.restore();
canvas.scale(0.5f, 0.5f, 400, 400);
mPaint.setColor(Color.GREEN);
canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
}
canvas.rotate( ) - 畫布的旋轉
canvas.rotate( )和canvas.scale()可以類比起來看,它也有兩個可以使用的方法:
/**
* Preconcat the current matrix with the specified rotation.
* @param degrees The amount to rotate, in degrees
*/
public native void rotate(float degrees);
/**
* Preconcat the current matrix with the specified rotation.
* @param degrees The amount to rotate, in degrees
* @param px The x-coord for the pivot point (unchanged by the rotation)
* @param py The y-coord for the pivot point (unchanged by the rotation)
*/
public final void rotate(float degrees, float px, float py) {
translate(px, py);
rotate(degrees);
translate(-px, -py);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
canvas.save();
mPaint.setColor(Color.YELLOW);
canvas.rotate(45);
canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
canvas.restore();
mPaint.setColor(Color.GREEN);
canvas.rotate(45,400,400);
canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
}
canvas.skew( ) - 畫布的錯切
public native void skew(float sx, float sy);
這個方法只要理解了兩個引數即可:
float sx:將畫布在x方向上傾斜相應的角度,sx為傾斜角度的tan值;
float sy:將畫布在y軸方向上傾斜相應的角度,sy為傾斜角度的tan值;
注意,這裡全是傾斜角度的tan值,比如我們打算在X軸方向上傾斜45度,tan45=1;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
canvas.save();
//x方向上傾斜45度
canvas.skew(1, 0);
mPaint.setColor(Color.YELLOW);
canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
canvas.restore();
//y方向上移動400再傾斜45度
canvas.translate(0, 400);
canvas.skew(0, 1);
mPaint.setColor(Color.GREEN);
canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
}
matrix的變換應用到canvas上
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
mPaint.setColor(Color.RED);
canvas.drawRect(0, 0, 100, 100, mPaint);
canvas.save();
Matrix matrix = new Matrix();
matrix.setScale(2f, 2f);
canvas.concat(matrix);
canvas.drawRect(100, 100, 200, 200, mPaint);
canvas.restore();
canvas.drawRect(400, 400, 500, 500, mPaint);
}
Matrix延伸:
我們通過animation來實現view元件的動畫效果時候,實際上是改變canvas的matrix, matrix矩陣的作用主要是對每個座標點(x,y)轉換為另外的(x’,y’),必要的時候canvas還會通過clipRect()方法改變它的繪製可見範圍,這樣不至於做移動的時候看不到view元件。我們看到view的動畫效果時,其實它的大小和佈局都沒有變化,所以會看到比較搞笑的現象,就是一個button通過translate偏離原來位置後,它的touch事件響應還是在原來位置上,而不是所看到的眼前位置。
Canvas的translate(int dx, int dy)方法,其實和通過設定它的matrix的postTranslate(int dx, int dy), preTranslate(int dx, int dy)方法效果是一樣的, 唯獨set系列的方法和pre, post的不同,它是直接設值,而後者它們是設定matrix的增量。
更進一步,
比如preTranslate, setTranslate, postTranslate這幾個方法的呼叫順序對座標變換的影響。抽象的說pre方法是向前”生長”,從佇列前面加入,post方法是向後”生長”,從佇列後面加入,然後從前到後按順序執行佇列即可。具體拿個例子來說,比如一個matrix呼叫了下列一系列的方法:
matrix.preScale(0.5f, 1); matrix.preTranslate(10, 0); matrix.postScale(0.7f, 1); matrix.postTranslate(15, 0); 則座標變換經過的4個變換過程依次是:translate(10, 0) -> scale(0.5f, 1) -> scale(0.7f, 1) -> translate(15, 0), 所以對matrix方法的呼叫順序是很重要的,不同的順序往往會產生不同的變換效果。pre方法的呼叫順序和post方法的互不影響,即以下的方法呼叫和前者在真實座標變換順序裡是一致的, matrix.postScale(0.7f, 1); matrix.preScale(0.5f, 1); matrix.preTranslate(10, 0); matrix.postTranslate(15, 0);
而matrix的set方法則會對先前的pre和post操作進行刷除,而後再設定它的值,比如下列的方法呼叫:
matrix.preScale(0.5f, 1); matrix.postTranslate(10, 0); matrix.setScale(1, 0.6f); matrix.postScale(0.7f, 1); matrix.preTranslate(15, 0); 其座標變換順序是translate(15, 0) -> scale(1, 0.6f) -> scale(0.7f, 1).
Canvas裡scale, translate, rotate, concat方法都是pre方法,如果要進行更多的變換可以先從canvas獲得matrix, 變換後再設定回canvas.