Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Canvas 畫各種圖形和Bitmap詳解

Android Canvas 畫各種圖形和Bitmap詳解

編輯:關於Android編程

Android中,如果我們想繪制復雜的自定義View或游戲,我們就需要熟悉繪圖API。Android通過Canvas類暴露了很多drawXXX方法,我們可以通過這些方法繪制各種各樣的圖形。Canvas繪圖有三個基本要素:Canvas、繪圖坐標系以及Paint。Canvas是畫布,我們通過Canvas的各種drawXXX方法將圖形繪制到Canvas上面,在drawXXX方法中我們需要傳入要繪制的圖形的坐標形狀,還要傳入一個畫筆Paint。drawXXX方法以及傳入其中的坐標決定了要繪制的圖形的形狀,比如drawCircle方法,用來繪制圓形,需要我們傳入圓心的x和y坐標,以及圓的半徑。drawXXX方法中傳入的畫筆Paint決定了繪制的圖形的一些外觀,比如是繪制的圖形的顏色,再比如是繪制圓面還是圓的輪廓線等。Android系統的設計吸收了很多已有系統的諸多優秀之處,比如Canvas繪圖。Canvas不是Android所特有的,Flex和Silverlight都支持Canvas繪圖,Canvas也是HTML5標准中的一部分,主流的現代浏覽器都支持用JavaScript在Canvas上繪圖,如果你用過HTML5中的Canvas,你會發現Android的Canvas的繪圖API與其很相似。總之,Canvas繪圖不是Android所特有的。

為了演示Android中各種drawXXX方法的時候,我做了一個App,通過單擊相應的按鈕繪制相應的圖形,主界面如下所示:

這裡寫圖片描述


Canvas坐標系與繪圖坐標系

Canvas繪圖中牽扯到兩種坐標系:Canvas坐標系與繪圖坐標系。

  • Canvas坐標系
    Canvas坐標系指的是Canvas本身的坐標系,Canvas坐標系有且只有一個,且是唯一不變的,其坐標原點在View的左上角,從坐標原點向右為x軸的正半軸,從坐標原點向下為y軸的正半軸。

  • 繪圖坐標系
  • Canvas的drawXXX方法中傳入的各種坐標指的都是繪圖坐標系中的坐標,而非Canvas坐標系中的坐標。默認情況下,繪圖坐標系與Canvas坐標系完全重合,即初始狀況下,繪圖坐標系的坐標原點也在View的左上角,從原點向右為x軸正半軸,從原點向下為y軸正半軸。但不同於Canvas坐標系,繪圖坐標系並不是一成不變的,可以通過調用Canvas的translate方法平移坐標系,可以通過Canvas的rotate方法旋轉坐標系,還可以通過Canvas的scale方法縮放坐標系,而且需要注意的是,translate、rotate、scale的操作都是基於當前繪圖坐標系的,而不是基於Canvas坐標系,一旦通過以上方法對坐標系進行了操作之後,當前繪圖坐標系就變化了,以後繪圖都是基於更新的繪圖坐標系了。也就是說,真正對我們繪圖有用的是繪圖坐標系而非Canvas坐標系。

為了更好的理解繪圖坐標系,請看如下的代碼:

    //繪制坐標系
    private void drawAxis(Canvas canvas){
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeWidth(6 * density);

        //用綠色畫x軸,用藍色畫y軸

        //第一次繪制坐標軸
        paint.setColor(0xff00ff00);//綠色
        canvas.drawLine(0, 0, canvasWidth, 0, paint);//繪制x軸
        paint.setColor(0xff0000ff);//藍色
        canvas.drawLine(0, 0, 0, canvasHeight, paint);//繪制y軸

        //對坐標系平移後,第二次繪制坐標軸
        canvas.translate(canvasWidth / 4, canvasWidth /4);//把坐標系向右下角平移
        paint.setColor(0xff00ff00);//綠色
        canvas.drawLine(0, 0, canvasWidth, 0, paint);//繪制x軸
        paint.setColor(0xff0000ff);//藍色
        canvas.drawLine(0, 0, 0, canvasHeight, paint);//繪制y軸

        //再次平移坐標系並在此基礎上旋轉坐標系,第三次繪制坐標軸
        canvas.translate(canvasWidth / 4, canvasWidth / 4);//在上次平移的基礎上再把坐標系向右下角平移
        canvas.rotate(30);//基於當前繪圖坐標系的原點旋轉坐標系
        paint.setColor(0xff00ff00);//綠色
        canvas.drawLine(0, 0, canvasWidth, 0, paint);//繪制x軸
        paint.setColor(0xff0000ff);//藍色
        canvas.drawLine(0, 0, 0, canvasHeight, paint);//繪制y軸
    }

界面如下所示:
這裡寫圖片描述vcfGvdLGwcvSu7bOvuDA66OsyLu687vm1saz9rXE1/ix6s+10rK+zdX7zOXP8tPSz8K9x8a90sbBy6O7PGJyIC8+DQq12sj9tM7U2bTOz/LT0s/CvcfGvdLGo6yyotD916rByzMwtsijrM28yc/H49CxtcTX+LHqz7W8tNfuuvO1xLvmzbzX+LHqz7WhozwvcD4NCjxociAvPg0KPGgyIGlkPQ=="drawargb">drawARGB

Canvas中的drawARGB可以用來對整個Canvas以某種統一的顏色整體繪制,四個參數分別是Alpha、Red、Green、Blue,取值都是0-255。
使用代碼如下:

private void drawARGB(Canvas canvas){
        canvas.drawARGB(255, 139, 197, 186);
    }

