Android自定義View之DashBoard(儀表盤)
前言
Android自定義View是Android初中級開發工程師向高階工程師進階所必須掌握的一塊內容,其重要性不言而喻。接下來的一段時間,我會連續出幾篇跟自定義View相關的文章,從易到難,跟大家一起學習Android自定義View。本文講一個Android很簡單的View——DashBoard(儀表盤),以這個例子帶大家去學習自定義View的基本繪製,讓大家學會自定義View,並最終掌握。
注:本文的Demo在文章的最後
必須要掌握的幾個點
在開始我們的繪製DashBoard之前,有幾個點是必須要掌握的,這些是繪製的基礎,也是前提。
Paint
自定義View的過程就是一個繪製的過程,而繪製就好像我們畫畫一樣,而畫畫就必須要會畫筆,Paint就是我們的畫筆。
- Paint 類的幾個最常用的方法。具體是:
- Paint.setStyle(Style style) 設定繪製模式
- Paint.setColor(int color) 設定顏色
- Paint.setStrokeWidth(float width) 設定線條寬度
- Paint.setTextSize(float textSize) 設定文字大小
- Paint.setAntiAlias(boolean aa) 設定抗鋸齒開關
這裡重點講一下Paint.setStyle(Style style)方法,這個方法設定的是繪製的 Style 。Style 具體來說有三種: FILL, STROKE 和 FILL_AND_STROKE 。FILL 是填充模式,STROKE 是畫線模式(即勾邊模式),FILL_AND_STROKE 是兩種模式一併使用:既畫線又填充。它的預設值是 FILL,填充模式。只有當Style是STROKE 和 FILL_AND_STROKE時,Paint.setStrokeWidth(float width)才有意義,你全是填充的就不涉及什麼線條寬度了。
canvas
Paint是畫筆,可畫畫光有畫筆還不行,還必須得有畫布,Canvas就是畫布。Canvas這個類是繪製最重要的類,沒有之一,幾乎所有繪製的方法都出自於這個類。
座標系
方法先不講,先講一下座標系,在Android 裡,每個View 都有一個自己的座標系,彼此之間是不影響的。這個座標系的原點是 View 左上角的那個點; 水平方向是 x 軸,右正左負;豎直方向是 y 軸,下正上負 (注意,是 下正上負 ,不是上正下負,和上學時候學的座標系方向不一樣也就是下面這個樣子。

座標系
這個座標非常重要,因為我們所有的繪製都是在這個座標系的基礎上開展的,而關於座標系還有這麼個兩個方法要特別注意:
- Canvas.rotate(float degrees)//旋轉座標系,正角度順時針,負角度逆時針
- Canvas.translate(float dx, float dy)
注意:以上兩個方法的操作的物件是 座標系 ,跟View本身沒有關係,之所以使用是為了讓我們更好、更方便地繪製View。
方法
Canvas最重要也最常用的方法都是drawXXX()方法,方法太多了,我不可能一一列舉,寫幾個最常用的,餘下的請自行google
- drawCircle(float centerX, float centerY, float radius, Paint paint) 畫圓
基本看引數名字就能猜出來是啥意思了,前面講了座標系的概念,前兩個引數就是圓心的X、Y座標了,第三個是半徑大小,最後一個是畫筆。 - drawRect(float left, float top, float right, float bottom, Paint paint) 畫矩形 (引數啥意思基本都能猜出來,不多講,不行還是google)
- drawOval(float left, float top, float right, float bottom, Paint paint) 畫橢圓
- drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 畫線
- drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 繪製弧形或扇形
left, top, right, bottom 描述的是這個弧形所在的橢圓;startAngle 是弧形的起始角度(x 軸的正向,即正右的方向,是 0 度的位置;順時針為正角度,逆時針為負角度),sweepAngle 是弧形劃過的角度;useCenter 表示是否連線到圓心,如果不連線到圓心,就是弧形,如果連線到圓心,就是扇形。 - drawPath(Path path, Paint paint) 畫自定義圖形
這裡 Path 物件要講一下,Path.addXxx()——新增子圖形,例如Path.addCircle(float x, float y, float radius, Direction dir) 新增圓;Path.xxxTo() ——畫線(直線或曲線)lineTo(float x, float y) / rLineTo(float x, float y) 畫直線.
小結
以上就是我們開始繪製DashBoard之前還需要掌握的基礎,因為都用得到。Paint是畫筆,主要就是設定畫筆相關的屬性,顏色、大小、風格等等;canvas是畫布,座標系的概念必須清楚,重要的幾個方法也必須知道。
DashBoard
先上個圖

DashBoard
看圖其實很簡單,基本上就分為三步,第一份畫弧;第二步畫刻度;第三步畫指標。
畫弧線
畫弧線之前,我簡單講一下自定義View的流程,建立一個DashBoard的型別繼承View,重寫構造方法和onDraw(Canvas canvas)
public class DashBoard extends View { public DashBoard(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initPaint();//初始化Paint } protected void onDraw(Canvas canvas) { super.onDraw(canvas); } }
初始化Paint
private void initPaint(){ mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);//抗鋸齒 mPaint.setStyle(Paint.Style.STROKE);//畫線模式 mPaint.setStrokeWidth(Utils.px2dp(2));//線寬度 mPaint.setColor(Color.BLACK); }
做好以上初始化工作,我們就開始第一步畫弧線。呼叫Canvas.drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)這個方法上面有介紹過,這裡就不詳細講了,前四個引數很好設定,我們定義圓心在View的中心位置,即(getWidth()/2,geHeight()/2),半徑150dp,那麼前四個引數就有了,第六個引數sweepAngle是劃過的度數,這個我們定義為240度,第七個是否連線中心,false,我們不需要連線中心,最後一個放自己的Paint就行了,現在的關鍵就是第五個引數,開始角度的計算,下面我出張圖幫助大家計算一下

startAngle角度分析
圖中畫得應該比較清楚了,不過多解釋,直接上程式碼
private void drawArc(Canvas canvas){ rectF = new RectF(getWidth() / 2 - RADIUS, getHeight() / 2 - RADIUS, getWidth() / 2 + RADIUS, getHeight() / 2 + RADIUS); canvas.drawArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE,false,mPaint); }
效果圖

效果圖
畫刻度
關於畫刻度,其實就是畫線嗎,那畫線的方法拿過來看一下,Canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint) ,需要線的起始點和結束點的座標,如果我要畫21個刻度,那需要21個點的刻度都算一遍,我去,誰可能這麼幹啊。放心,我們當然不會這麼幹了,下面提供兩種方式。
第一種方式
思路:座標系的旋轉+平移
之前在講Canvas裡提過,每一個Android的View都對應著有一個座標系,座標原點在View的左上角(可以看一下上面的那張圖),現在如果我們把座標原點平移到圓心的位置,並且再順時針旋轉30°,那麼當前的座標系就是下面這樣的

