公司的日报系统中,有个查看报表功能。但是现在只能查看表格形式的数据,不直观。


android原生图表开发 安卓图表制作app_android原生图表开发

这次的工作就是将数据画成图表,数据可视化工作。


开源图表库项目简介

之前做web开发时,有一个项目是vo,visual office,给客户网上办公用的。里面也涉及到数据可视化的工作,那时候了解到了有d3.js这样一个图表库。那么安卓上有没有类似的图表库呢?


我上github上找了下,找到了如下:


1. MPAndroidChart
https://github.com/PhilJay/MPAndroidChart 用的比较多的一个库。支持Android 2.2以上的项目。
2. achartengine
http://www.achartengine.org/ 比较老的一个库了 
3. XCL- Charts
https://github.com/xcltapestry/XCL-Charts
XCL-Charts基于Android原生Canvas来绘制各种图表,使用简便,定制灵活。
4. WilliamChart
https://github.com/diogobernardino/WilliamChart
绘制图表的库,支持LineChartView、BarChartView和StackBarChartView三中图表类型,并且支持 Android 2.2及以上的系统。有很漂亮的动画。
5. HelloCharts for Android
https://github.com/lecho/hellocharts-android

支持折线图、柱状图、饼图、气泡图、组合图;支持预览、放大缩小,滚动,部分图表支持动画;支持 Android 2.2 以上。


图表库的应用


1.导入jar包

在eclipse中导入MPChartLib之后,build一下,拿到bin文件夹下的jar包。


android原生图表开发 安卓图表制作app_android原生图表开发_02


将jar包放到目标项目的lib文件夹下,右击add to build path


2.柱状图的绘画

创建一个ReportChartsActivity,用来进行数据可视化的处理。为其创建界面activity_report_chart.xml如图


android原生图表开发 安卓图表制作app_报表_03


设置全屏

requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏
		setContentView(R.layout.activity_report_chart);

设置横屏

@Override
		protected void onResume() {
		 if(getRequestedOrientation()!=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
		  setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
		 }
		 super.onResume();
		}


图表初始化

private void initChart() {
	// TODO Auto-generated method stub
	//混合柱状图的初始化
        arChart = (BarChart) findViewById(R.id.chart_bar);
        barChart.setDrawBarShadow(false);
        barChart.setDrawGridBackground(false);
<span >	</span>barChart.setDrawHorizontalGrid(false);
        barChart.setDrawYValues(true);			//画出X,Y坐标系
	barChart.setDescription("");			//说明
	barChart.setMaxVisibleValueCount(40);	//一屏超过25列时不显示具体数值,设置超过60无效
	MyValueFormatter valueformatter = new MyValueFormatter();
	barChart.setValueFormatter(valueformatter); //设置自定义格式化方式
//	barChart.setValueFormatter(new LargeValueFormatter());
	barChart.setDrawValuesForWholeStack(false);  //是否显示每部分的数字,false则为显示和
	barChart.set3DEnabled(false); 			//3D视图
	barChart.setPinchZoom(false);   		//是否只能根据X,Y轴放大缩小
	barChart.setDrawBarShadow(false);		//设置该列空白部分是否用灰色补全
	
		//X,Y轴设定
	YLabels yLabels = barChart.getYLabels();
<span >	</span>yLabels.setPosition(YLabelPosition.LEFT);
        yLabels.setLabelCount(5);
        yLabels.setFormatter(valueformatter);
        XLabels xLabels = barChart.getXLabels();
        xLabels.setPosition(XLabelPosition.BOTTOM);
        xLabels.setCenterXLabelText(true);
        
	}


取得数据

在活动开始的时候,就向服务器发送一个请求,然后我们得到返回的json数据。


private void init() {
		handler=new ReportDetailJsonHandler();
		formUtil = new GetServerReportInfoUtil(handler, this);
		startMonth=getIntent().getExtras().getString("startMonth");
		endMonth=getIntent().getExtras().getString("endMonth");
		currentYear=getIntent().getExtras().getString("currentYear");
		formUtil.getJsonInfoIdFromServer(startMonth,endMonth ,currentYear);
	}

	class ReportDetailJsonHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);
			switch (msg.what) {
				...
				...
				...
			case HandlerCASE.MSG_DONE_SECOND:
				for (ReportDetailInfoBean bean : formUtil.getResponseForm()
						.getData()) {
					if (!"部门名称".equals(bean.getAttr1())) {
						dataList.add(bean);
					}
				}
				drawChart();
				break;
			case HandlerCASE.MSG_NODATA:
				...
				...
				...
			default:
				break;
			}
		}
	}