界面如下所示:
這裡寫圖片描述


drawText

Canvas中用drawText方法繪制文字,代碼如下所示:

private void drawText(Canvas canvas){
        int canvasWidth = canvas.getWidth();
        int halfCanvasWidth = canvasWidth / 2;
        float translateY = textHeight;

        //繪制正常文本
        canvas.save();
        canvas.translate(0, translateY);
        canvas.drawText("正常繪制文本", 0, 0, paint);
        canvas.restore();
        translateY += textHeight * 2;

        //繪制綠色文本
        paint.setColor(0xff00ff00);//設置字體為綠色
        canvas.save();
        canvas.translate(0, translateY);//將畫筆向下移動
        canvas.drawText("繪制綠色文本", 0, 0, paint);
        canvas.restore();
        paint.setColor(0xff000000);//重新設置為黑色
        translateY += textHeight * 2;

        //設置左對齊
        paint.setTextAlign(Paint.Align.LEFT);//設置左對齊
        canvas.save();
        canvas.translate(halfCanvasWidth, translateY);
        canvas.drawText("左對齊文本", 0, 0, paint);
        canvas.restore();
        translateY += textHeight * 2;

        //設置居中對齊
        paint.setTextAlign(Paint.Align.CENTER);//設置居中對齊
        canvas.save();
        canvas.translate(halfCanvasWidth, translateY);
        canvas.drawText("居中對齊文本", 0, 0, paint);
        canvas.restore();
        translateY += textHeight * 2;

        //設置右對齊
        paint.setTextAlign(Paint.Align.RIGHT);//設置右對齊
        canvas.save();
        canvas.translate(halfCanvasWidth, translateY);
        canvas.drawText("右對齊文本", 0, 0, paint);
        canvas.restore();
        paint.setTextAlign(Paint.Align.LEFT);//重新設置為左對齊
        translateY += textHeight * 2;

        //設置下劃線
        paint.setUnderlineText(true);//設置具有下劃線
        canvas.save();
        canvas.translate(0, translateY);
        canvas.drawText("下劃線文本", 0, 0, paint);
        canvas.restore();
        paint.setUnderlineText(false);//重新設置為沒有下劃線
        translateY += textHeight * 2;

        //繪制加粗文字
        paint.setFakeBoldText(true);//將畫筆設置為粗體
        canvas.save();
        canvas.translate(0, translateY);
        canvas.drawText("粗體文本", 0, 0, paint);
        canvas.restore();
        paint.setFakeBoldText(false);//重新將畫筆設置為非粗體狀態
        translateY += textHeight * 2;

        //文本繞繪制起點順時針旋轉
        canvas.save();
        canvas.translate(0, translateY);
        canvas.rotate(20);
        canvas.drawText("文本繞繪制起點旋轉20度", 0, 0, paint);
        canvas.restore();
    }

界面如下所示:
這裡寫圖片描述

對以上代碼進行一下說明:

  1. Android中的畫筆有兩種Paint和TextPaint,我們可以Paint來畫其他的圖形:點、線、矩形、橢圓等。TextPaint繼承自Paint,是專門用來畫文本的,由於TextPaint繼承自Paint,所以也可以用TextPaint畫點、線、面、矩形、橢圓等圖形。

  2. 我們在上面的代碼中將canvas.translate()和canvas.rotate()放到了canvas.save()和canvas.restore()之間,這樣做的好處是,在canvas.save()調用時,將當前坐標系保存下來,將當前坐標系的矩陣Matrix入棧保存,然後通過translate或rotate等對坐標系進行變換,然後進行繪圖,繪圖完成後,我們通過調用canvas.restore()將之前保存的Matrix出棧,這樣就將當前繪圖坐標系恢復到了canvas.save()執行的時候狀態。如果熟悉OpenGL開發,對這種模式應該很了解。

  3. 通過調用paint.setColor(0xff00ff00)將畫筆設置為綠色,paint的setColor方法需要傳入一個int值,通常情況下我們寫成16進制0x的形式,第一個字節存儲Alpha通道,第二個字節存儲Red通道,第三個字節存儲Green通道,第四個字節存儲Blue通道,每個字節的取值都是從00到ff。如果對這種設置顏色的方式不熟悉,也可以調用paint.setARGB(int a, int r, int g, int b)方法設置畫筆的顏色,不過paint.setColor(int color)的方式更簡潔。

  4. 通過調用paint.setTextAlign()設置文本的對齊方式,該對齊方式是相對於繪制文本時的畫筆的坐標來說的,在本例中,我們繪制文本時畫筆在Canvas寬度的中間。在drawText()方法執行時,需要傳入一個x和y坐標,假設該點為P點,P點表示我們從P點繪制文本。當對齊方式為Paint.Align.LEFT時,繪制的文本以P點為基准向左對齊,這是默認的對齊方式;當對齊方式為Paint.Align.CENTER時,繪制的文本以P點為基准居中對齊;當對齊方式為Paint.Align.RIGHT時,繪制的文本以P點為基准向右對齊。

  5. 通過調用paint.setUnderlineText(true)繪制帶有下劃線的文本。

  6. 通過調用paint.setFakeBoldText(true)繪制粗體文本。

  7. 通過rotate旋轉坐標系,我們可以繪制傾斜文本。

drawPoint

Canvas中用drawPoint方法繪制點,代碼如下所示:

private void drawPoint(Canvas canvas){
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        int x = canvasWidth / 2;
        int deltaY = canvasHeight / 3;
        int y = deltaY / 2;
        paint.setColor(0xff8bc5ba);//設置顏色
        paint.setStrokeWidth(50 * density);//設置線寬,如果不設置線寬,無法繪制點

        //繪制Cap為BUTT的點
        paint.setStrokeCap(Paint.Cap.BUTT);
        canvas.drawPoint(x, y, paint);

        //繪制Cap為ROUND的點
        canvas.translate(0, deltaY);
        paint.setStrokeCap(Paint.Cap.ROUND);
        canvas.drawPoint(x, y, paint);

        //繪制Cap為SQUARE的點
        canvas.translate(0, deltaY);
        paint.setStrokeCap(Paint.Cap.SQUARE);
        canvas.drawPoint(x, y, paint);
    }

界面如下所示:
這裡寫圖片描述

下面對以上代碼進行說明:

  1. Paint的setStrokeWidth方法可以控制所畫線的寬度,通過Paint的getStrokeWidth方法可以得到所畫線的寬度,默認情況下,線寬是0。其實strokeWidth不僅對畫線有影響,對畫點也有影響,由於默認的線寬是0,所以默認情況下調用drawPoint方法無法在Canvas上畫出點,為了讓大家清楚地看到所畫的點,我用Paint的setStrokeWidth設置了一個比較大的線寬,這樣我們看到的點也就比較大。

  2. Paint有個setStrokeCap方法可以設置所畫線段的時候兩個端點的形狀,即所畫線段的帽端的形狀,在下面講到drawLine方法時會詳細說明,其實setStrokeCap方法也會影響所畫點的形狀。Paint的setStrokeCap方法可以有三個取值:Paint.Cap.BUTT、Paint.Cap.ROUND和Paint.Cap.SQUARE。

  3. 默認情況下Paint的getStrokeCap的返回值是Paint.Cap.BUTT,默認畫出來的點就是一個正方形,上圖第一個點即是用BUTT作為帽端畫的。

  4. 我們可以調用setStrokeCap方法設置Paint的strokeCap為Paint.Cap.ROUND時,畫筆畫出來的點就是一個圓形,上圖第二個點即是用ROUND作為帽端畫的。

  5. 調用調用setStrokeCap方法設置Paint的strokeCap為Paint.Cap.SQUARE時,畫筆畫出來的電也是一個正方形,與用BUTT畫出來的效果在外觀上相同,上圖最後一個點即時用SQUARE作為帽端畫的。

drawLine

Canvas通過drawLine方法繪制一條線段,通過drawLines方法繪制多段線,使用代碼如下所示:
這裡寫圖片描述

下面對以上代碼進行說明:

  1. drawLine方法接收四個數值,即起點的x和y以及終點的x和y,繪制一條線段。

  2. drawLines方法接收一個float數組pts,需要注意的是在用drawLines繪圖時,其每次從pts數組中取出四個點繪制一條線段,然後再取出後面四個點繪制一條線段,所以要求pts的長度需要是4的倍數。假設我們有四個點,分別是p1、p2、p3、p4,我們依次將其坐標放到pts數組中,即pts = {p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y},那麼用drawLines繪制pts時,你會發現p1和p2之間畫了一條線段,p3和p4之間畫了一條線段,但是p2和p3之間沒有畫線段,這樣大家就應該能明白drawLines每次都需要從pts數組中取出4個值繪制一條線段的意思了。

  3. 通過調用Paint的setStrokeWidth方法設置線的寬度。

  4. 上面在講drawPoint時提到了strokeCap對所繪制點的形狀的影響,通過drawLine繪制的線段也受其影響,體現在繪制的線段的兩個端點的形狀上。
  • Paint.Cap.BUTT
    當用BUTT作為帽端時,所繪制的線段恰好在起點終點位置處戛然而止,兩端是方形,上圖中第一條加粗的線段就是用BUTT作為帽端繪制的。

  • Paint.Cap.ROUND
    當用ROUND作為帽端時,所繪制的線段的兩端端點會超出起點和終點一點距離,並且兩端是圓形狀,上圖中第二條加粗的線段就是用ROUND作為帽端繪制的。

  • Paint.Cap.SQUARE
  • 當用SQUARE作為帽端時,所繪制的線段的兩端端點也會超出起點和終點一點距離,兩端點的形狀是方形,上圖中最後一條加粗的線段就是用SQUARE作為帽端繪制的。

drawRect

Canvas通過drawRect方法繪制矩形,使用代碼如下所示:

private void drawRect(Canvas canvas){
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();

        //默認畫筆的填充色是黑色
        int left1 = 10;
        int top1 = 10;
        int right1 = canvasWidth / 3;
        int bottom1 = canvasHeight /3;
        canvas.drawRect(left1, top1, right1, bottom1, paint);

        //修改畫筆顏色
        paint.setColor(0xff8bc5ba);//A:ff,R:8b,G:c5,B:ba
        int left2 = canvasWidth / 3 * 2;
        int top2 = 10;
        int right2 = canvasWidth - 10;
        int bottom2 = canvasHeight / 3;
        canvas.drawRect(left2, top2, right2, bottom2, paint);
    }

界面如下所示:
這裡寫圖片描述

其方法簽名是drawRect(float left, float top, float right, float bottom, Paint paint),left和right表示矩形的左邊和右邊分別到繪圖坐標系y軸正半軸的距離,top和bottom表示矩形的上邊和下邊分別到繪圖坐標系x軸正半軸的距離。


drawCircle

Canvas中用drawCircle方法繪制圓形,使用代碼如下所示:

