Canvas中的繪圖師講解與實戰——Android高級UI

目錄
1、前言
2、如何畫好一幅圖
3、Canvas的圖形API
4、畫布保存狀態API
5、實戰——時鐘與指針
6、寫在最後
java

1、前言

在上一篇文章中,咱們只是分享了裁剪類型的API,今天接着分享繪圖部分API。話很少說,老規矩,先上實戰圖。git

時鐘與指針 github

2、如何畫好一幅圖

咱們在上一篇文章中講到了,繪製一幅圖的工具和座標系。咱們繼續思考,在現實中使用一張紙繪製時,咱們會對這張紙進行旋轉必定角度來方便本身繪製,有時爲了繪製一些細節,會進行放大,有時也會進行移動這張紙。而這些操做,在canvas中也有各自對應的操做。編程

一、rotate 旋轉

(1)第一個rotate函數canvas

public void rotate(float degrees) 複製代碼

描述: 以原點爲旋轉中心,旋轉畫布 degrees 角度。正數爲順時針旋轉,負數爲逆時針旋轉。數組

舉個例子微信

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

canvas.rotate(30);

mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖svg

紅色爲原圖,藍色爲旋轉後繪製的圖。 函數

(2)第二個rotate函數工具

public final void rotate(float degrees, float px, float py) 複製代碼

描述: 以 (px, py) 爲旋轉中心,將畫布旋轉 degrees 角度。正數爲順時針旋轉,負數爲逆時針旋轉。

舉個例子

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

canvas.rotate(30, 200, 300);

mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖

紅色爲原圖,藍色爲旋轉後繪製的圖。

二、scale 縮放

(1)第一個scale函數

public void scale(float sx, float sy) 複製代碼

描述 : 以原點進行縮放畫布,x軸縮放 sx 倍,y軸縮放 sy 倍。

舉個例子

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

canvas.scale(0.5f,0.33f);

mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖

紅色爲原圖,藍色爲縮放後繪製的圖。

(2)第二個scale函數

public final void scale(float sx, float sy, float px, float py) 複製代碼

描述: 以 (px, py) 進行縮放畫布,x軸縮放 sx 倍,y軸縮放 sy 倍。

舉個例子

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

canvas.scale(0.5f, 0.33f, 200, 300);

mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖

紅色爲原圖,藍色爲縮放後繪製的圖。

三、skew 斜切

public void skew(float sx, float sy) 複製代碼

描述: 進行 x 軸和 y軸 的拉伸。

拉伸規則 當一個點爲(x, y)時,進行斜切變換(sx, sy),獲得的結果 (rx, ry)

  1. rx = x + sx * y;
  2. ry = y + sy * x;

舉個例子

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

canvas.skew(1, 0.5f);

mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖

紅色爲原圖,藍色爲斜切後繪製的圖。

可使用上面的 「拉伸規則」 ,將紅色框的點帶入即可獲得藍色框對應的點。

四、translate 偏移

public void translate(float dx, float dy) 複製代碼

描述: 將畫布水平移動 dx 個像素, 垂直移動 dy 個像素。

舉個例子

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

canvas.translate(100, 200);

mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖

紅色爲原圖,藍色爲移動後繪製的圖。

五、setMatrix 矩陣

public void setMatrix(@Nullable Matrix matrix) 複製代碼

描述: 將矩陣做用於畫布。

舉個例子

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

mMatrix.preTranslate(getWidth() / 2, getHeight() / 2);
mMatrix.preScale(2, 1);

canvas.setMatrix(mMatrix);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖

紅色爲原圖,藍色爲使用矩陣後繪製的圖。

值得一提

矩陣的內容比較多,這裏只是略帶一提,若是想見識見識他的真正威力,能夠看看在小盆友另外一篇博文放蕩不羈SVG講解與實戰實戰中的使用,具體代碼請進傳送門

3、Canvas的圖形API

一、drawCircle 畫圓

public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) 複製代碼

描述: 在座標爲 (cx,cy) 的地方繪製半徑爲 radius 的圓。

舉個例子

// 在 原點處 畫半徑爲100的圓
canvas.drawCircle(0, 0, 100, mPaint);
複製代碼

效果圖

