高毅,王昕,丁勇
(云南师范大学文理学院,昆明650222)
随着信息技术的飞速发展,人们对信息系统的需求越来越大。在大多数信息系统中,都存在报表功能,用图表来向用户展示数据。然而在Android 开发中,系统提供了文本框、编辑框、按钮、单选按钮与单选按钮组、复选框、图片框、下拉列表框、列表框、开关按钮等大量的组件,虽能满足大多数应用的开发需求,但Android 系统并不提供图表组件,开发人员只能创建自定义的图表组件,以满足用户的特殊需求。
饼图作为最为常用的图表组件之一,在很多Android 应用中都会使用到。然而,Android 系统中的饼图组件的开源方案并不多,技术不够成熟,或多或少都会存在一些问题,如使用不便、不够灵活、用户体验差等。本文将从布局空间设计、饼图的绘制流程、饼图实现的核心代码等方面来描述一种基于Android 的饼图组件。该组件提供了很多属性接口,方便用户根据自身的需求来定制相应的饼图。该组件还实现了动画效果,是按照顺时针方向依次显示各个数据项对应的饼图,用户体验好。
饼图采用了饼干的隐喻,用环形方式呈现各分量在整体中的比例[1],是数据可视化的常用工具,通常用来显示一个数据系列(数据系列:在图表中绘制的相关数据点,这些数据源自数据表的行或列。图表中的每个数据系列具有唯一的颜色或图案并且在图表的图例中表示。可以在图表中绘制一个或多个数据系列。饼图只有一个数据系列。)中各项的大小与各项总和的比例。饼图中的数据点(数据点:在图表中绘制的单个值,这些值由条形、柱形、折线、饼图或圆环图的扇面、圆点和其他被称为数据标记的图形表示。相同颜色的数据标记组成一个数据系列。)显示为整个饼图的百分比。
Android 应用的绝大部分UI 组件都放在android.widget 包及其子包、android.view 包及其子包中,Android应用的所有UI 组件都继承了View 类。基于Android UI 组件的实现原理,开发者完全可以开发出项目定制的组件,当Android 系统提供的UI 组件不足以满足需求时,可以通过继承View 来派生自定义组件。过程为,首先定义一个继承View 基类的子类,然后重写View 类的一个或多个方法来实现[2]。
各类图形是要在一张画布上绘制的,Canvas 类则实现了画布这一功能,在绘制图形之前,需要对Canvas设置一些画布的属性,如画布的颜色、尺寸等[2]。
要实现绘图功能,首先需要画笔工具,Paint 类便是Android 的画笔,它包含了绘制几何图形、文本和位图所需的一些风格和颜色信息,如线宽、字体和大小等。通过Paint 类提供给用户的公共方法,可以对其属性进行设置[2]。
在实现饼图组件时,布局空间的设计尤为关键。移动端应用开发最大的特点之一就是可用显示空间小,要让饼图有更好的显示效果,必需要合理的分配布局空间。饼图的布局空间设计如图1 所示,由图表标题区、图表绘制区和系列标题区构成[3-4]。其中,图表标题区用来显示饼图的总标题,图表绘制区用来显示饼图,系列标题区用来显示饼图的系列标题。而饼图的系列一般会存在多个,那系列标题也就存在多个,为了更好的利用布局空间,本文设计的方案是每一行显示两个系列标题,依次从左到右。在饼图的设计过程中,为了能让Android 开发人员可以自定义标题文本字体大小,首先计算该饼图在移动设备端的显示大小,再计算系列标题区所占大小,最后得到图表绘制区的大小。下面就计算过程做详细描述,单位都为像素(px)。
图1 布局空间设计图
(1)计算饼图在移动设备端显示的大小
用W 表示饼图在移动端设备上所占的宽度,H 表示饼图在移动端设备上所占的高度。通过重写View类中的onMeasure 方法来实现对W 和H 的计算。关键代码如下:
//计算显示模式
int specMode=MeasureSpec.getMode(widthMeasureSpec);
//计算宽度
int width=MeasureSpec.getSize(widthMeasureSpec);
//若显示模式是不确定的值,或者未指定尺寸,设置一个300 的默认值
if(specMode==MeasureSpec.UNSPECIFIED)
{
width=300;
}
//计算高度的代码和上面计算宽度的代码类似,在此省略
......
setMeasuredDimension(width,height);
编写好上面的onMeasure 方法后,就可以通过以下公式来计算W 和H。
W=getMeasuredWidth() (1)
H=getMeasuredHeight() (2)
(2)计算系列标题区的大小
为了计算系列标题区所占空间的宽和高,特地编写了private Rect getTextRect(String text,float textSize)方法,该方法有两个参数,第一个参数text 是显示文本的内容,第2 个参数textSize 是显示文本的大小,返回值是Rect 类型的对象。由于中文的基线和英文的基线不一样,为了显示效果,在计算文本所占矩形时做了修正。关键代码如下:
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 方法后,就可以计算系列标题区所占空间的宽和高。
本文设计的饼图系列标题区是一行显示两个标题,可显示多个系列标题。系列标题区的宽WST和高HST计算公式如下:
WST=(getTextRect(系列标题文本内容,系列标题文本大小).width())*2 (3)
HST=(getTextRect(系列标题文本内容,系列标题文本大小).height())*(Integer)(n/2) (4)
其中,n 表示系列标题的数目,(Integer)(n/2)表示对表达式n/2 进行取整运算。
(3)计算图表绘制区的大小
图表绘制区的宽WC和高HC计算公式如下:
WC=W (5)
HC=H-HST(6)
然而,在饼图的实现过程中,要对图表绘制区的大小做修正,这和在一个矩形中画内切圆同理。当矩形是一个正方形时,图表绘制区占了整个矩形;当矩形的宽大于高时,图表绘制区的左右两侧会有空白区域,这时饼图的最大半径为矩形高的一半;当矩形的宽小于高时,图表绘制区的上下两端会有空白区域,这时饼图的最大半径为矩形宽的一半。所以,图表区的真实大小是由图表绘制区的宽和高的最小值决定的。
修正的图表绘制区的宽WC'和高HC'计算公式如下:
WC'=HC'=MIN(WC,HC) (7)
其中,MIN(WC,HC)表示计算WC和HC的最小值。
在Android 系统中实现自定义组件,需要继承View 类,重写其中的一个或者多个方法,其中对on-Draw 方法的重写尤为重要。本文描述的饼图组件是有动画效果的,在绘制过程中把背景的绘制和图表区的绘制分开,这样有利于控制图表区的动画效果。下面先介绍绘制流程,再对onDraw 方法中的核心代码做描述。
(1)绘制流程
饼图的绘制流程如图2 所示。首先取系列数据、设置图表属性,然后根据系列数据和图表属性去计算与绘制饼图相关的属性值,接着绘制背景,给属性动画的监听变量animatedValue 设置初值为0,接下来去判断animatedValue 的值是否小于等于1,若成立,重绘饼图,并计算变量animatedValue 新的监听值,接着返回去判断animatedValue 的值是否小于等于1,否则,绘制过程结束。
图2 绘制流程图
(2)onDraw 方法的核心代码
重写onDraw 方法的核心代码如下:
//计算图表区的宽和高
contentWidth=mRight-mLeft;
contentHeight=mBottom-mTop;
//计算图表区的中心点
centerX=mLeft+contentWidth/2.0f;
centerY=mTop+contentHeight/2.0f;
//设置画笔对象
piePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
piePaint.setStyle(Paint.Style.FILL_AND_STROKE);
//修正图表区的宽和高
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);
}
//设置绘制的初始角度
float currentx=0;
//遍历数据角度系列值
for(int i=0;i //若属性动画值乘上360 大于等于初始角度,则重绘图表内容 if(animatedValue*360)>=currentx){ //设置画笔对象颜色属性 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); } } 本文实现的饼图组件的效果如图3 和图4 所示。该组件可以显示系列中不同数据项的比例,不同的数据项用不同的颜色表示,还具有动画效果,动画效果为顺时针方向依次显示系列的数据项。饼图组件在设计的过程中,加入了大量的属性作为类的数据成员,并编写了相应的set 方法和get 方法,方便用户根据自身的需求去设置图表样式,如背景颜色、饼图边缘线条粗细、饼图边缘线条颜色、文本大小、文本颜色等属性。相比现有的类似的第三方开源方案,该饼图组件使用方便、灵活,所以,该组件还是具有一定的创新性,并具备一定的实用价值。 图3 实验效果图一 图4 实验效果图二 本文分别布局空间设计、饼图组件的绘制流程、on-Draw 方法的核心代码等方面对饼图组组件进行描述,实现的饼图组件可以用来解决Android 开发中数据展示的一些问题。经过测试,显示效果良好,布局空间设计合理,运行效率高,动画效果良好,用户体验好,能满足大多数Android 应用开发人员的需求。但是,还是有一些方面需要进一步研究,如环形图、南丁格尔玫瑰图、嵌套饼图,等等,下一步将会在这些方面做深入研究。3 实验效果
4 结语