高毅,王昕,丁勇
(云南師范大學(xué)文理學(xué)院,昆明650222)
隨著信息技術(shù)的飛速發(fā)展,人們對信息系統(tǒng)的需求越來越大。在大多數(shù)信息系統(tǒng)中,都存在報表功能,用圖表來向用戶展示數(shù)據(jù)。然而在Android 開發(fā)中,系統(tǒng)提供了文本框、編輯框、按鈕、單選按鈕與單選按鈕組、復(fù)選框、圖片框、下拉列表框、列表框、開關(guān)按鈕等大量的組件,雖能滿足大多數(shù)應(yīng)用的開發(fā)需求,但Android 系統(tǒng)并不提供圖表組件,開發(fā)人員只能創(chuàng)建自定義的圖表組件,以滿足用戶的特殊需求。
餅圖作為最為常用的圖表組件之一,在很多Android 應(yīng)用中都會使用到。然而,Android 系統(tǒng)中的餅圖組件的開源方案并不多,技術(shù)不夠成熟,或多或少都會存在一些問題,如使用不便、不夠靈活、用戶體驗差等。本文將從布局空間設(shè)計、餅圖的繪制流程、餅圖實現(xiàn)的核心代碼等方面來描述一種基于Android 的餅圖組件。該組件提供了很多屬性接口,方便用戶根據(jù)自身的需求來定制相應(yīng)的餅圖。該組件還實現(xiàn)了動畫效果,是按照順時針方向依次顯示各個數(shù)據(jù)項對應(yīng)的餅圖,用戶體驗好。
餅圖采用了餅干的隱喻,用環(huán)形方式呈現(xiàn)各分量在整體中的比例[1],是數(shù)據(jù)可視化的常用工具,通常用來顯示一個數(shù)據(jù)系列(數(shù)據(jù)系列:在圖表中繪制的相關(guān)數(shù)據(jù)點,這些數(shù)據(jù)源自數(shù)據(jù)表的行或列。圖表中的每個數(shù)據(jù)系列具有唯一的顏色或圖案并且在圖表的圖例中表示??梢栽趫D表中繪制一個或多個數(shù)據(jù)系列。餅圖只有一個數(shù)據(jù)系列。)中各項的大小與各項總和的比例。餅圖中的數(shù)據(jù)點(數(shù)據(jù)點:在圖表中繪制的單個值,這些值由條形、柱形、折線、餅圖或圓環(huán)圖的扇面、圓點和其他被稱為數(shù)據(jù)標(biāo)記的圖形表示。相同顏色的數(shù)據(jù)標(biāo)記組成一個數(shù)據(jù)系列。)顯示為整個餅圖的百分比。
Android 應(yīng)用的絕大部分UI 組件都放在android.widget 包及其子包、android.view 包及其子包中,Android應(yīng)用的所有UI 組件都繼承了View 類?;贏ndroid UI 組件的實現(xiàn)原理,開發(fā)者完全可以開發(fā)出項目定制的組件,當(dāng)Android 系統(tǒng)提供的UI 組件不足以滿足需求時,可以通過繼承View 來派生自定義組件。過程為,首先定義一個繼承View 基類的子類,然后重寫View 類的一個或多個方法來實現(xiàn)[2]。
各類圖形是要在一張畫布上繪制的,Canvas 類則實現(xiàn)了畫布這一功能,在繪制圖形之前,需要對Canvas設(shè)置一些畫布的屬性,如畫布的顏色、尺寸等[2]。
要實現(xiàn)繪圖功能,首先需要畫筆工具,Paint 類便是Android 的畫筆,它包含了繪制幾何圖形、文本和位圖所需的一些風(fēng)格和顏色信息,如線寬、字體和大小等。通過Paint 類提供給用戶的公共方法,可以對其屬性進行設(shè)置[2]。
在實現(xiàn)餅圖組件時,布局空間的設(shè)計尤為關(guān)鍵。移動端應(yīng)用開發(fā)最大的特點之一就是可用顯示空間小,要讓餅圖有更好的顯示效果,必需要合理的分配布局空間。餅圖的布局空間設(shè)計如圖1 所示,由圖表標(biāo)題區(qū)、圖表繪制區(qū)和系列標(biāo)題區(qū)構(gòu)成[3-4]。其中,圖表標(biāo)題區(qū)用來顯示餅圖的總標(biāo)題,圖表繪制區(qū)用來顯示餅圖,系列標(biāo)題區(qū)用來顯示餅圖的系列標(biāo)題。而餅圖的系列一般會存在多個,那系列標(biāo)題也就存在多個,為了更好的利用布局空間,本文設(shè)計的方案是每一行顯示兩個系列標(biāo)題,依次從左到右。在餅圖的設(shè)計過程中,為了能讓Android 開發(fā)人員可以自定義標(biāo)題文本字體大小,首先計算該餅圖在移動設(shè)備端的顯示大小,再計算系列標(biāo)題區(qū)所占大小,最后得到圖表繪制區(qū)的大小。下面就計算過程做詳細(xì)描述,單位都為像素(px)。
圖1 布局空間設(shè)計圖
(1)計算餅圖在移動設(shè)備端顯示的大小
用W 表示餅圖在移動端設(shè)備上所占的寬度,H 表示餅圖在移動端設(shè)備上所占的高度。通過重寫View類中的onMeasure 方法來實現(xiàn)對W 和H 的計算。關(guān)鍵代碼如下:
//計算顯示模式
int specMode=MeasureSpec.getMode(widthMeasureSpec);
//計算寬度
int width=MeasureSpec.getSize(widthMeasureSpec);
//若顯示模式是不確定的值,或者未指定尺寸,設(shè)置一個300 的默認(rèn)值
if(specMode==MeasureSpec.UNSPECIFIED)
{
width=300;
}
//計算高度的代碼和上面計算寬度的代碼類似,在此省略
......
setMeasuredDimension(width,height);
編寫好上面的onMeasure 方法后,就可以通過以下公式來計算W 和H。
W=getMeasuredWidth() (1)
H=getMeasuredHeight() (2)
(2)計算系列標(biāo)題區(qū)的大小
為了計算系列標(biāo)題區(qū)所占空間的寬和高,特地編寫了private Rect getTextRect(String text,float textSize)方法,該方法有兩個參數(shù),第一個參數(shù)text 是顯示文本的內(nèi)容,第2 個參數(shù)textSize 是顯示文本的大小,返回值是Rect 類型的對象。由于中文的基線和英文的基線不一樣,為了顯示效果,在計算文本所占矩形時做了修正。關(guān)鍵代碼如下:
Paint.FontMetricsInt fm=paint.getFontMetricsInt();
//修正上邊界,減去文本大小的四分之一
int top=baseLineY+fm.top-(int)(textSize/4.0f);
//修正下邊界,加上文本大小的四分之一
int bottom=baseLineY+fm.bottom+(int)(textSize/4.0f);
//計算文本所占矩形空間的寬度
int width=(int)paint.measureText(text);
Rect rect=new Rect(baseLineX,top,baseLineX+width,bottom);
編寫好上面的getTextRect 方法后,就可以計算系列標(biāo)題區(qū)所占空間的寬和高。
本文設(shè)計的餅圖系列標(biāo)題區(qū)是一行顯示兩個標(biāo)題,可顯示多個系列標(biāo)題。系列標(biāo)題區(qū)的寬WST和高HST計算公式如下:
WST=(getTextRect(系列標(biāo)題文本內(nèi)容,系列標(biāo)題文本大小).width())*2 (3)
HST=(getTextRect(系列標(biāo)題文本內(nèi)容,系列標(biāo)題文本大?。?height())*(Integer)(n/2) (4)
其中,n 表示系列標(biāo)題的數(shù)目,(Integer)(n/2)表示對表達式n/2 進行取整運算。
(3)計算圖表繪制區(qū)的大小
圖表繪制區(qū)的寬WC和高HC計算公式如下:
WC=W (5)
HC=H-HST(6)
然而,在餅圖的實現(xiàn)過程中,要對圖表繪制區(qū)的大小做修正,這和在一個矩形中畫內(nèi)切圓同理。當(dāng)矩形是一個正方形時,圖表繪制區(qū)占了整個矩形;當(dāng)矩形的寬大于高時,圖表繪制區(qū)的左右兩側(cè)會有空白區(qū)域,這時餅圖的最大半徑為矩形高的一半;當(dāng)矩形的寬小于高時,圖表繪制區(qū)的上下兩端會有空白區(qū)域,這時餅圖的最大半徑為矩形寬的一半。所以,圖表區(qū)的真實大小是由圖表繪制區(qū)的寬和高的最小值決定的。
修正的圖表繪制區(qū)的寬WC'和高HC'計算公式如下:
WC'=HC'=MIN(WC,HC) (7)
其中,MIN(WC,HC)表示計算WC和HC的最小值。
在Android 系統(tǒng)中實現(xiàn)自定義組件,需要繼承View 類,重寫其中的一個或者多個方法,其中對on-Draw 方法的重寫尤為重要。本文描述的餅圖組件是有動畫效果的,在繪制過程中把背景的繪制和圖表區(qū)的繪制分開,這樣有利于控制圖表區(qū)的動畫效果。下面先介紹繪制流程,再對onDraw 方法中的核心代碼做描述。
(1)繪制流程
餅圖的繪制流程如圖2 所示。首先取系列數(shù)據(jù)、設(shè)置圖表屬性,然后根據(jù)系列數(shù)據(jù)和圖表屬性去計算與繪制餅圖相關(guān)的屬性值,接著繪制背景,給屬性動畫的監(jiān)聽變量animatedValue 設(shè)置初值為0,接下來去判斷animatedValue 的值是否小于等于1,若成立,重繪餅圖,并計算變量animatedValue 新的監(jiān)聽值,接著返回去判斷animatedValue 的值是否小于等于1,否則,繪制過程結(jié)束。
圖2 繪制流程圖
(2)onDraw 方法的核心代碼
重寫onDraw 方法的核心代碼如下:
//計算圖表區(qū)的寬和高
contentWidth=mRight-mLeft;
contentHeight=mBottom-mTop;
//計算圖表區(qū)的中心點
centerX=mLeft+contentWidth/2.0f;
centerY=mTop+contentHeight/2.0f;
//設(shè)置畫筆對象
piePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
piePaint.setStyle(Paint.Style.FILL_AND_STROKE);
//修正圖表區(qū)的寬和高
if(contentWidth<=contentHeight){
oval=new RectF(centerX-contentWidth/2.0f,centerYcontentWidth/2.0f,
centerX+contentWidth/2.0f,centerY+contentWidth/2.0f);
}
else{
oval=new RectF(centerX-contentHeight/2.0f,center Y-contentHeight/2.0f,
centerX+contentHeight/2.0f,centerY+contentHeight/2.0f);
}
//設(shè)置繪制的初始角度
float currentx=0;
//遍歷數(shù)據(jù)角度系列值
for(int i=0;i //若屬性動畫值乘上360 大于等于初始角度,則重繪圖表內(nèi)容 if(animatedValue*360)>=currentx){ //設(shè)置畫筆對象顏色屬性 piePaint.setARGB(seriesItemColor.get(i%seriesItemColor.size()).getA(), seriesItemColor.get(i%seriesItemColor.size()).getR(), seriesItemColor.get(i%seriesItemColor.size()).getG(), seriesItemColor.get(i%seriesItemColor.size()).getB()); //繪制扇形 canvas.drawArc(oval,currentx,Math.min(seriesAngleValue.get(i),(animatedValue*360)-currentx)-1,true,pie-Paint); //計算新的初始角度 currentx=currentx+seriesAngleValue.get(i); } } 本文實現(xiàn)的餅圖組件的效果如圖3 和圖4 所示。該組件可以顯示系列中不同數(shù)據(jù)項的比例,不同的數(shù)據(jù)項用不同的顏色表示,還具有動畫效果,動畫效果為順時針方向依次顯示系列的數(shù)據(jù)項。餅圖組件在設(shè)計的過程中,加入了大量的屬性作為類的數(shù)據(jù)成員,并編寫了相應(yīng)的set 方法和get 方法,方便用戶根據(jù)自身的需求去設(shè)置圖表樣式,如背景顏色、餅圖邊緣線條粗細(xì)、餅圖邊緣線條顏色、文本大小、文本顏色等屬性。相比現(xiàn)有的類似的第三方開源方案,該餅圖組件使用方便、靈活,所以,該組件還是具有一定的創(chuàng)新性,并具備一定的實用價值。 圖3 實驗效果圖一 圖4 實驗效果圖二 本文分別布局空間設(shè)計、餅圖組件的繪制流程、on-Draw 方法的核心代碼等方面對餅圖組組件進行描述,實現(xiàn)的餅圖組件可以用來解決Android 開發(fā)中數(shù)據(jù)展示的一些問題。經(jīng)過測試,顯示效果良好,布局空間設(shè)計合理,運行效率高,動畫效果良好,用戶體驗好,能滿足大多數(shù)Android 應(yīng)用開發(fā)人員的需求。但是,還是有一些方面需要進一步研究,如環(huán)形圖、南丁格爾玫瑰圖、嵌套餅圖,等等,下一步將會在這些方面做深入研究。3 實驗效果
4 結(jié)語