二、drawOval 畫橢圓

(1)第一個drawOval函數

public void drawOval(@NonNull RectF oval, @NonNull Paint paint) 複製代碼

描述:oval 的矩形範圍內,繪製橢圓。

舉個例子

RectF mRectF = new RectF();
mRectF.left = -150;
mRectF.top = -150;
mRectF.right = 400;
mRectF.bottom = 150;

canvas.drawOval(mRectF, mPaint);
複製代碼

效果圖

橘色部分則爲咱們繪製的橢圓,而紫色框(爲了方便觀看而繪製出來)則是咱們的 oval 的範圍。

(2)第二個drawOval函數

public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) 複製代碼

描述:左上(left,top) 和 右下(right,bottom) 造成的矩形範圍內,繪製橢圓。

值得注意的是,這個方法只能在 API21 以上的版本 才能使用,因此建議使用第一個函數。

舉個例子

canvas.drawOval(-150, -150, 400, 150, mPaint);
複製代碼

效果圖

橘色部分則爲咱們繪製的橢圓,而紫色框(爲了方便觀看而繪製出來)則是咱們的 oval 的範圍。

兩個函數效果徹底同樣,只是前一個函數將兩個座標點封裝在 Rect 中,然後一函數展現在函數參數中。

三、drawLine 畫線

(1)drawLine函數

public void drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) 複製代碼

描述: 在座標 (startX, startY) 和 (stopX, stopY) 中繪製一條直線。

舉個例子

canvas.drawLine(-200, -200,0, 0, mPaint);
複製代碼

效果圖

(2)第一個drawLines函數

public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) 複製代碼

描述: pts數組中每四個數構成一條直線,每四個數中前兩個爲起始座標,後兩個爲終止座標。若是不夠四個數,則這一組不進行繪製。

舉個例子

private float[] pts = new float[]{
            0, -400, 200, -400, // 構成上面的線
            -300, 0, -300, 300, // 構成左邊的線
            0, 400, 300, 400    // 構成右邊的線
    };

canvas.drawLines(pts, mPaint);
複製代碼

效果圖

(3)第二個drawLines函數(帶偏移)

public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count, @NonNull Paint paint) 複製代碼

描述: 該方法比上一個方法多加兩個參數,即偏移量和數量。偏移量offset爲一時,則從pts的下標爲1的地方開始進行讀數,count則決定了多少個數。

舉個例子

private float[] pts = new float[]{
            0, -400, 200, -400, 
            -300, 0, -300, 300, 
            0, 400, 300, 400    
    };

canvas.drawLines(pts, 2, 8, mPaint);
複製代碼

效果圖

pts數組中,從下標爲2的數字開始,每四個數構成一條線,直到下標爲 10 (由8+2得來) 的數爲止。第一條線爲上面的線,第二條線爲下面的線。

四、drawArc 畫弧

(1)第一個drawArc函數

public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) 複製代碼

描述: 在 oval矩形範圍內,繪製 從startAngle角度 到 sweepAngle角度的圓弧。

參數說明: 1)oval:圓弧所繪的矩形範圍區域。 2)startAngle:起始角度。0度時,指向爲座標系中的x軸正半軸。 3)sweepAngle:基於 startAngle 角度,掃過的角度範圍,正數則按順時針方向,負數則按逆時針方向。 4)useCenter:弧的兩端是否要鏈接中心點。true鏈接中心點,false不鏈接中心點。 5)paint:畫筆。

舉個例子

RectF mRectF = new RectF();
mRectF.left = -150;
mRectF.top = -150;
mRectF.right = 400;
mRectF.bottom = 150;

canvas.drawArc(mRectF, 0, 120, true, mPaint);
複製代碼

效果圖

橘色部分則爲弧線部分,紫色則爲矩形範圍(爲了方便查看才繪出)。

(2)第二個drawArc函數

public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) 複製代碼

描述: 該方法和上一方法功能徹底同樣,只是用四個 float 表示 矩形的端點。

舉個例子

canvas.drawArc(-150, -150, 400, 150, 0, 120, false, mPaint);
複製代碼

效果圖

橘色爲圓弧,紫色爲矩形範圍

五、drawPoint 畫點