private void drawCircle(Canvas canvas){
        paint.setColor(0xff8bc5ba);//設置顏色
        paint.setStyle(Paint.Style.FILL);//默認繪圖為填充模式
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        int halfCanvasWidth = canvasWidth / 2;
        int count = 3;
        int D = canvasHeight / (count + 1);
        int R = D / 2;

        //繪制圓
        canvas.translate(0, D / (count + 1));
        canvas.drawCircle(halfCanvasWidth, R, R, paint);

        //通過繪制兩個圓形成圓環
        //1. 首先繪制大圓
        canvas.translate(0, D + D / (count + 1));
        canvas.drawCircle(halfCanvasWidth, R, R, paint);
        //2. 然後繪制小圓,讓小圓覆蓋大圓,形成圓環效果
        int r = (int)(R * 0.75);
        paint.setColor(0xffffffff);//將畫筆設置為白色,畫小圓
        canvas.drawCircle(halfCanvasWidth, R, r, paint);

        //通過線條繪圖模式繪制圓環
        canvas.translate(0, D + D / (count + 1));
        paint.setColor(0xff8bc5ba);//設置顏色
        paint.setStyle(Paint.Style.STROKE);//繪圖為線條模式
        float strokeWidth = (float)(R * 0.25);
        paint.setStrokeWidth(strokeWidth);
        canvas.drawCircle(halfCanvasWidth, R, R, paint);
    }

界面如下所示:
這裡寫圖片描述

下面對以上代碼進行說明:

  1. 其方法簽名是drawCircle (float cx, float cy, float radius, Paint paint),在使用時需要傳入圓心的坐標以及半徑,當然還有畫筆Paint對象。

  2. 當我們在調用drawCircle、drawOval、drawArc、drawRect等方法時,我們既可以繪制對應圖形的填充面,也可以只繪制該圖形的輪廓線,控制的關鍵在於畫筆Paint中的style。Paint通過setStyle方法設置要繪制的類型,style有取三種值:Paint.Style.FILL、Paint.Style.STROKE和Paint.Style.FILL_AND_STROKE。
  • 當style為FILL時,繪制是填充面,FILL是Paint默認的style;
  • 當style為STROKE時,繪制的是圖形的輪廓線;
  • 當style為FILL_AND_STROKE時,同時繪制填充面和輪廓線,不過這種情況用的不多,因為填充面和輪廓線是用同一種顏色繪制的,區分不出輪廓線的效果。
  • 在Paint的style是FILL時,我們通過drawCircle繪制出圓面,如上圖中的第一個圖形所示。

  • 我們可以通過繪制兩個圓面的方式繪制出圓環的效果。首先將畫筆設置為某一顏色,且style設置為FILL狀態,通過drawCircle繪制一個大的圓面;然後將畫筆Paint的顏色改為白色或其他顏色,並減小半徑再次通過drawCircle繪制一個小圓,這樣就用小圓遮蓋了大圓的一部分,未遮蓋的部分便自然形成了圓環的效果,如上圖中的第二個圖形所示。

  • 除了上述方法,我們還有一種辦法繪制圓環的效果。我們首先將畫筆Paint的style設置為STROKE模式,表示畫筆處於畫線條模式,而非填充模式。然後為了讓圓環比較明顯有一定的寬度,我們需要調用Paint的setStrokeWidth方法設置線寬。最後調用drawCircle方法繪制出寬度比較大的圓的輪廓線,也就形成了圓環效果,如上圖中的最後一個圖形所示。此處需要說明的是,當我們用STROKE模式畫圓時,輪廓線是以實際圓的邊界為分界線分別向內向外擴充1/2的線寬的距離,比如圓的半徑是100,線寬是20,那麼在STROKE模式下繪制出的圓環效果相當於半徑為110的大圓和半徑為90的小圓形成的效果,100 + 20 / 2 = 110, 100 - 20/2 = 90。

drawOval

Canvas中提供了drawOval方法繪制橢圓,其使用代碼如下所示:

private void drawOval(Canvas canvas){
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        float quarter = canvasHeight / 4;
        float left = 10 * density;
        float top = 0;
        float right = canvasWidth - left;
        float bottom= quarter;
        RectF rectF = new RectF(left, top, right, bottom);

        //繪制橢圓形輪廓線
        paint.setStyle(Paint.Style.STROKE);//設置畫筆為畫線條模式
        paint.setStrokeWidth(2 * density);//設置線寬
        paint.setColor(0xff8bc5ba);//設置線條顏色
        canvas.translate(0, quarter / 4);
        canvas.drawOval(rectF, paint);

        //繪制橢圓形填充面
        paint.setStyle(Paint.Style.FILL);//設置畫筆為填充模式
        canvas.translate(0, (quarter + quarter / 4));
        canvas.drawOval(rectF, paint);

        //畫兩個橢圓,形成輪廓線和填充色不同的效果
        canvas.translate(0, (quarter + quarter / 4));
        //1. 首先繪制填充色
        paint.setStyle(Paint.Style.FILL);//設置畫筆為填充模式
        canvas.drawOval(rectF, paint);//繪制橢圓形的填充效果
        //2. 將線條顏色設置為藍色,繪制輪廓線
        paint.setStyle(Paint.Style.STROKE);//設置畫筆為線條模式
        paint.setColor(0xff0000ff);//設置填充色為藍色
        canvas.drawOval(rectF, paint);//設置橢圓的輪廓線
    }

其界面如下所示:
這裡寫圖片描述

下面對以上代碼進行說明:

  1. 其方法簽名是public void drawOval (RectF oval, Paint paint),RectF有四個字段,分別是left、top、right、bottom,