至此我们得到了List<ReportDetailInfoBean> dataList


画出图表

然后根据dataList画出我们要的图表。于是调用drawChart().


private void drawChart() {
		// TODO Auto-generated method stub
		int StartMonth = Integer.valueOf(startMonth); //开始月份
		int SumMonth = Integer.valueOf(endMonth) - StartMonth + 1;		//总月份
		name = new ArrayList<String>();
		yVals = new ArrayList<ArrayList<BarEntry>>();
		
		for(int n = 0; n < SumMonth; n++){
			yVals.add(new ArrayList<BarEntry>());
		}
		name.add(dataList.get(0).getAttr1());
		for (int i = 0; i < dataList.size(); i++) {
			ReportDetailInfoBean bean = dataList.get(i);
			if(!name.get(name.size() - 1).equals(bean.getAttr1())){
				name.add(bean.getAttr1());
			}
		}
		
		int month=0;
		int barIndex=0;
		int dataIndex=0;
		for (int i = 0; i < name.size()*SumMonth; i++) {
			ReportDetailInfoBean bean = dataList.get(dataIndex);
			if (month>=SumMonth) {
				month=0;
				barIndex++;
			}
			if (month+StartMonth==Integer.valueOf(bean.getAttr2())) {
				yVals.get(month).add(new BarEntry(Form(bean), barIndex));
				dataIndex++;
			} else {
				yVals.get(month).add(new BarEntry(new float[] {0,0,0}, barIndex));
			}
			month++;
		}
		
		ArrayList<BarDataSet> dataSets = new ArrayList<BarDataSet>();
		for(int n = 0; n < SumMonth; n++){
			BarDataSet set = new BarDataSet(yVals.get(n), (StartMonth + n) + "月数据");
			set.setStackLabels(new String[] {"已完成", "进行中", "未开始"});
			set.setColors(colorList.get(n));
			dataSets.add(set);
		}
		
		BarData data = new BarData(name, dataSets);
        	data.setGroupSpace(110f);
        
       		barChart.setData(data);
        	barChart.animateY(500);
        	barChart.invalidate();
        	initPieChart();
        
	//BarData的各个属性,未设置StackLables直接设置此项会报空指针异常
        Legend legend = barChart.getLegend();
        legend.setPosition(LegendPosition.RIGHT_OF_CHART_INSIDE);
        legend.setFormSize(8f);
        legend.setTextSize(8f);
        legend.setFormToTextSpace(4f);
        legend.setXEntrySpace(6f);
	}


效果图:

android原生图表开发 安卓图表制作app_报表_04




3.饼状图的绘画

我们用的是堆积条形图,现在我们添加一个功能在:在单击柱状图的时候,弹出饼状图现实各个部分所占比例。


设置监听器

在initChart()中为barChart设置单击事件


barChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
			
			@Override
			public void onValueSelected(Entry entry, int barIndex) {
				// TODO Auto-generated method stub
				if (entry == null)
		            return;
				float[] vals = ((BarEntry)entry).getVals();
				String dev = name.get(entry.getXIndex());
				String month = (Integer.valueOf(startMonth) + barIndex) + "";
				ArrayList<String> choosen = new ArrayList<String>();
				ArrayList<Entry> yVals = new ArrayList<Entry>();
				choosen.add("已完成");
				choosen.add("进行中");
				choosen.add("未开始");
				for(int n = 0; n < vals.length; n++){
					if (vals[n]!=0) {
						yVals.add(new Entry(vals[n], n));
					}
				}
				pieChart.setCenterText(dev + "\n" + month + "月数据一览");
				PieDataSet dataSet = new PieDataSet(yVals, "");
				dataSet.setColors(ColorTemplate.APRIL_COLORS);
				PieData data = new PieData(choosen, dataSet);
				pieChart.setData(data);
				pieChart.invalidate();				
				Legend legend = pieChart.getLegend();		//说明模块设置
		        legend.setPosition(LegendPosition.RIGHT_OF_CHART);
		        legend.setTextColor(Color.WHITE);
		        legend.setXEntrySpace(7f);
		        legend.setYEntrySpace(5f);
			piechart.showAtLocation((View)findViewById(R.id.chart), Gravity.CENTER, 0, 0);
		}


初始化饼状图

可以看到刚刚的代码中,在drawChart()中有个initPieChart(),这个就是初始化饼状图的方法。