(1)drawPoint函數

public void drawPoint(float x, float y, @NonNull Paint paint) 複製代碼

描述: 在座標爲 (x,y) 處繪製點

舉個例子

mPaint.setColor(mColor1);
mPaint.setStrokeWidth(dpToPx(5));
canvas.drawPoint(100, 100, mPaint);
複製代碼

效果圖

(2)第一個drawPoints函數

public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) 複製代碼

描述: pts數組中每兩個數構成一個座標(前者爲x,後者爲y),並在該座標處點。

舉個例子

private float[] pts = new float[]{
        0, -400,
        200, -400,
        -300, 0
};

mPaint.setColor(mColor2);
mPaint.setStrokeWidth(dpToPx(5));
canvas.drawPoints(pts, mPaint);
複製代碼

效果圖

(3)第二個drawPoints函數(帶偏移)

public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count, @NonNull Paint paint) 複製代碼

描述: 這個方法和上面的方法大體相同,惟一區別在於從下標爲offset開始讀取座標,讀取長度個數爲count。

舉個例子

private float[] pts = new float[]{
        0, -400,
        200, -400,
        -300, 0
};

mPaint.setColor(mColor2);
mPaint.setStrokeWidth(dpToPx(5));
canvas.drawPoints(pts, 1, pts.length - 1, mPaint);
複製代碼

效果圖

六、drawRect 畫矩形

(1)drawRect函數

public void drawRect(@NonNull RectF rect, @NonNull Paint paint) public void drawRect(@NonNull Rect r, @NonNull Paint paint) 複製代碼

描述: 在 rect 的範圍內繪製矩形,兩個方法的惟一區別在於第一個參數類型分別爲 RectF 和 Rect。

RectF 和 Rect 的區別:

  1. 精度不一樣:RectF 四個點爲浮點數,Rect 四個點爲整型
  2. 所包含的方法不徹底相同。

舉個例子

RectF mRectF = new RectF();
mRectF.left = -150;
mRectF.top = -150;
mRectF.right = 400;
mRectF.bottom = 150;

canvas.drawRect(mRectF, mPaint);
複製代碼

效果圖

(2)drawRect函數

public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) 複製代碼

描述: 在 (left,top) 和 (right,bottom) 造成的矩形範圍內繪製矩形。

舉個例子

canvas.drawRect(-150, -150, 400, 150, mPaint);
複製代碼

效果圖

七、drawRoundRect 畫圓角矩形

(1)第一個drawRoundRect函數

public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) 複製代碼

描述: 在 rect 範圍內,繪製圓角矩形。

參數說明: 1)rx:水平方向的半徑,下圖中的橘色部分 2)ry:豎直方向的半徑,下圖中的紅色部分

舉個例子

canvas.drawRoundRect(mRectF, 80, 100, mPaint);
複製代碼

效果圖

(2)第二個drawRoundRect函數

public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint) 複製代碼

描述: 與上述的方法功能徹底相同,只是繪製範圍由四個浮點數進行肯定。

舉個例子

canvas.drawRoundRect(-150, -150, 400, 150, 100, 50, mPaint);
複製代碼

效果圖

八、drawColor 給畫布點顏色

(1)第一個drawColor函數

public void drawColor(@ColorInt int color) 複製代碼

描述: 給畫布繪製color顏色值。

舉個例子

canvas.drawColor(Color.parseColor("#ffffff"));
複製代碼

比較簡單就不上效果圖了。

(2)第二個drawColor函數

public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) 複製代碼

描述: 給畫布繪製顏色,會與以前的圖形有 mode 的做用。

舉個例子

Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.logo);

Matrix mMatrix = new Matrix();
mMatrix.setScale(0.25f, 0.25f);

canvas.drawBitmap(mBitmap, mMatrix, mPaint);
canvas.drawColor(Color.parseColor("#88880000"),
                PorterDuff.Mode.DST_OVER);
複製代碼

效果圖

值得一提

咱們所介紹的第一個 drawColor(@ColorInt int color) 函數,其實最後使用了 PorterDuff.Mode.SRC_OVER 模式。

至於 PorterDuff.Mode 的具體使用,請看小盆友的另外一篇博文:圖像操縱大師Xfermode講解與實戰