這四個值對應了橢圓的左、上、右、下四個點到相應坐標軸的距離,具體來說,left和right表示橢圓的最左側的點和最右側的點到繪圖坐標系的y軸的距離,top和bottom表示橢圓的最頂部的點和最底部的點到繪圖坐標系的x軸的距離,這四個值就決定了橢圓的形狀,right與left的差值即為橢圓的長軸,bottom與top的差值即為橢圓的短軸,如下圖所示:
這裡寫圖片描述

  1. 通過Paint的setStyle方法將畫筆的style設置成STROKE,即畫線條模式,這種情況下,用畫筆畫出來的是橢圓的輪廓線,而非填充面,如上圖中的第一個圖形所示。

  2. 當將畫筆Paint的style設置為FILL時,即填充模式,這種情況下,用畫筆畫出來的是橢圓的填充面,如上圖中的第二個圖形所示。

  3. 如果我們想繪制帶有其他顏色輪廓線的橢圓面,我們需要繪制兩個橢圓。首先以FILL模式畫一個橢圓的填充面,然後更改畫筆顏色,以STROKE模式畫橢圓的輪廓線,如上圖中的最後一個圖形所示。這樣從外觀上看,好像是橢圓面與橢圓的輪廓線顏色不同。

drawArc

Canvas中提供了drawArc方法用於繪制弧,這裡的弧指兩種:弧面和弧線,弧面即用弧圍成的填充面,弧線即為弧面的輪廓線。其使用代碼如下所示:

private void drawArc(Canvas canvas){
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        int count = 5;
        float ovalHeight = canvasHeight / (count + 1);
        float left = 10 * density;
        float top = 0;
        float right = canvasWidth - left;
        float bottom= ovalHeight;
        RectF rectF = new RectF(left, top, right, bottom);

        paint.setStrokeWidth(2 * density);//設置線寬
        paint.setColor(0xff8bc5ba);//設置顏色
        paint.setStyle(Paint.Style.FILL);//默認設置畫筆為填充模式

        //繪制用drawArc繪制完整的橢圓
        canvas.translate(0, ovalHeight / count);
        canvas.drawArc(rectF, 0, 360, true, paint);

        //繪制橢圓的四分之一,起點是鐘表的3點位置,從3點繪制到6點的位置
        canvas.translate(0, (ovalHeight + ovalHeight / count));
        canvas.drawArc(rectF, 0, 90, true, paint);

        //繪制橢圓的四分之一,將useCenter設置為false
        canvas.translate(0, (ovalHeight + ovalHeight / count));
        canvas.drawArc(rectF, 0, 90, false, paint);

        //繪制橢圓的四分之一,只繪制輪廓線
        paint.setStyle(Paint.Style.STROKE);//設置畫筆為線條模式
        canvas.translate(0, (ovalHeight + ovalHeight / count));
        canvas.drawArc(rectF, 0, 90, true, paint);

        //繪制帶有輪廓線的橢圓的四分之一
        //1. 先繪制橢圓的填充部分
        paint.setStyle(Paint.Style.FILL);//設置畫筆為填充模式
        canvas.translate(0, (ovalHeight + ovalHeight / count));
        canvas.drawArc(rectF, 0, 90, true, paint);
        //2. 再繪制橢圓的輪廓線部分
        paint.setStyle(Paint.Style.STROKE);//設置畫筆為線條模式
        paint.setColor(0xff0000ff);//設置輪廓線條為藍色
        canvas.drawArc(rectF, 0, 90, true, paint);
    }

界面如下所示:
這裡寫圖片描述

下面對以上代碼進行說明:

用drawArc畫的弧指的是橢圓弧,即橢圓的一部分。當然,如果橢圓的長軸和和短軸相等,這時候我們就可以用drawArc方法繪制圓弧。其方法簽名是:

public void drawArc (RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

  • oval是RecF類型的對象,其定義了橢圓的形狀。
  • startAngle指的是繪制的起始角度,鐘表的3點位置對應著0度,如果傳入的startAngle小於0或者大於等於360,那麼用startAngle對360進行取模後作為起始繪制角度。
  • sweepAngle指的是從startAngle開始沿著鐘表的順時針方向旋轉掃過的角度。如果sweepAngle大於等於360,那麼會繪制完整的橢圓弧。如果sweepAngle小於0,那麼會用sweepAngle對360進行取模後作為掃過的角度。
  • useCenter是個boolean值,如果為true,表示在繪制完弧之後,用橢圓的中心點連接弧上的起點和終點以閉合弧;如果值為false,表示在繪制完弧之後,弧的起點和終點直接連接,不經過橢圓的中心點。
  • 在代碼中我們一開始設置的Paint的style為FILL,即填充模式。通過上面的描述我們知道,drawOval方法可以看做是drawArc方法的一種特例。如果在drawArc方法中sweepAngle為360,無論startAngle為多少,drawArc都會繪制一個橢圓,如上圖中第一個圖形,我們用canvas.drawArc(rectF, 0, 360, true, paint)繪制了一個完整的橢圓,就像用drawOval畫出的那樣。

  • 當我們調用方法canvas.drawArc(rectF, 0, 90, true, paint)時, 我們指定了起始角度為0,然後順時針繪制90度,即我們會繪制從3點到6點這90度的弧,如上圖中第二個圖形所示,我們繪制了一個橢圓的右下角的四分之一的弧面,需要注意的是我們此處設置的useCenter為true,所以弧上的起點(3點位置)和終點(6點位置)都和橢圓的中心連接了形成了。

  • 當我們調用方法canvas.drawArc(rectF, 0, 90, false, paint)時,我們還是繪制橢圓右下角的弧面,不過這次我們將useCenter設置成了false,如上圖中的第三個圖形所示,弧上的起點(3點位置)和終點(6點位置)直接相連閉合了,而沒有經過橢圓的中心點。

  • 上面介紹到的繪圖都是在畫筆Paint處於FILL狀態下繪制的。我們可以通過paint.setStyle(Paint.Style.STROKE)方法將畫筆的style改為STROKE,即繪制線條模式。然後我們再次執行canvas.drawArc(rectF, 0, 90, true, paint),初始角度為0,掃過90度的區域,useCenter為true,繪制的效果見上圖中第四個圖形,此時我們只繪制了橢圓的輪廓線。需要注意的,由於Paint默認的線寬為0,所以在繪制之前要確保掉用過Paint.setStrokeWidth()方法以設置畫筆的線寬。

  • 如果我們想繪制出帶有其他顏色輪廓線的弧面時,該怎麼辦呢?我們可以分兩步完成:首先,將畫筆Paint的style設置為FILL模式,通過drawArc方法繪制出弧面。然後,將畫筆Paint的style設置為STROKE模式,並通過paint的setColor()方法改變畫筆的顏色,最後drawArc方法繪制出弧線。這樣我們就能繪制出帶有其他顏色輪廓線的弧面了,如上圖中最後一個圖形所示。

drawPath

Canvas通過drawPath方法可以繪制Path。那Path是什麼呢?Path致以過來是路徑的意思,在Android中,Path是一種線條的組合圖形,其可以由直線、二次曲線、三次曲線、橢圓的弧等組成。Path既可以畫線條,也可以畫填充面。其使用代碼如下所示:

private void drawPath(Canvas canvas){
        int canvasWidth = canvas.getWidth();
        int deltaX = canvasWidth / 4;
        int deltaY = (int)(deltaX * 0.75);

        paint.setColor(0xff8bc5ba);//設置畫筆顏色
        paint.setStrokeWidth(4);//設置線寬

        /*--------------------------用Path畫填充面-----------------------------*/
        paint.setStyle(Paint.Style.FILL);//設置畫筆為填充模式
        Path path = new Path();
        //向Path中加入Arc
        RectF arcRecF = new RectF(0, 0, deltaX, deltaY);
        path.addArc(arcRecF, 0, 135);
        //向Path中加入Oval
        RectF ovalRecF = new RectF(deltaX, 0, deltaX * 2, deltaY);
        path.addOval(ovalRecF, Path.Direction.CCW);
        //向Path中添加Circle
        path.addCircle((float)(deltaX * 2.5), deltaY / 2, deltaY / 2, Path.Direction.CCW);
        //向Path中添加Rect
        RectF rectF = new RectF(deltaX * 3, 0, deltaX * 4, deltaY);
        path.addRect(rectF, Path.Direction.CCW);
        canvas.drawPath(path, paint);

        /*--------------------------用Path畫線--------------------------------*/
        paint.setStyle(Paint.Style.STROKE);//設置畫筆為線條模式
        canvas.translate(0, deltaY * 2);
        Path path2 = path;
        canvas.drawPath(path2, paint);

        /*-----------------使用lineTo、arcTo、quadTo、cubicTo畫線--------------*/
        paint.setStyle(Paint.Style.STROKE);//設置畫筆為線條模式
        canvas.translate(0, deltaY * 2);
        Path path3 = new Path();
        //用pointList記錄不同的path的各處的連接點
        List pointList = new ArrayList();
        //1. 第一部分,繪制線段
        path3.moveTo(0, 0);
        path3.lineTo(deltaX / 2, 0);//繪制線段
        pointList.add(new Point(0, 0));
        pointList.add(new Point(deltaX / 2, 0));
        //2. 第二部分,繪制橢圓右上角的四分之一的弧線
        RectF arcRecF1 = new RectF(0, 0, deltaX, deltaY);
        path3.arcTo(arcRecF1, 270, 90);//繪制圓弧
        pointList.add(new Point(deltaX, deltaY / 2));
        //3. 第三部分,繪制橢圓左下角的四分之一的弧線
        //注意,我們此處調用了path的moveTo方法,將畫筆的移動到我們下一處要繪制arc的起點上
        path3.moveTo(deltaX * 1.5f, deltaY);
        RectF arcRecF2 = new RectF(deltaX, 0, deltaX * 2, deltaY);
        path3.arcTo(arcRecF2, 90, 90);//繪制圓弧
        pointList.add(new Point((int)(deltaX * 1.5), deltaY));
        //4. 第四部分,繪制二階貝塞爾曲線
        //二階貝塞爾曲線的起點就是當前畫筆的位置,然後需要添加一個控制點,以及一個終點
        //再次通過調用path的moveTo方法,移動畫筆
        path3.moveTo(deltaX * 1.5f, deltaY);
        //繪制二階貝塞爾曲線
        path3.quadTo(deltaX * 2, 0, deltaX * 2.5f, deltaY / 2);
        pointList.add(new Point((int)(deltaX * 2.5), deltaY / 2));
        //5. 第五部分,繪制三階貝塞爾曲線,三階貝塞爾曲線的起點也是當前畫筆的位置
        //其需要兩個控制點,即比二階貝賽爾曲線多一個控制點,最後也需要一個終點
        //再次通過調用path的moveTo方法,移動畫筆
        path3.moveTo(deltaX * 2.5f, deltaY / 2);
        //繪制三階貝塞爾曲線
        path3.cubicTo(deltaX * 3, 0, deltaX * 3.5f, 0, deltaX * 4, deltaY);
        pointList.add(new Point(deltaX * 4, deltaY));

        //Path准備就緒後,真正將Path繪制到Canvas上
        canvas.drawPath(path3, paint);

        //最後繪制Path的連接點,方便我們大家對比觀察
        paint.setStrokeWidth(10);//將點的strokeWidth要設置的比畫path時要大
        paint.setStrokeCap(Paint.Cap.ROUND);//將點設置為圓點狀
        paint.setColor(0xff0000ff);//設置圓點為藍色
        for(Point p : pointList){
            //遍歷pointList,繪制連接點
            canvas.drawPoint(p.x, p.y, paint);
        }
    }

界面如下所示:
這裡寫圖片描述

下面對以上代碼進行說明:

  1. Canvas的drawPath()方法接收Path和Paint兩個參數。當Paint的style是FILL時,我們可以用darwPath來畫填充面。Path類提供了addArc、addOval、addCircle、addRect等方法,可以通過這些方法可以向Path添加各種閉合圖形,Path甚至還提供了addPath方法讓我們將一個Path對象添加到另一個Path對象中作為其一部分。當我們通過Path的addXXX方法向Path中添加了各種圖形後,我們就可以調用canvas.drawPath(path, paint)繪制出Path了,如上圖中第一行中的幾個圖形所示。

  2. 我們可以通過調用Paint的setStyle()方法將畫筆Paint設置為STROKE,即線條模式, 然後我們再次執行canvas.darwPath()方法繪制同一個Path對象,我們這次繪制的就只是Path的輪廓線了,如上圖中第二行中的幾個圖形所示。

  3. Path對象還有很多xxTo方法,比如lineTo、arcTo、quadTo、cubicTo等,通過這些方法,我們可以方便的從畫筆位置繪制到指定坐標的連續線條,如上圖中最後一行的幾個線狀圖形所示。我們用了lineTo、arcTo、quadTo、cubicTo這四種方法畫了五段線條,下面會解釋,並且單獨通過調用drawPoint畫出了每段線條的兩個端點,方便大家觀察。
  • moveTo方法用於設置下一個線條的起始點,可以認為是移動了畫筆,但說移動畫筆不嚴格,後面會解釋,此處大家暫且這麼理解。

  • lineTo的方法簽名是public void lineTo (float x, float y),Path的lineTo方法會從當前畫筆的位置到我們指定的坐標構建一條線段,然後將其添加到Path對象中,如上圖中最後一行圖形中的第一條線段所示。

  • arcTo的方法簽名是public void arcTo (RectF oval, float startAngle, float sweepAngle),oval、startAngle與sweepAngle的參數與之前提到的darwArc方法對應的形參意義相同,在此不再贅述。Path的arcTo方法會構建一條弧線並添加到Path對象中,如上圖中最後一行圖形中的第二條和第三條線狀圖形所示,這兩條弧線都是通過Path的arcTo方法添加的。

  • quadTo是用來畫二階貝塞爾曲線的,即拋物線,其方法簽名是public void quadTo (float x1, float y1, float x2, float y2),如果對貝塞爾曲線的相關概念不了解,推薦大家讀一下博文《貝塞爾曲線初探》。下面借用該博文中的一張圖說一下二階貝塞爾曲線:
    這裡寫圖片描述
    二階貝塞爾曲線的繪制一共需要三個點,一個起點,一個終點,還要有一個中間的控制點。我們畫筆的位置就相當於上圖中P0的位置,quadTo中的前兩個參數x1和y1指定了控制點P1的坐標,後面兩個參數x2和y2指定了終點P2的坐標。上圖中最後一行的第四個線狀圖形就是用quadTo繪制的二階貝塞爾曲線。

  • cubicTo跟quadTo類似,不過是用來畫三階貝塞爾曲線的,其方法簽名是public void cubicTo (float x1, float y1, float x2, float y2, float x3, float y3)。我們還是借用一下上述博文《貝塞爾曲線初探》中的另一張圖片來解釋一下三階貝塞爾曲線:
    這裡寫圖片描述
    三階貝塞爾曲線的繪制需要四個點,一個起點,一個終點,以及兩個中間的控制點,也就是說它比二階貝塞爾曲線要多一個控制點。我們畫筆的位置就相當於上圖中P0的位置,cubicTo中的前兩個參數x1和y1指定了第一個控制點P1的坐標,參數x2和y2指定了第二個控制點P2的坐標,最後兩個參數x3和y3指定了終點P3的坐標。上圖中最後一行的最後一個線狀圖形就是用cubicTo繪制的三階貝塞爾曲線。

  • 上面提到Path的moveTo方法移動了畫筆的位置,這樣說不准確,因為Path和Paint沒有任何關系,准確的說法是移動了Path的當前點,當我們調用lineTo、arcTo、quadTo、cubicTo等方法時,首先要從當前點開始繪制。對於lineTo、quadTo、cubicTo這三個方法來說,Path的當前點作為了這三個方法繪制的線條中的起始點,但是對於arcTo方法來說卻不同。當我們調用arcTo方法時,首先會從Path的當前點畫一條直線到我們所畫弧的起始點,所以在使用Path的arcTo方法前要注意通過調用Path的moveTo方法使當前點與所畫弧的起點重合,否則有可能你就會看到多了一條當前點到弧的起點的線段。moveTo可以移動當前點,當調用了lineTo、arcTo、quadTo、cubicTo等方法時,當前點也會移動,當前點就變成了所繪制的線條的最後一個點。

  • 上面提到了moveTo、lineTo、arcTo、quadTo、cubicTo的方法中傳入的坐標都是繪圖坐標系中的坐標,即繪圖坐標系中的絕對坐標。其實我們可以用相對坐標調用這些類型功能的方法。Path因此提供了對應的rMoveTo、rLineTo、rQuadTo、rCubicTo方法,其形參列表與對應的方法相同,只不過裡面傳入的坐標不是相對於當前點的相對坐標,即傳入的坐標是相對於當前點的偏移值。

  • lineTo、arcTo、quadTo、cubicTo等方法只是向Path中添加相應的線條,只有執行了canvas.drawPath(path3, paint)方法時,我們才能將Path繪制到Canvas上。

drawBitmap

Canvas中提供了drawBitmap方法用於繪制Bitmap,其使用代碼如下所示:


private void drawBitmap(Canvas canvas){
        //如果bitmap不存在,那麼就不執行下面的繪制代碼
        if(bitmap == null){
            return;
        }

        //直接完全繪制Bitmap
        canvas.drawBitmap(bitmap, 0, 0, paint);

        //繪制Bitmap的一部分,並對其拉伸
        //srcRect定義了要繪制Bitmap的哪一部分
        Rect srcRect = new Rect();
        srcRect.left = 0;
        srcRect.right = bitmap.getWidth();
        srcRect.top = 0;
        srcRect.bottom = (int)(0.33 * bitmap.getHeight());
        float radio = (float)(srcRect.bottom - srcRect.top)  / bitmap.getWidth();
        //dstRecF定義了要將繪制的Bitmap拉伸到哪裡
        RectF dstRecF = new RectF();
        dstRecF.left = 0;
        dstRecF.right = canvas.getWidth();
        dstRecF.top = bitmap.getHeight();
        float dstHeight = (dstRecF.right - dstRecF.left) * radio;
        dstRecF.bottom = dstRecF.top + dstHeight;
        canvas.drawBitmap(bitmap, srcRect, dstRecF, paint);
    }

界面如下所示:
這裡寫圖片描述

我在res/drawable目錄下放置了一張android的圖片,下面對上面的代碼進行說明:

Canvas的drawBitmap有多個重載方法,最簡單的方法簽名是:

public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint)

