前言:最近在做一个第三方App,涉及到Android图表显示,图表数据来自后台,需要联网动态获取后台数据,然后加载到报表中显示。Android这边报表方面的实现一直在考虑是否需要谷歌提供的第三方库achartengine来实现,但考虑到和IOS的一致问题。同时,achartengine实现的报表效果并不是那么美观。鉴于FusionCharts在IOS上实现的效果非常不错,决定尝试在android上实现和IOS使用相同的报表组件。
一.什么是FusionCharts
简单来说,FusionCharts是一个Flash的图表组件(同时也可以通过javascrit渲染实现),可以用来制作数据动画图表,还有杠杠的跨平台性。
FusionCharts在Web上实现比较多,还有IOS上也经常用到,但是Android这边的实现,FusionCharts官网只字不提。汗...自己摸索了好一段时间。而且有一个问题,Android这边显示这个报表需要安装Adobe。这是因为Android的WebView组件不支持Flash播放,所以显示报表的时候需要安装Adobe player。这是个瓶颈。不过Android也可以用javaScrit渲染实现,但是会有细微的差异。Flash动画播放也会有一定的缺陷。不过,总体来说,还是可以接受。
FusionCharts的官网和源生组件下载地址:http://www.fusioncharts.com,http://www.fusioncharts.com/download/
二.Android上的实现。
1.前期准备工作:
链接到FusionCharts源生组件下载地址,下载相关的源生组件,包括有各种类型报表对应的js文件和swf(flash文件)。
将需要用到的报表类型组件添加到android工程asset目录下(可在asset目录下再创建个子目录存放js或swf)。
如下图截图,是本博文android工程中用到的报表组件。
asset-js目录存放的是FusionCharts开发必备的一些js文件,asset-swf存放的是FusionCharts各种类型报表对应的flash文件。asset-data存放的是报表需要显示的静态数据,在xml中定义。这些是写死的数据,在实际开发中,需要动态传递报表数据。
2.本博文实现的android demo效果如下截图(报表加载时会有动画效果)
3.MainActivity代码如下:
package com.eking.android.fusionchartsjs; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.AlertDialog; import android.app.ListActivity; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnClickListener; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.ListView; import android.widget.SimpleAdapter; public class MainActivity extends ListActivity { private String[] mChartsName; private String[] mChartsSign; private Intent mIntent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initialize(); setListAdapter(new SimpleAdapter(this, getListValues(), android.R.layout.simple_list_item_1, new String[] { "name" }, new int[] { android.R.id.text1 })); } private void initialize() { mChartsName = new String[] { "折线图" , "面积图" , "2D柱状图" , "3D柱状图" , "2D饼图" , "3D饼图" , "多重数据堆积图" , "组合图" , "不同单位的多数据集组合图", "散点图", "气泡图", "网格图表", "中国地图"}; mChartsSign = new String[] { "Line" , "Area2D" , "Column2D" , "Column3D" , "Pie2D" , "Pie3D" , "StackedColumn3D" , "MSCombi2D" , "MSColumn3DLineDY", "Scatter", "Bubble", "SSGrid", "FCMap_China2"}; mIntent = new Intent(this, ChartsShowActivity.class); } private List<Map<String , String>> getListValues() { List<Map<String , String>> values = new ArrayList<Map<String , String>>(); int length = mChartsName.length; for (int i = 0; i < length; i++) { Map<String , String> v = new HashMap<String , String>(); v.put("name", mChartsName[i]); values.add(v); } return values; } @Override protected void onListItemClick(ListView l,View v,int position,long id) { super.onListItemClick(l, v, position, id); if (checkAdobe()) { mIntent.putExtra("chartsSign", mChartsSign[position]); startActivity(mIntent); } else { installAdobe(); } } private boolean checkAdobe() { PackageManager pm = getPackageManager(); List<PackageInfo> infoList = pm.getInstalledPackages(PackageManager.GET_SERVICES); for (PackageInfo info : infoList) { if ("com.adobe.flashplayer".equals(info.packageName)) { return true; } } return false; } private void installAdobe() { new AlertDialog.Builder(this).setTitle(R.string.dialog_title).setPositiveButton("ok", new OnClickListener() { @Override public void onClick(DialogInterface dialog,int which) { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse("market://details?id=com.adobe.flashplayer")); startActivity(intent); finish(); } }).setNegativeButton("cancel", new OnClickListener() { @Override public void onClick(DialogInterface dialog,int which) { } }).show(); } }
package com.eking.android.fusionchartsjs; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.ObjectMapper; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.webkit.WebSettings; import android.webkit.WebView; import com.eking.android.fusionchartsjs.bean.ChartsDataEntity; import com.eking.android.fusionchartsjs.bean.ChartsEntity; import com.eking.android.fusionchartsjs.bean.DataSetEntity; public class ChartsShowActivity extends Activity { private WebView mWebView; private String mFlashType; private String mChartData; private String mJsonString; private ChartsDataEntity mChartsDataEntity; private List<DataSetEntity> mDataSets; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_show_charts); Intent intent = getIntent(); String sign = intent.getStringExtra("chartsSign"); initialize(); loadCharts(sign); } private void initialize() { mWebView = (WebView) findViewById(R.id.webview); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setPluginState(WebSettings.PluginState.ON); mWebView.setHorizontalScrollbarOverlay(true); mWebView.setVerticalScrollbarOverlay(true); // 设置是否支持缩放(手指伸缩操作) mWebView.getSettings().setBuiltInZoomControls(true); // 设置是否显示网络图像 mWebView.getSettings().setBlockNetworkImage(true); // 设置是否启用或禁止WebView访问文件数据 mWebView.getSettings().setAllowFileAccess(true); // 设置默认文本编码格式 mWebView.getSettings().setDefaultTextEncodingName("UTF-8"); // mWebView.loadUrl("file:///android_asset/demo.html"); //初始化Json格式的表数据 /*mJson = "{\"chart\":{\"caption\":\"Monthly Sales Summary\", \"subcaption\":\"For the year 2014\"," + "\"xaxisname\":\"Month\", \"yaxisname\":\"Sales\", \"numberprefix\":\"$\"}," + " \"data\":[{ \"label\":\"January\",\"value\":\"17400\"}, { \"label\":\"February\",\"value\":\"19800\" }," + "{ \"label\":\"March\",\"value\":\"21800\"}, { \"label\":\"April\",\"value\":\"23800\" }," + "{\"label\":\"May\",\"value\":\"29600\"}, { \"label\":\"June\",\"value\":\"27600\" }," + " {\"label\":\"July\",\"value\":\"31800\"}, {\"label\":\"August\",\"value\":\"39700\"}," + "{\"label\":\"September\",\"value\":\"37800\"}, {\"label\":\"October\",\"value\":\"21900\"}," + "{\"label\":\"November\",\"value\":\"32900\" }, {\"label\":\"December\",\"value\":\"39800\"}]}"; */ //定义Json格式的数据 mJsonString = "{\"chart\":{\"caption\":\"2014季度销售报表汇总\", \"subcaption\":\"2014季度\", " + "\"xaxisname\":\"销售月份\", \"yaxisname\":\"销售额度\", \"numberprefix\":\"$\"}," + " \"data\":[{ \"label\":\"一月\",\"value\":\"17400\"}, { \"label\":\"二月\",\"value\":\"19800\" }," + "{ \"label\":\"三月\",\"value\":\"21800\"}, { \"label\":\"四月\",\"value\":\"23800\" }," + "{\"label\":\"五月\",\"value\":\"29600\"}, { \"label\":\"六月\",\"value\":\"27600\" }," + " {\"label\":\"七月\",\"value\":\"31800\"}, {\"label\":\"八月\",\"value\":\"39700\"}," + "{\"label\":\"九月\",\"value\":\"37800\"}, {\"label\":\"十月\",\"value\":\"21900\"}," + "{\"label\":\"十一月\",\"value\":\"32900\" }, {\"label\":\"十二月\",\"value\":\"39800\"}]}"; } /*根据图表的Json格式创建对应的对象, * 对对象赋值,然后再将对象转化Json格式数字 */ private String getJsonFromBean() { mChartsDataEntity = new ChartsDataEntity(); mDataSets = new ArrayList<DataSetEntity>(); //ChartsEntity对象定义了图表属性 ChartsEntity chart = new ChartsEntity(); chart.setCaption("2014季度销售报表汇总"); chart.setSubcaption("2014季度"); chart.setXaxisname("销售月份"); chart.setYaxisname("销售额度"); chart.setNumberprefix("$"); mChartsDataEntity.setChart(chart); initDataSet("一月", "17400"); initDataSet("二月", "19800"); initDataSet("三月", "21800"); initDataSet("四月", "23800"); initDataSet("五月", "29600"); initDataSet("六月", "27600"); initDataSet("七月", "31800"); initDataSet("八月", "39700"); initDataSet("九月", "37800"); initDataSet("十月", "21900"); initDataSet("十一月", "32900"); initDataSet("十二月", "39800"); initDataSet("一月", "17400"); ObjectMapper objectMapper = new ObjectMapper(); try { StringWriter sw = new StringWriter(); JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(sw); //objectMapper.writeValue(jsonGenerator, mChartsDataEntity); jsonGenerator.writeObject(mChartsDataEntity); jsonGenerator.close(); String json = sw.toString(); Log.d("TAG1", "json-->" + json); return json; } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return null; } private void initDataSet(String month, String sale) { //DataSetEntity对象定义了图表的一组数据 DataSetEntity dataSet = new DataSetEntity(); dataSet.setLabel(month); dataSet.setValue(sale); mDataSets.add(dataSet); mChartsDataEntity.setData(mDataSets); } //加载显示图表 private void loadCharts(String sign) { if (sign.endsWith("Line")) { mFlashType = "swf/Line.swf"; mChartData = "data/sale_data.xml"; } else if (sign.equals("Area2D")) { //以Json格式传图表数据 mFlashType = "swf/Area2D.swf"; mChartData = getJsonFromBean(); mWebView.loadDataWithBaseURL("file:///android_asset/", getStringForHtmlByJson(mFlashType, mChartData), "text/html", "utf-8", null); return; } else if (sign.equals("Column2D")) { mFlashType = "swf/Column2D.swf"; mChartData = "data/Column2DData.xml"; } else if (sign.equals("Column3D")) { //以Json格式传图表数据 mFlashType = "swf/Column3D.swf"; mChartData = mJsonString; mWebView.loadDataWithBaseURL("file:///android_asset/", getStringForHtmlByJson(mFlashType, mChartData), "text/html", "utf-8", null); return; } else if (sign.equals("Pie2D")) { mFlashType = "swf/Pie2D.swf"; mChartData = "data/sale_data.xml"; } else if (sign.equals("Pie3D")) { mFlashType = "swf/Pie3D.swf"; mChartData = "data/sale_data.xml"; } else if (sign.equals("StackedColumn3D")) { mFlashType = "swf/StackedColumn3D.swf"; mChartData = "data/sale_data_StackedColumn3D.xml"; } else if (sign.equals("MSCombi2D")) { mFlashType = "swf/MSCombi2D.swf"; mChartData = "data/sale_data_MSCombi2D.xml"; } else if (sign.equals("MSColumn3DLineDY")) { mFlashType = "swf/MSColumn3DLineDY.swf"; mChartData = "data/sale_data_MSColumn3DLineDY.xml"; } else if (sign.equals("Scatter")) { mFlashType = "swf/Scatter.swf"; mChartData = "data/ScatterData.xml"; } else if (sign.equals("Bubble")) { mFlashType = "swf/Bubble.swf"; mChartData = "data/BubbleData.xml"; } else if (sign.equals("SSGrid")) { mFlashType = "swf/SSGrid.swf"; mChartData = "data/sale_data.xml"; } else if (sign.equals("FCMap_China2")) { mFlashType = "swf/FCMap_China2.swf"; mChartData = "data/MapData.xml"; } /*由于FusionCharts.js和flash资源均在资源目录asset下,所以第一个参数需要添加该路径。 * 否则资源将无法加载显示 */ mWebView.loadDataWithBaseURL("file:///android_asset/", getStringForHtml(mFlashType, mChartData), "text/html", "utf-8", null); } /** * 报表数据静态定义在XML文件中,载入组件中显示。 * @param flashType 报表组件类型 * @param chartData xml文件路径 * @return 拼接的html文件代码 */ private String getStringForHtml(String flashType,String chartData) { String summary = "<html>" + "<head>" + "<title>My First chart using FusionCharts XT - Using JavaScript</title>" + "<script type=\"text/javascript\" src=\"js/FusionCharts.js\"></script>" + "</head>" + "<body>" + "<div id=\"chartContainer\">FusionCharts XT will load here!</div>" + "<script type=\"text/javascript\">" + "var myChart = new FusionCharts( '" + flashType + "',\"myChartId\", \"300\",\"400\", \"0\", \"1\" );" + "myChart.setXMLUrl('" + chartData + "');" + "myChart.render(\"chartContainer\");" + "</script>" + "</body>" + "</html>"; return summary; } /** * 传递Json格式数据,作为报表显示数据。实现后台获取动态数据在报表中显示。 * @param flashType 报表组件类型 * @param chartData 报表显示数据 * @return 拼接的html文件代码 */ private String getStringForHtmlByJson(String flashType,String chartData) { String summary = "<html>" + "<head>" + "<title>My First chart using FusionCharts XT - Using JavaScript</title>" + "<script type=\"text/javascript\" src=\"js/FusionCharts.js\"></script>" + "</head>" + "<body>" + "<div id=\"chartContainer\">FusionCharts XT will load here!</div>" + "<script type=\"text/javascript\">" + "var myChart = new FusionCharts( '" + flashType + "',\"myChartId\", \"300\",\"400\", \"0\", \"1\" );" + "myChart.setJSONData('" + chartData + "');" + "myChart.render(\"chartContainer\");" + "</script>" + "</body>" + "</html>"; return summary; } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <WebView android:id="@+id/webview" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>