private void initPieChart(){
		//PieChart的PopupWindow初始化
        View view = ReportChartsActivity.this.getLayoutInflater()
        		.inflate(R.layout.popup_report_chart_pie, null);
        
        piechart = new PopupWindow(view, fins, fins);
        piechart.setFocusable(true);
        piechart.setOutsideTouchable(false);
        piechart.setBackgroundDrawable(new BitmapDrawable());
        piechart.setAnimationStyle(R.style.AnimationFade);
        pieChart = (PieChart) view.findViewById(R.id.chart_pie);
        pieChart.setHoleRadius(60f);
        pieChart.setDescription("");
        pieChart.setDrawYValues(true);		
        pieChart.setDrawCenterText(true);	//中间字
        pieChart.setDrawHoleEnabled(true);
        pieChart.setRotationAngle(0);
        pieChart.setDrawXValues(true);		//画出各部分说明
        pieChart.setRotationEnabled(false);	//可旋转
        pieChart.setUsePercentValues(false);	//显示百分数
	}


效果图:

android原生图表开发 安卓图表制作app_报表_05




4.折线图的绘画

柱状图饼状图都有了,现在我们要查看单个部门的数据趋势。这个时候用折线图来处理吧。


入口怎么处理呢?用OptionsMenu吧。


@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// TODO Auto-generated method stub
		getMenuInflater().inflate(R.menu.chart, menu);
		return super.onCreateOptionsMenu(menu);
	}
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// TODO Auto-generated method stub
        int id = item.getItemId();
        if (id == R.id.action_chart) {
        	Intent intent=new Intent(ReportChartsActivity.this, ReportDetialChartsActivity.class);
			Bundle detailData = new Bundle();
			detailData.putString("reportDetailTitle", "部门计划折线图");
			detailData.putSerializable("departmentName", name);
			detailData.putSerializable("dataList",  (Serializable) dataList);
			intent.putExtras(detailData);
        	startActivity(intent);
            return true;
        }
		return super.onOptionsItemSelected(item);
	}


我们要新写一个yReportDetialChartsActivit活动,然后再写出对应的界面,由于步骤差不多就略过了。这里可以看到我将ReportChartsActivity中已经取得的数据直接塞到Intent中带给下个活动了,这样ReportDetialChartsActivity中我就不再需要发送请求来取得dataList了。


布局是这样的:


android原生图表开发 安卓图表制作app_android原生图表开发_06


图表上方添加一个下拉菜单选择部门,然后下方显示对应部门的折线图。


初始化数据

private void init() {
		String titleText = getIntent().getExtras().getString("reportDetailTitle");
		name=(ArrayList<String>) getIntent().getExtras().getSerializable("departmentName");
		dataList=(List<ReportDetailInfoBean>) getIntent().getExtras().getSerializable("dataList");
	}

初始化折线图

private void initChart() {
		// TODO Auto-generated method stub
        lineChart = (LineChart) findViewById(R.id.chart1);
        lineChart.setOnChartValueSelectedListener(this);
        //是否显示网格线
        lineChart.setDrawGridBackground(false);
        // mChart.setStartAtZero(true);
        // disable the drawing of values into the chart 画出X,Y坐标系
        lineChart.setDrawYValues(true);
        lineChart.setMaxVisibleValueCount(30);
        // enable value highlighting
        lineChart.setHighlightEnabled(true);
        lineChart.setHighlightIndicatorEnabled(false);
        // enable touch gestures
        lineChart.setTouchEnabled(true);
        // enable scaling and dragging
        lineChart.setDragEnabled(true);
        lineChart.setScaleEnabled(true);
        // if disabled, scaling can be done on x- and y-axis separately 是否只能根据X,Y轴放大缩小
        lineChart.setPinchZoom(false);
        lineChart.setDescription("");
        // create a custom MarkerView (extend MarkerView) and specify the layout
        // to use for it
        MyMarkerView mv = new MyMarkerView(this, R.layout.custom_marker_view);
        // define an offset to change the original position of the marker
        // (optional)
        mv.setOffsets(-mv.getMeasuredWidth() / 2, -mv.getMeasuredHeight());
        // set the marker to the chart
        lineChart.setMarkerView(mv);
        dep_tv.setText(name.get(0));
        setData(name.get(0));
	}


设置数据