該方法除了傳入bitmap對象外,還需要傳入left和top,left和top組成了一個坐標,決定了在Canvas中從哪個地方繪制Bitmap。在我們的代碼中,left和top都設置為0,所以我們就在Canvas的左上角繪制了bitmap。

drawBitmap還有一個比較實用的方法,其方法簽名是:

public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)

該方法有兩個功能:1.只繪制原有bitmap對象的一部分,2.還可以將要繪制的bitmap縮放到指定的區域。

只繪制原有bitmap對象的一部分
我們知道Bitmap是一個矩形,其是有寬度和高度的,也就說以bitmap對象本身作為坐標系(原點在bitmap左上角),我們可以構建一個Rect對象,如果滿足left為0,top為0,right為bitmap的寬度,bottom為bitmap的高度,那麼就說名我們要繪制整個Bitmap。但是有時候我們只想繪制Bitmap的一部分,例如我們上面的圖中所示,我們想只繪制Android圖像的頭部區域怎麼辦呢?辦法是我們構建一個Rect對象,定義我們要繪制Bitmap的哪些部位。
比如我們通過代碼srcRect.bottom = (int)(0.33 * bitmap.getHeight())指定了我們只繪制bitmap對象頭部1/3的位置,即Android圖像的頭部,這樣我們用該指定的srcRect繪制bitmap時只繪制了其頭部位置。需要特別注意的是,srcRect中left、top、right、bottom的值都是以Bitmap本身的局部坐標系為基礎的。

