編輯:關於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坐標系有且只有一個,且是唯一不變的,其坐標原點在View的左上角,從坐標原點向右為x軸的正半軸,從坐標原點向下為y軸的正半軸。
為了更好的理解繪圖坐標系,請看如下的代碼:
//繪制坐標系 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); }
界面如下所示:
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(); }
界面如下所示:
對以上代碼進行一下說明:
Android中的畫筆有兩種Paint和TextPaint,我們可以Paint來畫其他的圖形:點、線、矩形、橢圓等。TextPaint繼承自Paint,是專門用來畫文本的,由於TextPaint繼承自Paint,所以也可以用TextPaint畫點、線、面、矩形、橢圓等圖形。
我們在上面的代碼中將canvas.translate()和canvas.rotate()放到了canvas.save()和canvas.restore()之間,這樣做的好處是,在canvas.save()調用時,將當前坐標系保存下來,將當前坐標系的矩陣Matrix入棧保存,然後通過translate或rotate等對坐標系進行變換,然後進行繪圖,繪圖完成後,我們通過調用canvas.restore()將之前保存的Matrix出棧,這樣就將當前繪圖坐標系恢復到了canvas.save()執行的時候狀態。如果熟悉OpenGL開發,對這種模式應該很了解。
通過調用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)的方式更簡潔。
通過調用paint.setTextAlign()設置文本的對齊方式,該對齊方式是相對於繪制文本時的畫筆的坐標來說的,在本例中,我們繪制文本時畫筆在Canvas寬度的中間。在drawText()方法執行時,需要傳入一個x和y坐標,假設該點為P點,P點表示我們從P點繪制文本。當對齊方式為Paint.Align.LEFT時,繪制的文本以P點為基准向左對齊,這是默認的對齊方式;當對齊方式為Paint.Align.CENTER時,繪制的文本以P點為基准居中對齊;當對齊方式為Paint.Align.RIGHT時,繪制的文本以P點為基准向右對齊。
通過調用paint.setUnderlineText(true)繪制帶有下劃線的文本。
通過調用paint.setFakeBoldText(true)繪制粗體文本。
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); }
界面如下所示:
下面對以上代碼進行說明:
Paint的setStrokeWidth方法可以控制所畫線的寬度,通過Paint的getStrokeWidth方法可以得到所畫線的寬度,默認情況下,線寬是0。其實strokeWidth不僅對畫線有影響,對畫點也有影響,由於默認的線寬是0,所以默認情況下調用drawPoint方法無法在Canvas上畫出點,為了讓大家清楚地看到所畫的點,我用Paint的setStrokeWidth設置了一個比較大的線寬,這樣我們看到的點也就比較大。
Paint有個setStrokeCap方法可以設置所畫線段的時候兩個端點的形狀,即所畫線段的帽端的形狀,在下面講到drawLine方法時會詳細說明,其實setStrokeCap方法也會影響所畫點的形狀。Paint的setStrokeCap方法可以有三個取值:Paint.Cap.BUTT、Paint.Cap.ROUND和Paint.Cap.SQUARE。
默認情況下Paint的getStrokeCap的返回值是Paint.Cap.BUTT,默認畫出來的點就是一個正方形,上圖第一個點即是用BUTT作為帽端畫的。
我們可以調用setStrokeCap方法設置Paint的strokeCap為Paint.Cap.ROUND時,畫筆畫出來的點就是一個圓形,上圖第二個點即是用ROUND作為帽端畫的。
Canvas通過drawLine方法繪制一條線段,通過drawLines方法繪制多段線,使用代碼如下所示:
下面對以上代碼進行說明:
drawLine方法接收四個數值,即起點的x和y以及終點的x和y,繪制一條線段。
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個值繪制一條線段的意思了。
通過調用Paint的setStrokeWidth方法設置線的寬度。
Paint.Cap.BUTT
當用BUTT作為帽端時,所繪制的線段恰好在起點終點位置處戛然而止,兩端是方形,上圖中第一條加粗的線段就是用BUTT作為帽端繪制的。
Paint.Cap.ROUND
當用ROUND作為帽端時,所繪制的線段的兩端端點會超出起點和終點一點距離,並且兩端是圓形狀,上圖中第二條加粗的線段就是用ROUND作為帽端繪制的。
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軸正半軸的距離。
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); }
界面如下所示:
下面對以上代碼進行說明:
其方法簽名是drawCircle (float cx, float cy, float radius, Paint paint),在使用時需要傳入圓心的坐標以及半徑,當然還有畫筆Paint對象。
在Paint的style是FILL時,我們通過drawCircle繪制出圓面,如上圖中的第一個圖形所示。
我們可以通過繪制兩個圓面的方式繪制出圓環的效果。首先將畫筆設置為某一顏色,且style設置為FILL狀態,通過drawCircle繪制一個大的圓面;然後將畫筆Paint的顏色改為白色或其他顏色,並減小半徑再次通過drawCircle繪制一個小圓,這樣就用小圓遮蓋了大圓的一部分,未遮蓋的部分便自然形成了圓環的效果,如上圖中的第二個圖形所示。
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);//設置橢圓的輪廓線 }
其界面如下所示:
下面對以上代碼進行說明:
這四個值對應了橢圓的左、上、右、下四個點到相應坐標軸的距離,具體來說,left和right表示橢圓的最左側的點和最右側的點到繪圖坐標系的y軸的距離,top和bottom表示橢圓的最頂部的點和最底部的點到繪圖坐標系的x軸的距離,這四個值就決定了橢圓的形狀,right與left的差值即為橢圓的長軸,bottom與top的差值即為橢圓的短軸,如下圖所示:
通過Paint的setStyle方法將畫筆的style設置成STROKE,即畫線條模式,這種情況下,用畫筆畫出來的是橢圓的輪廓線,而非填充面,如上圖中的第一個圖形所示。
當將畫筆Paint的style設置為FILL時,即填充模式,這種情況下,用畫筆畫出來的是橢圓的填充面,如上圖中的第二個圖形所示。
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);
}
}
界面如下所示:
下面對以上代碼進行說明:
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了,如上圖中第一行中的幾個圖形所示。
我們可以通過調用Paint的setStyle()方法將畫筆Paint設置為STROKE,即線條模式, 然後我們再次執行canvas.darwPath()方法繪制同一個Path對象,我們這次繪制的就只是Path的輪廓線了,如上圖中第二行中的幾個圖形所示。
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,那麼繪制的就是輪廓線。
本節引言: 本節主要介紹的是Android系統服務中的---AlarmManager(鬧鐘服務), 除了開發手機鬧鐘外,更多的時候是作為一個全
什麼是View?實現View滑動的方式有哪些?1. 關於View我們需要知道的(1)什麼是View? Android中的View類是所
首先還是xml布局文件,在其中添加ListView控件:主布局layout_main.xml復制代碼 代碼如下:<RelativeLayout xmlns:andr
這篇博客我們來介紹一下橋接模式(Bridge Pattern),它也是結構型設計模式之一。橋接,顧名思義,就是用來連接兩個部分,使得兩個部分可以互相通訊或者使用,橋接模式