在開發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的每一清單項的高度設定。這裡需要根據顯示的内容對其進行最小高度的設定。
stackoverflow上面有對此問題的描述,具體分析可搜尋stackoverflow。
第二:在進行圖形及清單的繪制時,經常需要用到參數的傳遞。在傳遞過程中,可能會出現一種情況如下:
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則與此不同)。
很多時候,浮躁源于我們的眼睛看外界太多,看内心太少。