安卓自定義View進階-Canvas之繪製圖形

在本篇文章中,咱們先了解Canvas的基本用法,最後用一個小示例來結束本次教程。css

一.Canvas簡介

Canvas咱們能夠稱之爲畫布,可以在上面繪製各類東西,是安卓平臺2D圖形繪製的基礎,很是強大。html

通常來講,比較基礎的東西有兩大特色:java

  • 1.可操做性強:因爲這些是構成上層的基礎,因此可操做性必然十分強大。
  • 2.比較難用:各類方法太過基礎,想要完美的將這些操做組合起來有必定難度。

不過沒必要擔憂,本系列文章不只會介紹到Canvas的操做方法,還會簡單介紹一些設計思路和技巧。android

二.Canvas的經常使用操做速查表

操做類型 相關API 備註
繪製顏色 drawColor, drawRGB, drawARGB 使用單一顏色填充整個畫布
繪製基本形狀 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次爲 點、線、矩形、圓角矩形、橢圓、圓、圓弧
繪製圖片 drawBitmap, drawPicture 繪製位圖和圖片
繪製文本 drawText, drawPosText, drawTextOnPath 依次爲 繪製文字、繪製文字時指定每一個文字位置、根據路徑繪製文字
繪製路徑 drawPath 繪製路徑,繪製貝塞爾曲線時也須要用到該函數
頂點操做 drawVertices, drawBitmapMesh 經過對頂點操做可使圖像形變,drawVertices直接對畫布做用、 drawBitmapMesh只對繪製的Bitmap做用
畫布剪裁 clipPath, clipRect 設置畫布的顯示區域
畫布快照 save, restore, saveLayerXxx, restoreToCount, getSaveCount 依次爲 保存當前狀態、 回滾到上一次保存的狀態、 保存圖層狀態、 回滾到指定狀態、 獲取保存次數
畫布變換 translate, scale, rotate, skew 依次爲 位移、縮放、 旋轉、錯切
Matrix(矩陣) getMatrix, setMatrix, concat 實際上畫布的位移,縮放等操做的都是圖像矩陣Matrix, 只不過Matrix比較難以理解和使用,故封裝了一些經常使用的方法。

PS: Canvas經常使用方法在上面表格中已經所有列出了,固然還存在一些其餘的方法未列出,具體能夠參考官方文檔 Canvasgit


三.Canvas詳解

本篇內容主要講解如何利用Canvas繪製基本圖形。github

繪製顏色:

繪製顏色是填充整個畫布,經常使用於繪製底色。canvas

canvas.drawColor(Color.BLUE); //繪製藍色

關於顏色的更多資料請參考基礎篇_顏色數組


建立畫筆:

要想繪製內容,首先須要先建立一個畫筆,以下:ide

// 1.建立一個畫筆
private Paint mPaint = new Paint();

// 2.初始化畫筆
private void initPaint() {
	mPaint.setColor(Color.BLACK);       //設置畫筆顏色
	mPaint.setStyle(Paint.Style.FILL);  //設置畫筆模式爲填充
	mPaint.setStrokeWidth(10f);         //設置畫筆寬度爲10px
}

// 3.在構造函數中初始化
public SloopView(Context context, AttributeSet attrs) {
   super(context, attrs);
   initPaint();
}

在建立完畫筆以後,就能夠在Canvas中繪製各類內容了。函數


繪製點:

能夠繪製一個點,也能夠繪製一組點,以下:

canvas.drawPoint(200, 200, mPaint);     //在座標(200,200)位置繪製一個點
canvas.drawPoints(new float[]{          //繪製一組點,座標位置由float數組指定
      500,500,
      500,600,
      500,700
},mPaint);

關於座標原點默認在左上角,水平向右爲x軸增大方向,豎直向下爲y軸增大方向。

更多參考這裏 基礎篇_座標系


繪製直線:

繪製直線須要兩個點,初始點和結束點,一樣繪製直線也能夠繪製一條或者繪製一組:

canvas.drawLine(300,300,500,600,mPaint);    // 在座標(300,300)(500,600)之間繪製一條直線
canvas.drawLines(new float[]{               // 繪製一組線 每四數字(兩個點的座標)肯定一條線
    100,200,200,200,
    100,300,200,300
},mPaint);


繪製矩形:

肯定肯定一個矩形最少須要四個數據,就是對角線的兩個點的座標值,這裏通常採用左上角和右下角的兩個點的座標。

關於繪製矩形,Canvas提供了三種重載方法,第一種就是提供四個數值(矩形左上角和右下角兩個點的座標)來肯定一個矩形進行繪製。 其他兩種是先將矩形封裝爲Rect或RectF(實際上仍然是用兩個座標點來肯定的矩形),而後傳遞給Canvas繪製,以下:

// 第一種
canvas.drawRect(100,100,800,400,mPaint);

// 第二種
Rect rect = new Rect(100,100,800,400);
canvas.drawRect(rect,mPaint);

// 第三種
RectF rectF = new RectF(100,100,800,400);
canvas.drawRect(rectF,mPaint);

以上三種方法所繪製出來的結果是徹底同樣的。

看到這裏,相信不少觀衆會產生一個疑問,爲何會有Rect和RectF兩種?二者有什麼區別嗎?

答案固然是存在區別的,二者最大的區別就是精度不一樣,Rect是int(整形)的,而RectF是float(單精度浮點型)的。除了精度不一樣,兩種提供的方法也稍微存在差異,在這裏咱們暫時無需關注,想了解更多參見官方文檔 RectRectF


繪製圓角矩形:

繪製圓角矩形也提供了兩種重載方式,以下:

// 第一種
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);

// 第二種
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);

上面兩種方法繪製效果也是同樣的,但鑑於第二種方法在API21的時候才添加上,因此咱們通常使用的都是第一種。

下面簡單解析一下圓角矩形的幾個必要的參數的意思。

很明顯能夠看出,第二種方法前四個參數和第一種方法的RectF做用是同樣的,都是爲了肯定一個矩形,最後一個參數Paint是畫筆,無需多說,與矩形相比,圓角矩形多出來了兩個參數rx 和 ry,這兩個參數是幹什麼的呢?

稍微分析一下,既然是圓角矩形,他的角確定是圓弧(圓形的一部分),咱們通常用什麼肯定一個圓形呢?

答案是圓心 和 半徑,其中圓心用於肯定位置,而半徑用於肯定大小

因爲矩形位置已經肯定,因此其邊角位置也是肯定的,那麼肯定位置的參數就能夠省略,只須要用半徑就能描述一個圓弧了。

可是,半徑只須要一個參數,但這裏怎麼會有兩個呢?

好吧,讓你發現了,這裏圓角矩形的角實際上不是一個正圓的圓弧,而是橢圓的圓弧,這裏的兩個參數其實是橢圓的兩個半徑,他們看起來個以下圖:

紅線標註的 rx 與 ry 就是兩個半徑,也就是相比繪製矩形多出來的那兩個參數。

咱們瞭解到原理後,就能夠隨心所欲了,經過計算可知咱們上次繪製的矩形寬度爲700,高度爲300,當你讓 rx大於350(寬度的一半), ry大於150(高度的一半) 時奇蹟就出現了, 你會發現圓角矩形變成了一個橢圓, 他們畫出來是這樣的 ( 爲了方便確認我更改了畫筆顏色, 同時繪製出了矩形和圓角矩形 ):

// 矩形
RectF rectF = new RectF(100,100,800,400);  

// 繪製背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);

// 繪製圓角矩形
mPaint.setColor(Color.BLUE);
canvas.drawRoundRect(rectF,700,400,mPaint);

其中灰色部分是咱們所選定的矩形,而裏面的圓角矩形則變成了一個橢圓,實際上在rx爲寬度的一半,ry爲高度的一半時,恰好是一個橢圓,經過上面咱們分析的原理推算一下就能獲得,而當rx大於寬度的一半,ry大於高度的一半時,其實是沒法計算出圓弧的,因此drawRoundRect對大於該數值的參數進行了限制(修正),凡是大於一半的參數均按照一半來處理。


繪製橢圓:

相對於繪製圓角矩形,繪製橢圓就簡單的多了,由於他只須要一個矩形矩形做爲參數:

// 第一種
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);

// 第二種
canvas.drawOval(100,100,800,400,mPaint);

一樣,以上兩種方法效果徹底同樣,但通常使用第一種。

繪製橢圓實際上就是繪製一個矩形的內切圖形,原理以下,就很少說了:

PS: 若是你傳遞進來的是一個長寬相等的矩形(即正方形),那麼繪製出來的實際上就是一個圓。


繪製圓:

繪製圓形也比較簡單, 以下:

canvas.drawCircle(500,500,400,mPaint);  // 繪製一個圓心座標在(500,500),半徑爲400 的圓。

繪製圓形有四個參數,前兩個是圓心座標,第三個是半徑,最後一個是畫筆。


繪製圓弧:

繪製圓弧就比較神奇一點了,爲了理解這個比較神奇的東西,咱們先看一下它須要的幾個參數:

// 第一種
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}
    
// 第二種
public void drawArc(float left, float top, float right, float bottom, float startAngle,
            float sweepAngle, boolean useCenter, @NonNull Paint paint) {}

從上面能夠看出,相比於繪製橢圓,繪製圓弧還多了三個參數:

startAngle  // 開始角度
sweepAngle  // 掃過角度
useCenter   // 是否使用中心

經過字面意思咱們基本能猜想出來前兩個參數(startAngle, sweepAngel)的做用,就是肯定角度的起始位置和掃過角度, 不過第三個參數是幹嗎的?試一下就知道了,上代碼:

RectF rectF = new RectF(100,100,800,400);
// 繪製背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);

// 繪製圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);

//-------------------------------------

RectF rectF2 = new RectF(100,600,800,900);
// 繪製背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);

// 繪製圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);

上述代碼其實是繪製了一個起始角度爲0度,掃過90度的圓弧,二者的區別就是是否使用了中心點,結果以下:

能夠發現使用了中心點以後繪製出來相似於一個扇形,而不使用中心點則是圓弧起始點和結束點之間的連線加上圓弧圍成的圖形。這樣中心點這個參數的做用就很明顯了,沒必要多說想必你們試一下就明白了。 另外能夠關於角度能夠參考一下這篇文章: 角度與弧度

相比於使用橢圓,咱們仍是使用正圓比較多的,使用正圓展現一下效果:

RectF rectF = new RectF(100,100,600,600);
// 繪製背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);

// 繪製圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);

//-------------------------------------

RectF rectF2 = new RectF(100,700,600,1200);
// 繪製背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);

// 繪製圓弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);


簡要介紹Paint

看了上面這麼多,相信有一部分人會產生一個疑問,若是我想繪製一個圓,只要邊不要裏面的顏色怎麼辦?

很簡單,繪製的基本形狀由Canvas肯定,但繪製出來的顏色,具體效果則由Paint肯定

若是你注意到了的話,在一開始咱們設置畫筆樣式的時候是這樣的:

mPaint.setStyle(Paint.Style.FILL);  //設置畫筆模式爲填充

爲了展現方便,容易看出效果,以前使用的模式一直爲填充模式,實際上畫筆有三種模式,以下:

STROKE                //描邊
FILL                  //填充
FILL_AND_STROKE       //描邊加填充

爲了區分三者效果咱們作以下實驗:

Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40);     //爲了實驗效果明顯,特意設置描邊寬度很是大

// 描邊
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);

// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);

// 描邊加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);

一圖勝千言,經過以上實驗咱們能夠比較明顯的看出三種模式的區別,若是隻須要邊緣不須要填充內容的話只須要設置模式爲描邊(STROKE)便可。

其實關於Paint的內容也是有很多的,這些只是冰山一角,在後續內容中會詳細的講解Paint。


小示例

簡要介紹畫布的操做:

畫布操做詳細內容會在下一篇文章中講解, 不是本文重點,但如下示例中可能會用到,因此此處簡要介紹一下。

相關操做 簡要介紹
save 保存當前畫布狀態
restore 回滾到上一次保存的狀態
translate 相對於當前位置位移
rotate 旋轉

製做一個餅狀圖

在展現百分比數據的時候常常會用到餅狀圖,像這樣:

簡單分析

其實根據咱們上面的知識已經能本身製做一個餅狀圖了。不過製做東西最重要的不是製做結果,而是製做思路。 相信我貼上代碼你們一看就馬上明白了,很是簡單的東西。不過嘛,我們仍是想了解一下製做思路:

先分析餅狀圖的構成,很是明顯,餅狀圖就是一個又一個的扇形構成的,每一個扇形都有不一樣的顏色,對應的有名字,數據和百分比。

經以上信息能夠得出餅狀圖的最基本數據應包括:名字 數據值 百分比 對應的角度 顏色

用戶關心的數據 : 名字 數據值 百分比

須要程序計算的數據: 百分比 對應的角度

其中顏色這一項能夠用戶指定也能夠用程序指定(咱們這裏採用程序指定)。

封裝數據:

public class PieData {
    // 用戶關心數據
    private String name;        // 名字
    private float value;        // 數值
    private float percentage;   // 百分比
    
    // 非用戶關心數據
    private int color = 0;      // 顏色
    private float angle = 0;    // 角度

    public PieData(@NonNull String name, @NonNull float value) {
        this.name = name;
        this.value = value;
    }
}

PS: 以上省略了get set方法

自定義View:

先按照自定義View流程梳理一遍(肯定各個步驟應該作的事情):

步驟 關鍵字 做用
1 構造函數 初始化(初始化畫筆Paint)
2 onMeasure 測量View的大小(暫時不用關心)
3 onSizeChanged 肯定View大小(記錄當前View的寬高)
4 onLayout 肯定子View佈局(無子View,不關心)
5 onDraw 實際繪製內容(繪製餅狀圖)
6 提供接口 提供接口(提供設置數據的接口)

代碼以下:

public class PieView extends View {
    // 顏色表 (注意: 此處定義顏色使用的是ARGB,帶Alpha通道的)
    private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
            0xFFE6B800, 0xFF7CFC00};
    // 餅狀圖初始繪製角度
    private float mStartAngle = 0;
    // 數據
    private ArrayList<PieData> mData;
    // 寬高
    private int mWidth, mHeight;
    // 畫筆
    private Paint mPaint = new Paint();

    public PieView(Context context) {
        this(context, null);
    }

    public PieView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null == mData)
            return;
        float currentStartAngle = mStartAngle;                    // 當前起始角度
        canvas.translate(mWidth / 2, mHeight / 2);                // 將畫布座標原點移動到中心位置
        float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8);  // 餅狀圖半徑
        RectF rect = new RectF(-r, -r, r, r);                     // 餅狀圖繪製區域

        for (int i = 0; i < mData.size(); i++) {
            PieData pie = mData.get(i);
            mPaint.setColor(pie.getColor());
            canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint);
            currentStartAngle += pie.getAngle();
        }

    }

    // 設置起始角度
    public void setStartAngle(int mStartAngle) {
        this.mStartAngle = mStartAngle;
        invalidate();   // 刷新
    }

    // 設置數據
    public void setData(ArrayList<PieData> mData) {
        this.mData = mData;
        initData(mData);
        invalidate();   // 刷新
    }

    // 初始化數據
    private void initData(ArrayList<PieData> mData) {
        if (null == mData || mData.size() == 0)   // 數據有問題 直接返回
            return;

        float sumValue = 0;
        for (int i = 0; i < mData.size(); i++) {
            PieData pie = mData.get(i);

            sumValue += pie.getValue();       //計算數值和

            int j = i % mColors.length;       //設置顏色
            pie.setColor(mColors[j]);
        }

        float sumAngle = 0;
        for (int i = 0; i < mData.size(); i++) {
            PieData pie = mData.get(i);

            float percentage = pie.getValue() / sumValue;   // 百分比
            float angle = percentage * 360;                 // 對應的角度

            pie.setPercentage(percentage);                  // 記錄百分比
            pie.setAngle(angle);                            // 記錄角度大小
            sumAngle += angle;

            Log.i("angle", "" + pie.getAngle());
        }
    }
}

PS: 在更改了數據須要重繪界面時要調用invalidate()這個函數從新繪製。

效果圖

PS: 這個餅狀圖並無添加百分比等數據,僅做爲示例使用。

PieView源碼下載

總結:

其實自定義View只要按照流程一步步的走,也是比較容易的。不過裏面也有很多坑,這些坑仍是本身踩過印象比較深,建議你們不要直接copy源碼,本身手打體驗一下。

About

本系列相關文章

做者微博: GcsSloop

參考資料:

View
Canvas
Android Canvas繪圖詳解

相關文章
相關標籤/搜索