高毅 王昕 丁勇 涂小琴



摘 ?要: 在數據可視化方面,Android系統提供的組件不能滿足開發人員的需求,而第三方的圖表組件技術不夠成熟,本文提出了一種基于Android的圖表組件的實現方法,著重討論了圖表組件的布局空間設計、類設計、單位轉換、繪制流程、圖表繪制。該組件自定義程度高,使用方便,布局整齊,動畫效果良好,大大增強了用戶體驗,能滿足大多數Android應用軟件開發的需求,具有一定的創新性和很好的實用價值。
關鍵詞:?圖表;自定義;Android;數據可視化
中圖分類號: TP317????文獻標識碼:?A????DOI:10.3969/j.issn.1003-6970.2019.09.009
本文著錄格式:高毅,王昕,丁勇,等. 基于Android自定義圖表組件的關鍵技術研究[J]. 軟件,2019,40(9):40-44
Research on Key Technologies Based on Android Custom Chart Component
GAO Yi, WANG Xin, DING Yong, TU Xiao-qin
(College of Arts and Sciences,?Yunnan Normal University, Kunming?650222,China)
【Abstract】: In terms of data visualization, the components provided by Android system can not meet the needs of developers, and the third-party chart component technology is not mature enough. This paper proposes an Android-based chart component implementation method, focusing on the layout space design of chart components, class design, unit conversion, drawing process, chart drawing. The component has a high degree of customization, convenient use, neat layout, good animation effect, greatly enhances the user experience, can meet the needs of most Android application software development, has certain innovation and good practical value.
【Key words】: Chart; Custom; Android; Data visualization
在數據的分析和展示過程中,數據可視化是非常重要的手段之一,而各種類型的圖表,又是數據可視化中最重要和最常用的工具[1]。圖表可以以簡潔的方式和最清晰的視覺效果,高效地將有價值的信息傳遞給用戶。所以,在應用軟件開發中正得到越來越廣泛的應用。在Android應用軟件開發中,雖然系統提供了大量的組件用于界面設計,但是沒有圖表組件,因此需要開發者來創建自定義的圖表組件,以實現用戶的特殊需求[2]。
然而,Android系統中的圖表組件的開源方案并不多,第三方的圖表組件技術又不夠成熟。本文通
過設計一套基于Android的圖表組件,包含了散點圖、折線圖、柱狀圖、條形圖、餅圖和雷達圖六種基本圖表,實現了數據的可視化展示,該組件的實現通過繼承View類,重寫了onMeasure、onDraw等多個方法,加入了好多的組件屬性作為類的數據成員,并編寫了get方法和set方法,豐富了圖表組件的顯示樣式,通過了ValueAnimator類的相關技術來加入動畫效果,增強了用戶體驗。
1.1??圖表
圖表泛指在屏幕中顯示的,可直觀展示統計信息屬性(時間性、數量性等),對知識挖掘和信息直觀生動感受起關鍵作用的圖形結構,是一種很好的將對象屬性數據直觀、形象地"可視化"的手段[3]。合理的數據圖表,會更直觀的反映數據間的關系,比用數據和文字描述更清晰、更易懂。將工作表中的數據轉換成圖表呈現,可以關注我們更好地了解數據見的比例關系及變化趨勢,對研究對象做出合理的推斷和預測。
1.2View
Android應用的絕大部分UI組件都放在android.widget包及其子包、android.view包及其子包中,Android應用的所有UI組件都繼承了View類,View組件非常類似于Swing編程的JPanel,它代表一個空白的矩形區域[4]。
1.3Paint類
要實現繪圖功能,首先需要畫筆工具,Paint類便是Android的畫筆,它包含了繪制幾何圖形、文本和位圖所需的一些風格和顏色信息,如線寬、字體和大小等。通過Paint類提供給用戶的公共方法,可以對其屬性進行設置。
1.4Canvas類
各類圖形是要在一張畫布上繪制的,Canvas類則實現了畫布這一功能,在繪制圖形之前,需要對Canvas設置一些畫布的屬性,如畫布的顏色、尺寸等。
1.5Path類
在進行劃線等操作時還需要連接路徑,這個工具由Path提供,Path類中包含一些直線或曲線連接到指定點的方法。Android提供的Path是一個非常有用的類,它可以預先在View上將N個點連成一條“路徑”,然后調用Canvas的drawPath方法即可沿著路徑繪制圖形。
2.1布局空間設計
移動端設備的屏幕相對于計算機顯示器尺寸相對較小,移動端應用開發的特點之一就是可用來顯示的空間小,要讓圖表有好的顯示效果,一定要合理分配利用有限的空間,所以,在實現圖表組件時,布局空間的設計尤為關鍵。圖表組件的布局空間設計如圖1所示,分為圖表標題區、圖表繪制區和系列標題區三個部分[5-6]。其中,圖表標題區用來顯示圖表的標題,本文實現的圖表組件可以設置圖表標題的文本大小,圖表標題的文本顏色;圖表繪制區用來顯示圖表及圖表相關元素,包括坐標軸、坐標刻度值、背景線條等,本文實現的圖表組件可以設置坐標線條顏色、坐標線條粗細、坐標刻度值文本大小、坐標刻度值文本顏色、背景樣式等,這是一個核心區域;系列標題區用來顯示圖表的系列標題,本文實現的圖表組件可以顯示多個系列數據,所以,圖表的系列標題一般會存在多個,為了更好的利用布局空間,本文設計的方案是每一行顯示兩個系列標題,依次從左到右,從上到下。該圖表組件的布局空間的設計,需要進行計算,首先計算該圖表在移動設備端的顯示大小,再計算系列標題區所占布局空間的大小,最后得到圖表繪制區的大小。
2.2單位轉換
在Android應用開發中,在設置組件的大小和文字的大小時都要用到單位,Android中常用的單位有px、dip、dp和sp等。其中,px(像素),每個px對應屏幕上的一個點。dip或dp(設備獨立像素),一種基于屏幕密度的抽象單位。sp(比例像素),主要處理字體的大小,可以根據用戶的字體大小首選項進行縮放。一般情況下,用dp來表示距離大小,用sp表示字體大小。
圖表組件在實現時,使用的單位為像素。由于移動端設備的尺寸大小和分辨率各式各樣,各種屏幕密度不同導致同樣像素大小的長度在不同密度的屏幕上的顯示長度不同,相同長度的屏幕高密度屏幕包含更多像素點,為了在不同大小的屏幕上都有好的顯示效果,該組件在實現的過程中需要進行單位轉換,需要把dp和sp轉換為px。
(1)dp轉px
在圖表組件實現中,編寫了把dp單位轉換為px單位的方法,代碼如下:
private float dpTopx(float dp) {
return TypedValue.applyDimension(Type d V alue.?CO MPLEX_UNIT_DIP,
dp, getResources().getDisplayMe t rics());
}
(2)sp轉px
在圖表組件實現中,編寫了把sp單位轉換為px單位的方法,代碼如下:
private float spTopx(float sp) {
return TypedValue.applyDimension(Ty pedValue.?COMPLEX_UNIT_SP,
sp,?getResources().getDisplayM e t rics());
}
2.3??類設計
圖表組件在實現的過程中,涉及到FColor、DataItems、ChartEntity、View、ChartView5個類,除了類View是系統類,其它的類都是為了實現該圖表組件而編寫的。類及類關系如圖2所示,類ChartView繼承于View類,類ChartView依賴于FColor類,類ChartView和類ChartEntity的關系是組合,類ChartEntity和類DataItems的關系是組合。下面就這幾個類做詳細描述。
(1)FColor類
在Android程序設計中,我們可以在xml布局文件中使用井號加6位十六進制(形如:#XXXXXX)或者井號加8位十六進制(形如:#XXXXXXXX)來表示顏色值,而在java代碼中不行。用這種形式來表示顏色值還是非常直觀明了的,為了在java代碼中也能夠這樣表示,特地編寫FColor類來實現此功能。
FColor類的數據成員由a、r、g、b構成。其中a表示透明度的值,r表示紅色分量的值,g表示綠色分量的值,b表示藍色分量的值。它們數據類型為int,取值范圍介于0到255之間。
FColor類中的關鍵方法public void setColor(String color),是把字符串表示的顏色值分割并轉換到a、r、g、b四個分量上面。
8位十六進制轉換代碼如下:
this.a=Integer.parseInt(color.substring(1, 2+1), 16);
this.r=Integer.parseInt(color.substring(3, 4+1), 16);
this.g=Integer.parseInt(color.substring(5, 6+1), 16);
this.b=Integer.parseInt(color.substring(7, 8+1), 16);
6位十六進制轉換代碼如下:
this.a=255;
this.r=Integer.parseInt(color.substring(1, 2+1), 16);
this.g=Integer.parseInt(color.substring(3, 4+1), 16);
this.b=Integer.parseInt(color.substring(5, 6+1), 16);
(2)DataItems類
DataItems類是用來表示一個序列數據的,有7個數據成員,其中,seriesName表示系列名,XItemValues表示X項的值(數組),XItemValuesSize表示X項個數seriesValues表示Y項的值(數組),seriesValuesSize表示Y項個數,seriesValuesMax表示Y項最大值,seriesValuesMin表示Y項最小值,為了方便構造圖表Y軸坐標刻度,特別地加了最后2個數據成員。DataItems類除了構造方法、數據成員的get/set方法外,關鍵方法有3個。其中,?addSeriesNam方法的功能是用來添加數據序列的名稱;addData方法的功能是用來添加1個數據項,1個數據項由2個部分組成,分別為數據項的名稱和數據項的值;clearData方法的功能是用來清空數據序列值的。
(3)ChartEntity類
ChartEntity類是用來表示圖表的數據源的,可以存儲多個系列數據,有2個數據成員,其中,ChartTitle表示系統標題,Series是DataItems類的數組對象,用來存儲多個系列的數據。ChartEntity類除了構造方法、數據成員的get/set方法外,有一個關鍵方法setData,該方法是用來加載數據的。
(4)ChartView類
ChartView類是用來實現圖表組件繪制的,該類有幾十個數據成員,用來表示圖表的數據源、標題文本、標題文本大小、標題文本顏色、系列標題文本、系列標題文本大小、系列標題文本顏色、坐標軸相關屬性、內外邊距、動畫相關屬性、背景相關屬性等。ChartView類除了構造方法、數據成員的get/set方法外,最為重要的就是onDraw方法了。ChartView類作為View類的子類,需要去重寫多個方法來實現圖表組件的繪制。
2.4圖表的繪制
(1)繪制流程
在Android系統中實現圖表組件,需要繼承View類,重寫其中的一個或者多個方法。本文描述的圖表組件是有動畫效果的,在繪制過程中把背景的繪制和圖表區的繪制分開,這樣有利于控制圖表區的動畫效果。圖表組件繪制流程的算法描述如算法1所示。
算法1
第1步:根據用戶設置計算圖表系列數據;
第2步:根據用戶設置計算圖表屬性值;
第3步:根據系列數據和圖表屬性值計算相應的圖表參數,用于后面繪制背景和圖表;
第4步:繪制背景;
第5步:初始屬性動畫值,animatedValue=0;
第6步:判斷animatedValue <= 1 是否成立,若成立,繼續下一步,否則,跳到第9步;
第7步:根據屬性動畫值animatedValue重繪圖表,也就是重新執行onDraw方法;
第8步:根據ValueAnimator對象的addUpd ate Listener監聽事件計算新的屬性動畫值animatedValue,返回第6步;
第9步:算法結束。
(2)重寫onDraw方法
基于Android UI組件的實現原理,開發者完全可以開發出項目定制的組件,當Android系統提供的UI組件不足以滿足需求時,可以通過繼承View來派生自定義組件。過程為,首先定義一個繼承View基類的子類,然后重寫View類的一個或多個方法來實現,其中,onDraw方法尤為關鍵。本文描述的圖表組件由散點圖、折線圖、柱狀圖、條形圖、餅圖和雷達圖六種基本圖表組成,每一種圖表的繪制都有一定的差異,在此,僅以折線圖為例來對onDraw方法的關鍵代碼做描述。
……
//計算起點坐標
startX=startLeft; startY=startTop+((Float.valueOf(yItemTitle.get(yIt emsCount-1))-(float)value)/(Float.valueOf(yItemTitle.get?(yItemsCount-1))-Float.valueOf(yItemTitle.get(0))))*co nt entHeight;
//繪制第一個點標志
drawMark(canvas,seriesMark[i%seriesMark.length],?seriesItemColor.get(i%seriesItemColor.size()),startX,startY,?dpTopx(markWidth));
//根據屬性動畫animatedValue變量值計算當前繪制到哪一個刻度區(X軸方向)
int no=getScope(animatedValue);
//對小于no的刻度區進行繪制
for(int j=1;j if(j v1=lineSeries.get(i).getSeriesValues().get(j-1); v2=lineSeries.get(i).getSeriesValues().get(j); //計算折線的起點坐標 startX=startLeft+(j-1)*avgDis; stopX=startLeft+(j)*avgDis; //計算折線的終點坐標 startY=startTop+((Float.valueOf(yItemTitle.get(yItemsCount-1))- (float)v1)/(Float.valueOf(yItemTitle.get(yIte msCount-1))- Float.valueOf(yItemTitle.get(0))))*contentH eight; stopY=startTop+((Float.valueOf(yItemTitle.get(yItemsCount-1))- (float)v2)/(Float.valueOf(yItemTitle.get(yIte msCount-1))- Float.valueOf(yItemTitle.get(0))))*contentH eight; //繪制折線 canvas.drawLine(startX, startY, stopX, stopY,?linePaint); //繪制點標志 drawMark(canvas,seriesMark[i% seriesMark.?length], seriesItemColor.get(i%seriesItemColor.size()), stopX,stopY,dpTopx(markWidth)); //把上面折線的終點設置為下一條折線的起點 startX=stopX; startY=stopY; } } //若屬性動畫animatedValue變量值小于等于1,對等于no的刻度區進行重新繪制,以實現動畫效果 if(animatedValue<=1){ if(no<lineSeries.get(i).getSeriesValues().size()){ //計算折線終點的X坐標
stopX=startLeft+animatedValue*contentWidth;
//計算折線終點的Y坐標
v1=lineSeries.get(i).getSeriesValues().get(no-1);
v2=lineSeries.get(i).getSeriesValues().get(no);
float x1=startLeft+(no-1)*avgDis;
float x2=startLeft+(no)*avgDis;
float y1=startTop+((Float.valueOf(yItemTitle.get?(yItemsCount-1))-
(float)v1)/(Float.valueOf(yItemTitle.get(yItemsCount-1))-
Float.valueOf(yItemTitle.get(0))))*contentHeight;
float y2=startTop+((Float.valueOf(yItemTitle.get?(yItemsCount-1))-
(float)v2)/(Float.valueOf(yItemTitle.get(yItems Count-1))-
Float.valueOf(yItemTitle.get(0))))*contentHeight;
float x=startLeft+animatedValue*contentWidth;
stopY=(y2*(x-x1)+y1*(x2-x))/(x2-x1);
//繪制折線
canvas.drawLine(startX, startY, stopX, stopY, linePaint);
}
}
//繪制最后一個點標志
if(animatedValue==1){
drawMark(canvas,seriesMark[i%seriesMark.length],
seriesItemColor.get(i%seriesItemColor.size()),
stopX,stopY,dpTopx(markWidth));
}
本文實現的圖表組件的效果如圖所示,該圖表組件由散點圖、折線圖、柱狀圖、條形圖、餅圖和雷達圖六個基本圖表構成。該圖表組件可以展示多個系列數據,還具有動畫效果,文本和圖表可以很好的自適應移動端設備。組件在設計的過程中,加入了大量的屬性作為類的數據成員,并編寫了相應的set方法和get方法,方便Android應用軟件開發人員根據自身的需求去設置圖表樣式,如標題文本、標題文本大小、標題文本顏色、系列標題文本、系列標題文本大小、系列標題文本顏色、坐標軸相關屬性、內外邊距、動畫相關屬性、背景相關屬性等。相比現有的類似的第三方開源方案,自定義程度高、使用方便、靈活,用戶體驗好,所以,該組件還是具有很好的實用性和創新性。
本文實現的圖表組件可以解決一些數據展示的問題,可以展示多個系列的數據,方便不同系列的數據進行對比,經過測試,組件自定義程度高,使用方便,布局整齊,動畫效果良好,大大增強了用戶體驗,能滿足大多數Android應用軟件開發的需求。但是,圖表包含很多種類型,而本文僅僅實現了散點圖、折線圖、柱狀圖、條形圖、餅圖和雷達圖六種,當遇到一些特殊的數據可視化時,該組件就不能滿足需求了,在以后的研究工作中,將在圖表改進、圖表類型擴展方面做深入研究。
參考文獻