1. 程式人生 > >Android的Paint、Canvas和Matrix講解

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.