private void setData(String department){
			ArrayList<String> xVals = new ArrayList<String>();
			ArrayList<LineDataSet> dataSets = new ArrayList<LineDataSet>();
			ArrayList<Entry> values1 = new ArrayList<Entry>();
			ArrayList<Entry> values2 = new ArrayList<Entry>();
			ArrayList<Entry> values3 = new ArrayList<Entry>();
			for(int i = 0; i < dataList.size(); i++){
				ReportDetailInfoBean reportDetail = dataList.get(i);
				if(reportDetail.getAttr1().equals(department)){
					xVals.add(reportDetail.getAttr2());
					values1.add(new Entry(Integer.valueOf(reportDetail.getAttr4()), xVals.size() - 1));
					values2.add(new Entry(Integer.valueOf(reportDetail.getAttr5()), xVals.size() - 1));
					values3.add(new Entry(Integer.valueOf(reportDetail.getAttr3()), xVals.size() - 1));
				}
			}
			LineDataSet d1 = new LineDataSet(values1, "已完成");
			LineDataSet d2 = new LineDataSet(values2, "进行中");
			LineDataSet d3 = new LineDataSet(values3, "总任务");
			d1.setColor(ColorTemplate.VORDIPLOM_COLORS[2]);
			d1.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[2]);
			d1.setLineWidth(2.5f);
			d1.setCircleSize(4f);
			d2.setColor(ColorTemplate.VORDIPLOM_COLORS[3]);
			d2.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[3]);
			d2.setLineWidth(2.5f);
			d2.setCircleSize(4f);
			d3.setColor(ColorTemplate.VORDIPLOM_COLORS[4]);
			d3.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[4]);
			d3.setLineWidth(2.5f);
			d3.setCircleSize(4f);
			
			dataSets.add(d1);
			dataSets.add(d2);
			dataSets.add(d3);
	
	        LineData data = new LineData(xVals, dataSets);
	        lineChart.setData(data);
	        lineChart.animateY(500);
	        lineChart.invalidate();
		}



设置markView

class MyMarkerView extends MarkerView {
	    private TextView tvContent;
	    public MyMarkerView(Context context, int layoutResource) {
	        super(context, layoutResource);
	        tvContent = (TextView) findViewById(R.id.tvContent);
	    }
	    // callbacks everytime the MarkerView is redrawn, can be used to update the
	    // content
	    @Override
	    public void refreshContent(Entry e, int dataSetIndex) {
	        if (e instanceof CandleEntry) {
	            CandleEntry ce = (CandleEntry) e;
	            tvContent.setText("" + Utils.formatNumber(ce.getHigh(), 0, true));
	        } else {
	            tvContent.setText("" + Utils.formatNumber(e.getVal(), 0, true));
	        }
	    }
	}


效果图:


android原生图表开发 安卓图表制作app_android_07


实现部门选择

在init()方法中,设置部门的单击事件。


private void init() {
		String titleText = getIntent().getExtras().getString("reportDetailTitle");
		name=(ArrayList<String>) getIntent().getExtras().getSerializable("departmentName");
		dataList=(List<ReportDetailInfoBean>) getIntent().getExtras().getSerializable("dataList");
		
		setTitleBarText(titleText);
		initTitleBarBack();
		
		dep_lin = (LinearLayout) findViewById(R.id.dep_lin);
		dep_img = (ImageView) findViewById(R.id.dep_img);
		dep_tv = (TextView) findViewById(R.id.dep_tv);
		dep_tv.setText(name.get(0));
		View layout_area = LayoutInflater.from(ReportDetialChartsActivity.this).inflate(
				R.layout.pop_list, null);
		ListView listview = (ListView) layout_area.findViewById(R.id.listView1);
		listview.setAdapter(getMenuAdapter(name));
		listview.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
					long arg3) {
				menu_dep.dismiss();
				currentDep = name.get(arg2);
				dep_tv.setText(currentDep);
				setData(currentDep);
			}
		});


		menu_dep = new PopupWindow(layout_area,
				WindowManager.LayoutParams.MATCH_PARENT,
				WindowManager.LayoutParams.WRAP_CONTENT);
		menu_dep.setFocusable(true);


		menu_dep.setBackgroundDrawable(getResources().getDrawable(
				android.R.color.transparent));
		menu_dep.setOnDismissListener(new OnDismissListener() {


			@Override
			public void onDismiss() {
				dep_img.setBackgroundResource(R.drawable.arrow_blue_down);
				dep_tv.setTextColor(getResources().getColor(
						R.color.textcontentcolor));
			}
		});
		menu_dep.setAnimationStyle(R.style.AnimationFade);
		
		dep_lin.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				menu_dep.showAsDropDown(dep_lin);
				dep_img.setBackgroundResource(R.drawable.arrow_blue_up);
			}
		});
	}

效果图


android原生图表开发 安卓图表制作app_android原生图表开发_08

折线图效果

android原生图表开发 安卓图表制作app_List_09


完成效果图

android原生图表开发 安卓图表制作app_android原生图表开发_10

android原生图表开发 安卓图表制作app_android_11