將要繪制的bitmap縮放到指定的區域
有時候我們需要將原有的bitmap進行放大或縮小,如上圖所示,我們將原有圖片放大了,這怎麼做呢?我們需要指定RectF類型的參數dstRectF,以便告訴Android將srcRect中定義的bitmap縮放到哪裡。即Android會將srcRect中定義的bitmap縮放到dstRectF區域范圍內。需要注意的是,此處的dstRecF是繪圖坐標系中的坐標,不是Bitmap本身的局部坐標系。我們在代碼中保證了dstRecF的長寬比與srcRect中的長寬比相同,這樣不會導致圖片長寬比例變形,效果見上圖中的第二個放大的圖形。

此處有一點需要說明,在繪圖結束退出Activity的時候,我們需要調用bitmap的recyle()方法,防止內存洩露,本程序在onDestroy()方法中執行了該方法。


總結

Canvas通過drawXXX等一些列的繪圖方法決定了要繪制的圖形的外形,我們可以通過自由組合繪制出我們想要的效果。drawXXX方法中的坐標都是基於當前繪圖坐標系的坐標,而非Canvas坐標系,默認情況下二者重合。通過調用translate、rotate、scale等方法可以對繪圖坐標系進行變換。

畫筆Paint控制著所繪制的圖形的具體外觀,Paint默認的字體大小為12px,在繪制文本時我們往往要考慮密度density設置合適的字體大小。畫筆的默認顏色為黑色,默認的style為FILL,默認的cap為BUTT,默認的線寬為0,參見下圖所示:
這裡寫圖片描述

在畫面狀的圖形時,如果Paint的style是FILL,那麼繪制的就是填充面;如果是STROKE,那麼繪制的就是輪廓線。

 


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved