1. 程式人生 > >安卓自己定義View進階-Canvas之繪制基本形狀

安卓自己定義View進階-Canvas之繪制基本形狀

記錄 區域 封裝 -1 mangle set tran dia 中心

Canvas之繪制基本形狀

作者微博: @GcsSloop

【本系列相關文章】

在上一篇自己定義View分類與流程中我們了解自己定義View相關的基本知識,只是,這些東西依然還是理論,並不能拿來(zhuang)用(B), 這一次我們就了解一些能(zhaung)用(B)的東西。

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

一.Canvas簡單介紹

Canvas我們能夠稱之為畫布,能夠在上面繪制各種東西,是安卓平臺2D圖形繪制的基礎,非常強大。

**一般來說,比較基礎的東西有兩大特點:

1.可操作性強:因為這些是構成上層的基礎。所以可操作性必定十分強大。

2.比較難用:各種方法太過基礎,想要完美的將這些操作組合起來有一定難度。**

只是不必操心。本系列文章不僅會介紹到Canvas的操作方法,還會簡單介紹一些設計思路和技巧。

二.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經常用法在上面表格中已經所有列出了,當然還存在一些其它的方法未列出,具體能夠參考官方文檔 Canvas


三.Canvas具體解釋

本篇內容主要解說怎樣利用Canvas繪制基本圖形。

繪制顏色:

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

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

技術分享

關於顏色的很多其它資料請參考基礎篇_顏色


創建畫筆:

要想繪制內容,首先須要先創建一個畫筆,例如以下:

  // 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(單精度浮點型)的。除了精度不同,兩種提供的方法也略微存在差別。在這裏我們臨時無需關註,想了解很多其它參見官方文檔 Rect 和 RectF


繪制圓角矩形:

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

        // 第一種
        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,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);

技術分享


簡要介紹Paint

看了上面這麽多。相信有一部分人會產生一個疑問,假設我想繪制一個圓,僅僅要邊不要裏面的顏色怎麽辦?

非常easy,繪制的基本形狀由Canvas確定。但繪制出來的顏色,具體效果則由Paint確定

假設你註意到了的話,在一開始我們設置畫筆樣式的時候是這種:

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

為了展示方便,easy看出效果,之前使用的模式一直為填充模式,實際上畫筆有三種模式,例如以下:

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 旋轉

制作一個餅狀圖

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

技術分享

簡單分析

事實上依據我們上面的知識已經能自己制作一個餅狀圖了。只是制作東西最重要的不是制作結果,而是制作思路。


相信我貼上代碼大家一看就立馬明確了,非常easy的東西。只是嘛,咱們還是想了解一下制作思路:

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

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


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

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

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



封裝數據:

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 {
    // 顏色表
    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;
        initDate(mData);
        invalidate();   // 刷新
    }

    // 初始化數據
    private void initDate(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: 這個餅狀圖並沒有加入百分比等數據。僅作為演示樣例使用。

總結:

事實上自己定義View僅僅要依照流程一步步的走。也是比較easy的。只是裏面也有不少坑,這些坑還是自己踩過印象比較深,建議大家不要直接copy源代碼,自己手打體驗一下。

About Me

作者微博: @GcsSloop

技術分享

參考資料:

View

Canvas

Android Canvas畫圖具體解釋






安卓自己定義View進階-Canvas之繪制基本形狀