九、drawRGB 給畫布點顏色

(1)drawRGB函數

public void drawRGB(int r, int g, int b) 複製代碼

描述: 給畫布繪製顏色,按照 紅(r),綠(g),藍(b) 三原色進行組合

舉個例子

canvas.drawARGB(255, 217, 142);
複製代碼

(2)drawARGB函數

public void drawARGB(int a, int r, int g, int b) 複製代碼

描述: 給畫布繪製顏色,按照 透明度(a),紅(r),綠(g),藍(b) 三原色進行組合

舉個例子

canvas.drawARGB(200, 255, 217, 142);
複製代碼

十、drawPath 繪製路徑

public void drawPath(@NonNull Path path, @NonNull Paint paint) 複製代碼

描述: 將 路徑path 繪製在畫布上。

舉個例子 這個方法使用的地方很是之多,例如咱們繪製一個 「心」 形

mPaint.setColor(mColor1);
mPaint.setStyle(Paint.Style.FILL);
// 路徑的構建,移步github
canvas.drawPath(mPath, mPaint);
複製代碼

效果圖

值得一提

心形路徑的構建使用了 貝塞爾曲線,對 貝塞爾曲線 有興趣的童鞋,能夠移步小盆友的另外一篇博文:自帶美感的貝塞爾曲線原理與實戰

4、畫布保存狀態API

一、狀態值

在進行 API 講解前,咱們須要先說明狀態值,他控制着咱們要保存什麼信息。

  1. MATRIX_SAVE_FLAG:保存圖層的 Matrix矩陣信息
  2. CLIP_SAVE_FLAG:保存裁剪信息
  3. HAS_ALPHA_LAYER_SAVE_FLAG:保存該圖層的透明度
  4. FULL_COLOR_LAYER_SAVE_FLAG:徹底保留該圖層顏色
  5. CLIP_TO_LAYER_SAVE_FLAG:建立圖層時,會把canvas(全部圖層)裁剪到參數指定的範圍,若是省略這個flag將致使圖層開銷巨大,性能很差。
  6. ALL_SAVE_FLAG:保存全部信息

敲黑板了!!! 雖然羅列了這麼多,但1-5的FLAG已經所有被遺棄,只剩 ALL_SAVE_FLAG 這個FLAG

二、save

public int save() 複製代碼

描述: 這個函數用於保存圖層狀態,保存此刻的 canvas 畫布的全部狀態(例如:原點位置,旋轉角度,一切咱們對canvas的操做都被保存)。

三、saveLayer

// saveFlags 只能是 Canvas.ALL_SAVE_FLAG
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint, @Saveflags int saveFlags) // saveFlags 只能是 Canvas.ALL_SAVE_FLAG  public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) // API21及以上纔可以使用 public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) // API21及以上纔可以使用 public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint) 複製代碼

描述: 該方法與 save 同樣會保存進狀態棧,而後經過 restorerestoreToCount 進行恢復。不一樣的是該方法會建立一個新的圖層

這裏建立的圖層,咱們能夠類比爲PS中的圖層概念,存在乎義是不會影響到其餘圖層的數據。例如咱們在XFermode的博文中的刮刮卡的實戰中,就有用到這一律念,不然咱們須要看到的圖片也會被一同清除。

四、saveLayerAlpha

// saveFlags 只能是 Canvas.ALL_SAVE_FLAG
public int saveLayerAlpha(@Nullable RectF bounds, int alpha, @Saveflags int saveFlags) // saveFlags 只能是 Canvas.ALL_SAVE_FLAG  public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, @Saveflags int saveFlags) // API21及以上纔可以使用 public int saveLayerAlpha(@Nullable RectF bounds, int alpha) // API21及以上纔可以使用 public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) 複製代碼

描述:saveLayer 相同的是會存進狀態棧和建立一個圖層,而後經過 restorerestoreToCount 進行恢復。不一樣的是建立的圖層是具備透明度的,而透明度由 alpha 決定,範圍爲 0-255。

五、恢復

// 恢復
public void restore() // 恢復至指定的 狀態棧層數 public void restoreToCount(int saveCount) 複製代碼

描述: 這兩個方法,是將上面三種方法保存的函數進行恢復。而區別在於 restore 每次從狀態棧中恢復拿出一個狀態恢復,而 restoreToCount恢復到指定的狀態棧層數(該層也會被出棧),這個 saveCount 參數在上面三種類型的方法調用後都會進行返回各自對應的層數。

六、小結

先舉個例子彙總一下這幾個方法:

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    log(canvas);

    int layer = canvas.saveLayer(0, 0, getWidth(), getHeight(),
            mPaint, Canvas.ALL_SAVE_FLAG);
    log(canvas);

    canvas.save();
    log(canvas);

    canvas.saveLayer(0, 0, getWidth(), getHeight(),
            mPaint, Canvas.ALL_SAVE_FLAG);
    log(canvas);

    canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(),
            50, Canvas.ALL_SAVE_FLAG);
    log(canvas);

    canvas.translate(getWidth() / 2, getHeight() / 2);
    canvas.drawRect(mRect, mPaint);

    canvas.restore();
    log(canvas);

    canvas.restoreToCount(layer);
    log(canvas);

}

private void log(Canvas canvas) {
    Log.i("canvas", "canvas count:" + canvas.getSaveCount());
}
複製代碼

輸出結果

咱們從代碼和輸出結果能夠得出如下幾個結論:

  1. 初始狀態下,狀態棧中便有一個默認的狀態;
  2. 在不建立圖層的狀況下,全部操做都是做用於默認圖層;
  3. 使用 restoreToCount(x) 進行恢復,會連同x層出棧;

一圖勝千言:

將上面的代碼轉換成圖,就以下效果

5、實戰——時鐘與指針

一、效果圖

github地址: 傳送門

二、編程思路

咱們先拆解下這幅圖,其實構成的爲三部分:

  1. 一個圓圈
  2. 刻度
  3. 指針

咱們逐一解決:

(1)一個圓圈

這個咱們信手拈來,canvas就有繪製圓的 API,咱們在第三節的一小點就講到了

canvas.drawCircle(0, 0, width / 2, mPaint);
複製代碼

(2)刻度

對於刻度,其實有兩種畫法:

  • 第一種:是聽起來比較 「高大上」 ,使用三角函數算出每一個刻度的起始座標和終止座標,而後進行繪製。
  • 第二種:較爲機靈,使用咱們在 第二小節的第一點 介紹的 rotate 進行一點點的旋轉畫布,而後繪製線。

(3)指針

咱們須要先構建下圖中藍色的路徑做爲指針,由一段圓弧和兩條線構成。

構建思路:

第一步:在紅色的矩形內,繪製圓弧(使用了第三小節第四點) 第二步:從圓弧的左點繪製線到圖中紅色頂點 第三部:從紅色頂點繪製線到圓弧右點,最後關閉路徑path

具體代碼以下:

mPointerPath.moveTo(mPointerRadius, 0);
// 第一步
mPointerPath.addArc(mPointerRectF, 0, 180);
// 第二步
mPointerPath.lineTo(0, -width / 4);
// 第三步
mPointerPath.lineTo(mPointerRadius, 0);
mPointerPath.close();
複製代碼

(4)開啓旋轉

咱們只須要經過屬性動畫,讓指針動起來便可。而指針的旋轉只須要經過讓畫布旋轉便可,也就是用到第二小節第一點的rotate

canvas.save();
canvas.rotate(mCurAngle);

... 省略建立指針

mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mPointerColor);
canvas.drawPath(mPointerPath, mPaint);
canvas.restore();
複製代碼

時鐘與指針 完整代碼:傳送門

6、寫在最後

此次介紹的是canvas最爲基礎的API操做,但其實越爲基礎的東西,越容易被忽略也越是進階中最須要的部分。此次寫的時間耗時較久,主要是API較多,寫demo和截圖比較頻繁。

若是你以爲文章對你有所幫助,請給我一個贊並關注我吧。若是發現有那些欠妥的地方,請留言區與我討論,咱們共同進步。

高級UI系列的Github地址:請進入傳送門,若是喜歡的話給我一個star吧😄

歡迎加我微信,咱們能夠進行更多更有趣的交流

相關文章
相關標籤/搜索