在开发Android应用的过程中,常需要将数据库中的数据进行可视化显示,比较常用的显示方式有两种:图和表。这里将对图形库AchartEngine以及列表视图ListView进行说明,以期对您的项目有所帮助。
一、AchartEngine的原理
就我的使用体会来讲,AchartEngine是将数据与图像分离,分别用XYMultipleSeriesDataset与XYMultipleSeriesRenderer来进行封装。XYMultipleSeriesDataset中可以添加各种类型的Series(如RangeCategorySeries,XYSeries等),而XYMultipleSeriesRenderer可以添加各种SeriesRenderer(如SimpleSeriesRenderer,XYSeriesRenderer等),然后通过AchartEngine的工厂类得到相应的图形View或者Intent。
这里举一例进行说明,先给出一张截图如下:
上图是两种图形的叠加,即SimpleSeriesRenderer与XYSeriesRenderer。SimpleSeriesRenderer负责三个柱线图的显示,XYSeriesRenderer负责折线图的显示。
首先,获取两类图形的数据。柱线图要表示出最大值与最小值,用数组来表示。
// range graph
double[] minValues = new double[LabelsGraphWhichDays.size()];
double[] maxValues = new double[LabelsGraphWhichDays.size()];
当然,minValues的值一般来自数据库SQLite,这里需要对数据库进行读取(数据库文件、表的设立需要根据使用情况灵活对待,这里不重点讲述数据库了),读取方式不再赘述。由图形即可推测,上述数组长度为3,minValues的数据分别为3.1,6.1,13.8,maxValues的值类似。
得到柱线图的数据之后,需要获取折线图的数据,折线图的数据包含x轴坐标和y轴坐标。
double[] xData = new double[xList.size()];
double[] yData = new double[yList.size()];
其中,xData分别为1,2,3,yData分别为3.1, 12.5,13.8。
得到数据以后,将数据添加到相应的Series。
RangeCategorySeries series0 = new RangeCategorySeries("Title");
int length = minValues.length;
for (int k = 0; k < length; k++) {
BigDecimal min = new BigDecimal(minValues[k]);
BigDecimal max = new BigDecimal(maxValues[k]);
double minValue = min.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
double maxValue = max.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
//RangeCategorySeries
series0.add(minValue, maxValue);
}
XYSeries series1 = new XYSeries("Title");for(int i = 0; i<xData.length; i++){ //series1 series1.add(xHungryData[i],yHungryData[i]);}
得到两个含有数据的Series后,只需要将两个Series分别添加至XYMultipleSeriesDataset即可。
XYMultipleSeriesDataset multipleSeriesDataset = new XYMultipleSeriesDataset();
// addSeries
multipleSeriesDataset.addSeries(0, series1);
multipleSeriesDataset.addSeries(0, series0.toXYSeries());
然后,设置相应的SeriesRenderer,即图形显示。
与上述设置数据的步骤类似,设置图形也需要从SeriesRenderer开始。对于柱线图的RangeCategorySeries,采用SimpleSeriesRenderer来显示,而对于XYSeries,也就用XYSeriesRenderer来显示。
// SeriesRenderer Range
SimpleSeriesRenderer renderer0 = new SimpleSeriesRenderer();
renderer0.setDisplayChartValues(true);
renderer0.setChartValuesTextSize(16);
renderer0.setChartValuesSpacing(3);
renderer0.setChartValuesTextAlign(Align.CENTER);
renderer0.setColor(Color.rgb(0, 220, 173));
renderer0.setGradientEnabled(false);
// SeriesRenderer XY
XYSeriesRenderer renderer1 = new XYSeriesRenderer();
renderer1.setColor(Color.rgb(255, 7, 87));
renderer1.setDisplayChartValues(false);
renderer1.setLineWidth(2);
// addSeriesRenderer
multipleSeriesRenderer.addSeriesRenderer(0, renderer1);
multipleSeriesRenderer.addSeriesRenderer(0, renderer0);
对于上述图形,还需要对multipleSeriesRenderer进行一些设置,这里不再详细说明。
最后,只需一步,即可完成。即通过工厂类获得相应的图形View。
String[] types = new String[] {RangeBarChart.TYPE, LineChart.TYPE};//
displayView = ChartFactory.getCombinedXYChartView(getActivity(), multipleSeriesDataset, multipleSeriesRenderer, types);
获取displayView后,就可以将其在Layout中进行显示,得到上述图形。
细心的朋友会发现这里还有对X轴标签的设置。在对标签设置时,需要首先将原始的标签屏蔽掉。
//set X label
multipleSeriesRenderer.setXLabels(0);
multipleSeriesRenderer.setXLabelsColor(Color.BLACK);
然后添加自定义的标签。
for(int i = 0; i < LabelsGraphWhichDays.size(); i++)
{
multipleSeriesRenderer.addXTextLabel(i+1, xDateLabel[i] + "\n" + xTimesLabel[i]);
}
好啦,大功告成。
二、ListView解析
在需要以列表显示大量信息时,ListView应该是一个很常用的View。ListView属于AdapterView,即在使用ListView时,需要为ListView配置一个Adapter。Adapter常见的实现类有ArrayAdapter,SimpleAdapter,SimpleCursorAdapter,BaseAdapter。ArrayAdapter属于简单、易用的Adapter,通常用于将数组或者List集合的多个值包装成列表项;SimpleAdapter功能非常强大,可用于将List集合的多个对象包装成多个列表项;SimpleCursorAdapter与SimpleAdapter不同的是,它用于包装Cursor提供的数据;BaseAdapter通常用于被扩展,扩展BaseAdapter可以对列表项进行最大限度的定制。
这里重点说明SimpleAdapter以及BaseAdapter。
SimpleAdapter可以将列表的每一项都采用一个布局文件来显示,因此,列表项的显示就可以很丰富(因为可以自定义显示内容以及布局)。SimpleAdapter的教程较多,这里不详细说明,只针对SimpleAdapter的创建进行阐述。
SimpleAdapter simpleAdapter = new SimpleAdapter(this, listItems, R.layout.simple_item,
new String[]{"Name", "ID", "Gender"},
new int[]{R.id.name, R.id.id, R.id.gender});
this是指代上下文,即Context,可能在某些应用中,这些需要用到getActivity()。listItems是一个重点参数,包含该Adapter的数据来源。它的格式应该是List<? extends Map<String, ?>>,该集合中每个Map<String, ?>对象将生产一个列表。R.layout.simple_item指定一个布局界面的ID,即用于在ListView中的每个Item的显示的布局,此布局根据自定义而实现SimpleAdapter的丰富显示。后两个数组中,String数组决定提取Map<String, ?>对象中哪些key对应的value值来生产列表项。int数组就是布局界面上组件的ID,决定哪些组件被填充。
建立此SimpleAdapter之后,将此Adapter应用某个ListView即可。
可能,你已经觉得SimpleAdapter已经足够强大,可以实现丰富的显示,但是,在很多对列表有个性化要求的场合,比如需要根据需要对不同的列表项进行颜色区分,SimpleAdapter依然显得不够用,这时,你就需要考虑扩展BaseAdapter了,好吧,重点来了。请先看下图:
如果某一天,你突然被要求实现上图中的效果,你会打算怎么实现呢?(请忽略图中的上部分的图形Title,只关注中间的数据列表)。说明一下,上述数据列表首先根据左侧的时间进行分栏,不同天之间用不同 颜色背景区分(比如这里18/04的背景颜色是Color.argb(100, 226, 215, 247),19/04的背景颜色是Color.argb(100, 243, 241, 250)),其次,在18/04的显示中,第四栏的背景颜色为Color.argb(100, 0xff, 00 , 00)。
好啦,说说实现方案吧,这里采用了两层扩展的BaseAdapter,外层BaseAdapter负责每一天的所有显示,即上图中的外层BaseAdapter的getCount()将返回2,分别为18/04和19/04。外层BaseAdapter的getView()方法返回的View的布局包括左侧的文本TextView(显示18/04)和右侧的ListView,这里即是ListView的嵌套。内层BaseAdapter的getCount()方法将根据每天的数据多少来决定,在上图中18/04为4,19/04为1。其getView()方法将返回每一行的从时间到最后的所有信息。其中的红色背景的那一行则根据对本行显示的数据的判断来决定。
具体实现时,首先获取外层ListView,设置其Adapter为adapterForListOuter。
LinearLayout history_data_list_layout = (LinearLayout) getActivity().getLayoutInflater().inflate(R.layout.history_data_list, null);
ListView history_data_list = (ListView) history_data_list_layout.findViewById(R.id.myList);
history_data_list.setAdapter(adapterForListOuter);
在adapterForListOuter的getView()方法中,获取内层ListView,并为其设置adapterForListInner。
ListView history_data_list_content = (ListView) history_data_list_outer.findViewById(R.id.history_data_list_content);
// Set the data display
TextView history_data_list_date = (TextView) history_data_list_outer.findViewById(R.id.history_data_list_date);
history_data_list_date.setText(history_data_list_date_display[position]);
// Update outer position
history_data_list_content.setAdapter(adapterForListInner);
在adapterForListInner的getView()方法中,首先获取显示信息的行的布局。
if (convertView == null) {
history_data_list_inner = getActivity().getLayoutInflater().inflate(R.layout.history_data_list_cell, parent, false);
} else {
history_data_list_inner = convertView;
}
然后,对每一行的数据以及显示背景进行设置就可以了。三、两个其他的问题。
在实现过程中,遇到两个其他的问题,这里指出,供交流。
第一:ListView中每一项的高度问题。在进行内层List显示时,出现过只能显示一行的情况,即上述18/04本来有四行,结果只能显示第一行,经调试,发现原因在于外层List的每一列表项的高度设置。这里需要根据显示的内容对其进行最小高度的设置。
history_data_list_outer.setMinimumHeight(thisDayTimes * 42 + history_data_list_content.getDividerHeight() * (adapterForListInner.getCount() - 1));
*上面有对此问题的描述,具体分析可搜索*。
第二:在进行图形及列表的绘制时,经常需要用到参数的传递。在传递过程中,可能会出现一种情况如下:
ObjectA objectA = new ObjectA();
ObjectB objectB = new ObjectB();
objectB.add(objectA);
objectA.clear();
objectA传递给objectB的只是对对象ObejectA的一个引用(reference),将objectA清除掉以后,objectB中持有的对ObejectA的引用也随之清除,并不能达到将objectA的内容(value)传递给objectB。(这个问题是Java中基本的对象、引用、参数传递的问题,可在开发过程中却常犯的一个错误)不过对于基本数据类型,则不存在此问题。如int a = 1; int b =2; a = b; 之后即使将b clear, a 的值依然为2,因为这里不存在引用的问题(Integer则与此不同)。
很多时候,浮躁源于我们的眼睛看外界太多,看内心太少。