我们在安卓开发中,有时会用到统计图表的功能,而曲线绘制是其中比较典型的一种,一般是利用给定的坐标点集和安卓自带的绘图模块进行绘制,直接得到的是一张完整的静态的曲线图。但有时,我们需要动态绘制一些曲线图,就像我们打开电脑的任务管理器,里面有一个CPU使用记录的动态变化的带网格的曲线图,对于这一类的曲线绘制,安卓SDK自带的绘图模块貌似就不那么好用了。
在这里,我就利用Handler+timer机制和第三方开发包achartengine实现动态绘制安卓手机充放电曲线的应用。
1、下载第三方开发包achartengine,也就是jar包,可以在网上找到下载源,我下载的是achartengine-1.1.0.jar;
2、在创建的应用工程文件中引入该jar包,以Eclipse为例方法是:在包资源管理器目录中右键单击已创建的项目名称——>构建路径——>配置构建路径——>Java构建路径——>库——>外部JAR,然后找到achartengine包的存放路径,找到achartengine-1.1.0.jar文件,点击打开即完成了该包的引用,这时会在项目目录下出现一个名为引用的库文件,里面就是我们刚才引入的jar包。
3、接下来就可以按正常步骤开发该应用了,需要注意的是:需要在主类中引入jar中绘图需要的库文件,如下:
import org.achartengine.ChartFactory; import org.achartengine.GraphicalView; import org.achartengine.chart.PointStyle; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer;
同时需要在AndroidManifest.xml文件中加上语句:
4、接下来需要定义一些绘图的变量和样式:
private XYSeries series; private XYMultipleSeriesDataset mDataset; private GraphicalView chart; private XYMultipleSeriesRenderer renderer; private Context context, context1; private int addX = -1, addY; int[] xv = new int[720];// X轴数组元素个数 int[] yv = new int[720];// Y轴数组元素个数 context = getApplicationContext(); // 这里获得main界面上的布局,下面会把图表画在这个布局里面 LinearLayout layout = (LinearLayout) findViewById(R.id.linearLayout1); // 这个类用来放置曲线上的所有点,是一个点的集合,根据这些点画出曲线 series = new XYSeries(title); // 创建一个数据集的实例,这个数据集将被用来创建图表 mDataset = new XYMultipleSeriesDataset(); // 将点集添加到这个数据集中 mDataset.addSeries(series); // 以下都是曲线的样式和属性等等的设置,renderer相当于一个用来给图表做渲染的句柄 int color = Color.GREEN; PointStyle style = PointStyle.CIRCLE; renderer = buildRenderer(color, style, true); // 设置好图表的样式(横轴时间分钟值,纵轴电压毫伏值) setChartSettings(renderer, "X", "Y", 0, 240, 3400, 4400, Color.WHITE,Color.WHITE); // 生成图表 chart = ChartFactory.getLineChartView(context, mDataset, renderer); // 将图表添加到布局中去 layout.addView(chart, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); protected XYMultipleSeriesRenderer buildRenderer(int color,PointStyle style, boolean fill)// 配置绘图属性 { XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); // 设置图表中曲线本身的样式,包括颜色、点的大小以及线的粗细等 XYSeriesRenderer r = new XYSeriesRenderer(); r.setColor(color); r.setPointStyle(style); r.setFillPoints(fill); r.setLineWidth((float) 1);// 线粗尺寸 renderer.addSeriesRenderer(r); return renderer; } protected void setChartSettings(XYMultipleSeriesRenderer renderer, String xTitle, String yTitle, double xMin, double xMax, double yMin, double yMax, int axesColor, int labelsColor) { // 有关对图表的渲染可参看api文档 renderer.setChartTitle(title); renderer.setXTitle(xTitle); renderer.setYTitle(yTitle); renderer.setXAxisMin(xMin); renderer.setXAxisMax(xMax); renderer.setYAxisMin(yMin); renderer.setYAxisMax(yMax); renderer.setAxesColor(axesColor); renderer.setLabelsColor(labelsColor); renderer.setShowGrid(true); renderer.setGridColor(Color.GREEN);// 曲线颜色 renderer.setXLabels(20); renderer.setYLabels(10); renderer.setChartTitle("时间/电压变化曲线图");// 图表名称 renderer.setXTitle("时间(min)");// 横坐标名称 renderer.setYTitle("电压(mv)");// 纵坐标名称 renderer.setYLabelsAlign(Align.RIGHT); renderer.setPointSize((float) 1.5);// 设置点的大小 renderer.setShowLegend(false); renderer.setPanEnabled(true, false);// 设置滑动,这边是横向可以滑动,纵向不可滑动 renderer.setZoomEnabled(true, false);// 设置缩放,横向可以,纵向不可以 // renderer.setZoomLimits(new double[] { 0, 720, 3400, 4400 });//设置缩放的范围 // renderer.setPanLimits(new double[] { -0.5, 720, 3400, 4400 // });//设置拉动的范围 }
5、动态绘制的实现:动态绘制主要需要实现两方面的内容,一是更新绘图的方法、二是动态绘图时间任务的定义。
1)更新绘图方法:
private void updateChart()// 更新绘图方法 { // 设置好下一个需要增加的节点 addX = (int) (addX + 1); addY = BatteryV; // 移除数据集中旧的点集 mDataset.removeSeries(series); // 判断当前点集中到底有多少点,因为屏幕总共只能容纳240个,所以当点数超过240时,长度永远是240 int length = series.getItemCount(); if (length > 720) { length = 720; } // 将旧的点集中x和y的数值取出来放入backup中,并且将x的值加1,造成曲线向右平移的效果 for (int i = 0; i < length; i++) { xv[i] = (int) series.getX(i); yv[i] = (int) series.getY(i); } // 点集先清空,为了做成新的点集而准备 series.clear(); // 将新产生的点首先加入到点集中,然后在循环体中将坐标变换后的一系列点都重新加入到点集中 // 这里可以试验一下把顺序颠倒过来是什么效果,即先运行循环体,再添加新产生的点 series.add(addX, addY); for (int k = 0; k < length; k++) { series.add(xv[k], yv[k]); } // 在数据集中添加新的点集 mDataset.addSeries(series); // 视图更新,没有这一步,曲线不会呈现动态 // 如果在非UI主线程中,需要调用postInvalidate(),具体参考api chart.invalidate(); }
2)时间任务定义:
handler = new Handler() { @Override public void handleMessage(Message msg) { // 刷新图表 updateChart();// 电压变化曲线刷新 updateChart1();// 电量变化曲线刷新 super.handleMessage(msg); } }; task = new TimerTask()// 刷新绘图计时任务配置 { @Override public void run() { Message message = new Message(); message.what = 1; handler.sendMessage(message); } }; timer.schedule(task, 0, 60000);// 以一分钟为时间间隔运行
再发一下完整的代码:
主类:
package com.cfzz.vcd; import java.io.FileOutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import org.achartengine.ChartFactory; import org.achartengine.GraphicalView; import org.achartengine.chart.PointStyle; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer; //import com.cfzz.vqcd.R; //import com.cfzz.wy.R; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Paint.Align; import android.os.BatteryManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import android.view.View.OnClickListener; public class VoltageChangeDraw extends Activity { // protected static final Activity Activity = null; private PowerManager.WakeLock wl; private Timer timer = new Timer(); private long mExitTime = 0; private TimerTask task, task1; private Handler handler; private String title = "Signal Strength"; private XYSeries series, series1; private XYMultipleSeriesDataset mDataset, mDataset1; private GraphicalView chart, chart1; private XYMultipleSeriesRenderer renderer, renderer1; private Context context, context1; private int addX = -1, addY; private int addX1 = -1, addY1; private Button button; int[] xv = new int[720];// X轴数组元素个数 int[] yv = new int[720];// Y轴数组元素个数 int[] xv1 = new int[720];// X轴数组元素个数 int[] yv1 = new int[720];// Y轴数组元素个数 public TextView TV; // ScreenShot screenShot = new ScreenShot(); private int BatteryN; // 目前电量 private int BatteryV; // 电池电压 // private double BatteryT; //电池温度 private String BatteryStatus; // 电池状态 private String BatteryTemp; // 电池使用情况 /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.main); this.button = (Button) this.findViewById(R.id.my_button);// 截图按键定义 // 截图按键监听 this.button.setOnClickListener(new OnClickListener() { public void onClick(View v) { SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd_HH-mm-ss", Locale.US);// 日期格式名定义 String fname = "/sdcard/" + sdf.format(new Date()) + ".png";// 存储路径及文件名定义 View view = v.getRootView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); if (bitmap != null) { System.out.println("bitmap got!"); try { FileOutputStream out = new FileOutputStream(fname); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);// 图片格式及质量输出 System.out.println("file" + fname + "output done."); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("bitmap is NULL!"); } } }); // MyTag可以随便写,可以写应用名称等 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); // 换成PowerManager.SCREEN_DIM_WAKE_LOCK会变暗) PowerManager.WakeLock wl = pm.newWakeLock( PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "MyTest"); wl.acquire();// 开启屏幕常亮 TV = (TextView) findViewById(R.id.TV);// 电池信息打印控件定义 // 注册一个系统 BroadcastReceiver,作为访问电池计量之用,这个不能直接在AndroidManifest.xml中注册 registerReceiver(mBatInfoReceiver, new IntentFilter( Intent.ACTION_BATTERY_CHANGED)); context = getApplicationContext(); context1 = getApplicationContext();// 图2 // 这里获得main界面上的布局,下面会把图表画在这个布局里面 LinearLayout layout = (LinearLayout) findViewById(R.id.linearLayout1); // 这个类用来放置曲线上的所有点,是一个点的集合,根据这些点画出曲线 series = new XYSeries(title); series1 = new XYSeries(title);// 图2 // 创建一个数据集的实例,这个数据集将被用来创建图表 mDataset = new XYMultipleSeriesDataset(); mDataset1 = new XYMultipleSeriesDataset(); // 将点集添加到这个数据集中 mDataset.addSeries(series); mDataset1.addSeries(series1);// 图2 // 以下都是曲线的样式和属性等等的设置,renderer相当于一个用来给图表做渲染的句柄 int color = Color.GREEN; int color1 = Color.RED; PointStyle style = PointStyle.CIRCLE; renderer = buildRenderer(color, style, true); renderer1 = buildRenderer(color1, style, true);// 图2 // 设置好图表的样式(横轴时间分钟值,纵轴电压毫伏值) setChartSettings(renderer, "X", "Y", 0, 240, 3400, 4400, Color.WHITE, Color.WHITE); setChart1Settings(renderer1, "X", "Y", 0, 240, 0, 100, Color.WHITE, Color.WHITE); // 生成图表 chart = ChartFactory.getLineChartView(context, mDataset, renderer); chart1 = ChartFactory.getLineChartView(context1, mDataset1, renderer1);// 图2 // 将图表添加到布局中去,此处的纵向尺寸设置存在一些问题,当两张图都设置WRAP_CONTENT时,在实际界面上只显示第一张图,在这里可根据手 // 机屏幕的大小设置具体的数值(像素点个数),可以保证两张图都能显示在屏幕上 layout.addView(chart, new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT)); layout.addView(chart1, new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT)); // layout.addView(chart, new LayoutParams(LayoutParams.FILL_PARENT, // 400));//525 // layout.addView(chart1, new LayoutParams(LayoutParams.FILL_PARENT, // 400));//图2 525 // 这里的Handler实例将配合下面的Timer实例,完成定时更新图表的功能 handler = new Handler() { @Override public void handleMessage(Message msg) { // 刷新图表 updateChart();// 电压变化曲线刷新 updateChart1();// 电量变化曲线刷新 super.handleMessage(msg); } }; task = new TimerTask()// 刷新绘图计时任务配置 { @Override public void run() { Message message = new Message(); message.what = 1; handler.sendMessage(message); } }; timer.schedule(task, 0, 60000);// 以一分钟为时间间隔运行 task1 = new TimerTask()// 保存绘图计时任务配置 { @Override public void run() { if (BatteryStatus == "充满电" || (BatteryStatus == "放电状态" && BatteryN == 1))// 充/放电完成条件设定 { button.callOnClick();// 保存曲线图按键触发 timer.cancel(); // 停止计时功能 } } }; timer.schedule(task1, 0, 10000);// 以十秒钟为时间间隔运行 } @Override public void onDestroy() // 结束程序声明 { button.callOnClick();// 保存曲线图按键触发 // 当结束程序时关掉Timer timer.cancel(); // wl.release(); // wl = null; super.onDestroy(); } protected XYMultipleSeriesRenderer buildRenderer(int color, PointStyle style, boolean fill)// 配置绘图属性 { XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); // 设置图表中曲线本身的样式,包括颜色、点的大小以及线的粗细等 XYSeriesRenderer r = new XYSeriesRenderer(); r.setColor(color); r.setPointStyle(style); r.setFillPoints(fill); r.setLineWidth((float) 1);// 线粗尺寸 renderer.addSeriesRenderer(r); return renderer; } // 图2 protected XYMultipleSeriesRenderer buildRenderer1(int color1, PointStyle style, boolean fill)// 配置绘图属性 { XYMultipleSeriesRenderer renderer1 = new XYMultipleSeriesRenderer();// 图2 // 设置图表中曲线本身的样式,包括颜色、点的大小以及线的粗细等 XYSeriesRenderer r = new XYSeriesRenderer(); r.setColor(color1); r.setPointStyle(style); r.setFillPoints(fill); r.setLineWidth((float) 1);// 线粗尺寸 renderer1.addSeriesRenderer(r); return renderer1; } protected void setChartSettings(XYMultipleSeriesRenderer renderer, String xTitle, String yTitle, double xMin, double xMax, double yMin, double yMax, int axesColor, int labelsColor) { // 有关对图表的渲染可参看api文档 renderer.setChartTitle(title); renderer.setXTitle(xTitle); renderer.setYTitle(yTitle); renderer.setXAxisMin(xMin); renderer.setXAxisMax(xMax); renderer.setYAxisMin(yMin); renderer.setYAxisMax(yMax); renderer.setAxesColor(axesColor); renderer.setLabelsColor(labelsColor); renderer.setShowGrid(true); renderer.setGridColor(Color.GREEN);// 曲线颜色 renderer.setXLabels(20); renderer.setYLabels(10); renderer.setChartTitle("时间/电压变化曲线图");// 图表名称 renderer.setXTitle("时间(min)");// 横坐标名称 renderer.setYTitle("电压(mv)");// 纵坐标名称 renderer.setYLabelsAlign(Align.RIGHT); renderer.setPointSize((float) 1.5);// 设置点的大小 renderer.setShowLegend(false); renderer.setPanEnabled(true, false);// 设置滑动,这边是横向可以滑动,纵向不可滑动 renderer.setZoomEnabled(true, false);// 设置缩放,横向可以,纵向不可以 // renderer.setZoomLimits(new double[] { 0, 720, 3400, 4400 });//设置缩放的范围 // renderer.setPanLimits(new double[] { -0.5, 720, 3400, 4400 // });//设置拉动的范围 } // 图2 protected void setChart1Settings(XYMultipleSeriesRenderer renderer1, String xTitle, String yTitle, double xMin, double xMax, double yMin, double yMax, int axesColor, int labelsColor) { // 有关对图表的渲染可参看api文档 renderer1.setChartTitle(title); renderer1.setXTitle(xTitle); renderer1.setYTitle(yTitle); renderer1.setXAxisMin(xMin); renderer1.setXAxisMax(xMax); renderer1.setYAxisMin(yMin); renderer1.setYAxisMax(yMax); renderer1.setAxesColor(axesColor); renderer1.setLabelsColor(labelsColor); renderer1.setShowGrid(true); renderer1.setGridColor(Color.GREEN);// 曲线颜色 renderer1.setXLabels(20); renderer1.setYLabels(10); renderer1.setChartTitle("时间/电量变化曲线图");// 图表名称 renderer1.setXTitle("时间(min)");// 横坐标名称 renderer1.setYTitle("电量(%)");// 纵坐标名称 renderer1.setYLabelsAlign(Align.RIGHT); renderer1.setPointSize((float) 1.5);// 设置点的大小 renderer1.setShowLegend(false); renderer1.setPanEnabled(true, false);// 设置滑动,这边是横向可以滑动,纵向不可滑动 renderer1.setZoomEnabled(true, false);// 设置缩放,横向可以,纵向不可以 // renderer1.setZoomLimits(new double[] { 0, 720, 0, 100 });//设置缩放的范围 // renderer1.setPanLimits(new double[] { -0.5, 720, 0, 100 });//设置拉动的范围 } private void updateChart()// 更新绘图方法 { // 设置好下一个需要增加的节点 addX = (int) (addX + 1); addY = BatteryV; // 移除数据集中旧的点集 mDataset.removeSeries(series); // 判断当前点集中到底有多少点,因为屏幕总共只能容纳240个,所以当点数超过240时,长度永远是240 int length = series.getItemCount(); if (length > 720) { length = 720; } // 将旧的点集中x和y的数值取出来放入backup中,并且将x的值加1,造成曲线向右平移的效果 for (int i = 0; i < length; i++) { xv[i] = (int) series.getX(i); yv[i] = (int) series.getY(i); } // 点集先清空,为了做成新的点集而准备 series.clear(); // 将新产生的点首先加入到点集中,然后在循环体中将坐标变换后的一系列点都重新加入到点集中 // 这里可以试验一下把顺序颠倒过来是什么效果,即先运行循环体,再添加新产生的点 series.add(addX, addY); for (int k = 0; k < length; k++) { series.add(xv[k], yv[k]); } // 在数据集中添加新的点集 mDataset.addSeries(series); // 视图更新,没有这一步,曲线不会呈现动态 // 如果在非UI主线程中,需要调用postInvalidate(),具体参考api chart.invalidate(); } // 图2 private void updateChart1()// 更新绘图方法 { // 设置好下一个需要增加的节点 addX1 = (int) (addX1 + 1); addY1 = BatteryN; // 移除数据集中旧的点集 mDataset1.removeSeries(series1); // 判断当前点集中到底有多少点,因为屏幕总共只能容纳240个,所以当点数超过240时,长度永远是240 int length1 = series1.getItemCount(); if (length1 > 720) { length1 = 720; } // 将旧的点集中x和y的数值取出来放入backup中,并且将x的值加1,造成曲线向右平移的效果 for (int j = 0; j < length1; j++) { xv1[j] = (int) series1.getX(j); yv1[j] = (int) series1.getY(j); } // 点集先清空,为了做成新的点集而准备 series1.clear(); // 将新产生的点首先加入到点集中,然后在循环体中将坐标变换后的一系列点都重新加入到点集中 // 这里可以试验一下把顺序颠倒过来是什么效果,即先运行循环体,再添加新产生的点 series1.add(addX1, addY1); for (int l = 0; l < length1; l++) { series1.add(xv1[l], yv1[l]); } // 在数据集中添加新的点集 mDataset1.addSeries(series1); // 视图更新,没有这一步,曲线不会呈现动态 // 如果在非UI主线程中,需要调用postInvalidate(),具体参考api chart1.invalidate(); } /* 创建电池状态广播接收器 */ public BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // 如果捕捉到的action是ACTION_BATTERY_CHANGED, 就运行onBatteryInfoReceiver() if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { BatteryN = intent.getIntExtra("level", 0); // 目前电量 BatteryV = intent.getIntExtra("voltage", 0); // 电池电压 // BatteryT = intent.getIntExtra("temperature", 0); //电池温度 switch (intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN)) { case BatteryManager.BATTERY_STATUS_CHARGING: BatteryStatus = "充电状态"; break; case BatteryManager.BATTERY_STATUS_DISCHARGING: BatteryStatus = "放电状态"; break; case BatteryManager.BATTERY_STATUS_NOT_CHARGING: BatteryStatus = "未充电"; break; case BatteryManager.BATTERY_STATUS_FULL: BatteryStatus = "充满电"; break; case BatteryManager.BATTERY_STATUS_UNKNOWN: BatteryStatus = "未知道状态"; break; } switch (intent.getIntExtra("health", BatteryManager.BATTERY_HEALTH_UNKNOWN)) { case BatteryManager.BATTERY_HEALTH_UNKNOWN: BatteryTemp = "未知错误"; break; case BatteryManager.BATTERY_HEALTH_GOOD: BatteryTemp = "状态良好"; break; case BatteryManager.BATTERY_HEALTH_DEAD: BatteryTemp = "电池没有电"; break; case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: BatteryTemp = "电池电压过高"; break; case BatteryManager.BATTERY_HEALTH_OVERHEAT: BatteryTemp = "电池过热"; break; } TV.setText("目前电量为" + BatteryN + "% --- " + BatteryStatus + "\n" + "电压为" + BatteryV + "mV ----- " + BatteryTemp); } } }; @Override public boolean onKeyDown(int keyCode, KeyEvent event)// 程序按返回键退出处理 { switch (keyCode) { case KeyEvent.KEYCODE_BACK: // 双击退出 // if (isStart == true){isStart = false;}//关闭前确认频闪是否关闭 if ((System.currentTimeMillis() - mExitTime) > 2000) { Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show(); mExitTime = System.currentTimeMillis(); } else { // isStart = false; finish(); } return true; default: break; } return super.onKeyDown(keyCode, event); } }
布局文件:
AndroidManifest.xml配置文件
在该源码中,我们绘制了两条曲线,一条是电压变化曲线,另一条是电量变化曲线,并加一个保存手机屏幕的截图按钮和自动保存时间任务(也可手动保存),用来保存绘制的截图。
运行结果图(充电曲线图):
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。