平移後的座標系
的橫座標就是(半徑-刻度線的長度)。下面我還是用一張圖來解釋一下

,用程式碼表達一下會更清晰。
private void drawDegree(Canvas canvas){ canvas.translate(getWidth()/2,getHeight()/2); canvas.rotate(30); for (int i=0;i<20;i++){ //Utils.px2dp(10)是刻度線的長度,為10dp canvas.drawLine(RADIUS-Utils.px2dp(10),0,RADIUS,0,mPaint); canvas.rotate(-SWEEPANGLE/20);//逆時針選擇 負值是逆時針 } //最後一根線 canvas.drawLine(RADIUS-Utils.px2dp(10),0,RADIUS,0,mPaint); canvas.rotate(240-30);//旋轉回去的角度 canvas.translate(-getWidth()/2,-getHeight()/2); }
畫完以後的效果圖

這個圖我故意沒有縮放,細心的你可能已經發現了第一個和最後一個刻度明顯有點不自然,我再放大一下

這下很明顯了吧,感覺好像是空了一半的寬度沒有畫在弧線上。這是為什麼呢? 還得再上個圖

的高度,直接看程式碼吧
private void drawDegree(Canvas canvas){ canvas.translate(getWidth()/2,getHeight()/2); canvas.rotate(30); for (int i=0;i<20;i++){ //縱座標下正上負,向上提高,加負值,即-mPaint.getStrokeWidth()/2 canvas.drawLine(RADIUS-Utils.px2dp(10),-mPaint.getStrokeWidth()/2,RADIUS,-mPaint.getStrokeWidth()/2,mPaint); canvas.rotate(-SWEEPANGLE/20); } //最後一個點,因座標系已經旋轉了240度,向上提高,加整值,即mPaint.getStrokeWidth()/2 canvas.drawLine(RADIUS-Utils.px2dp(10),mPaint.getStrokeWidth()/2,RADIUS,mPaint.getStrokeWidth()/2,mPaint); canvas.rotate(240-30);//旋轉回去的角度 canvas.translate(-getWidth()/2,-getHeight()/2); }
看了註釋,你會發現第一個點和最後一個點的提高方式不同,這也是為什麼上面”相應的“三個字我要加粗了,再看一眼修改後的效果圖

微信圖片_20181024141740.jpg
第二種方式
思路:使用PathMeasure測量弧線長度,利用PathDashPathEffect來畫刻度
簡單講一下PathMeasure和PathDashPathEffect
- PathMeasure :用來測量路徑的長度,public PathMeasure(Path path, boolean forceClosed),通過PathMeasure.getLength()
- PathDashPathEffect :Paint.setPathEffect(PathEffect effect)給圖形的輪廓設定效果的,PathDashPathEffect是PathEffect的一個子類,它的構造方法 PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style) 中, shape 引數是用來繪製的 Path ; advance 是兩個相鄰的 shape 段之間的間隔,不過注意,這個間隔是兩個 shape 段的起點的間隔,而不是前一個的終點和後一個的起點的距離; phase 和 DashPathEffect 中一樣,是虛線的偏移;最後一個引數 style,是用來指定拐彎改變的時候 shape 的轉換方式。style 的型別為 PathDashPathEffect.Style ,是一個 enum ,具體有三個值:TRANSLATE:位移,ROTATE:旋轉,MORPH:變體
知道了這兩個方法,我們就可以先用PathMeasure拿到弧線的長度,除以20獲得每個間隔的長度,然後通過Paint.setPathEffect(new PathDashPathEffect())方法來畫刻度就行了,直接上程式碼
private void drawDegree2(Canvas canvas){ //刻度的路徑 dash=new Path(); //Path.Direction.CW順時針方向 同時順時針切線方向為X軸正向 dash.addRect(0,0,Utils.px2dp(2),Utils.px2dp(10), Path.Direction.CW); //弧線長度的路徑 Path length=new Path(); length.addArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE); //測量弧線長度 pathMeasure=new PathMeasure(length,false); //這裡(pathMeasure.getLength()-mPaint.getStrokeWidth())/20 弧線長度之所以減去Paint的寬度跟我第一種方式去掉寬度是一個意思 mPaint.setPathEffect(new PathDashPathEffect(dash, (pathMeasure.getLength()-mPaint.getStrokeWidth())/20,0, PathDashPathEffect.Style.ROTATE)); canvas.drawArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE,false,mPaint); mPaint.setPathEffect(null); }
這裡我就不細講了,註釋還是比較清楚,效果圖跟第一種方式是一樣的就不貼圖,個人還是更加推薦第一種的畫刻度方式。
畫指標
畫指標呢,就比較簡單了,其實就是呼叫畫線的方法,先把座標系平移動原點位置,設定一個當前的角度 currentAngle 還有指標長度 INDICATOR ,唯一有一點難度的就是計算結束點的橫縱座標,需要用到三角函式的知識
- 橫座標:Math.cos(Math.toRadians(currentAngle))*INDICATOR
- 縱座標:Math.sin(Math.toRadians(currentAngle))*INDICATOR
很簡單,上程式碼
private void drawIndicator(Canvas canvas){ canvas.translate(getWidth()/2,getHeight()/2); canvas.drawLine(0,0, (float) Math.cos(Math.toRadians(currentAngle))*INDICATOR, (float)Math.sin(Math.toRadians(currentAngle))*INDICATOR, mPaint); canvas.translate(getWidth()/2,getHeight()/2); }
最後
Android自定義View是Android比較難的一塊內容,本文主要通過繪製DashBoard來講基本的繪製,Paint和Canvas的基本用法,接下來的一段時間內,我會繼續出自定義View相關的內容,下一篇文章會講繪製文字。
最後放上文章的demo ofollow,noindex">DashBoard ,覺得還不錯的請給個star哈