来源:点击打开链接
简洁明了~
很多时候,做一些统计数据,可能放到列表里面看起来不太直观,这个时候借助于折线图,饼状图,柱形图等报表图形展示,即直观,又可以一目了然的明白数据的趋势变化,而且安卓应用,最主要的就是用户和界面交互,一个良好的界面,对用户来说,能起的作用不亚于内存,响应速度等的优化效果。
如何实现一个自定义的折线图呢,当然,你得继承自View,然后写好你的带参构造方法,并且为了考虑以后代码的复用性,把很多能够影响图表的条件都提到全局变量上,这样就能在以后随时随地为你的控件切换不同的皮肤和效果。例如:X轴, Y轴, 线颜色, 点颜色,填充颜色等等
具体代码:
- private int bgColor = Color.rgb(Integer.parseInt("4d", 16),
- Integer.parseInt("af", 16), Integer.parseInt("ea", 16));// 整体的背景色
- private int singleColumnFillColor = Color.rgb(Integer.parseInt("e7", 16),
- Integer.parseInt("e7", 16), Integer.parseInt("e9", 16));// 单数列的背景色
- private int doubleColumnFillColor = Color.rgb(Integer.parseInt("4d", 16),
- Integer.parseInt("af", 16), Integer.parseInt("ea", 16));// 单数行的背景色
- private int fillDownColor = Color.rgb(Integer.parseInt("45", 16),
- Integer.parseInt("64", 16), Integer.parseInt("bf", 16));// 填充下面部分的背景色
- private int xyLineColor = Color.rgb(Integer.parseInt("a9", 16),
- Integer.parseInt("d8", 16), Integer.parseInt("f5", 16));// 表格的线颜色
- private int chartLineColor = Color.WHITE;// 绘制趋势线的颜色
- private int shadowLineColor = Color.rgb(Integer.parseInt("1a", 16),
- Integer.parseInt("49", 16), Integer.parseInt("84", 16));// 趋势线阴影的颜色
- private String yUnit = "";// Y轴单位
- private boolean isDrawY = false;// 是否绘制Y轴
- private boolean isDrawX = true;// 是否绘制X轴
- private boolean isDrawInsideX = true;// 是否绘制内部的X轴
- private boolean isDrawInsedeY = false;// 是否绘制内部的Y轴
- private boolean isFillDown = false;// 是否填充点的下面部分
- private boolean isFillUp = false;// 是否填充点的上面部分(暂未实现)
- private boolean isAppendX = true;// X轴是否向左突出一点
- private boolean isDemo = true;// 是否demo测试数据
- private int ScreenX;// view的宽度
- private int ScreenY;// view的高度
- private int numberOfX = 6;// 默认X轴放6个值
- private int numberOfY = 5;// 默认Y轴放5个值(越多显示的值越精细)
- private int paddingTop = 30;// 默认上的padding
- private int paddingLeft = 70;// 默认左的padding
- private int paddingRight = 30;// 默认右的padding
- private int paddingDown = 50;// 默认下的padding
- private int appendXLength = 10;// 向左X轴突出的长度
- private float maxNumber = 0;// Y轴最大值
- private List<List<Float>> pointList;// 传入的数据
- private List<Integer> bitmapList;// 传入的点资源id
- private List<Integer> lineColorList;// 传入的线颜色
- private List<String> titleXList;// 传入的X轴标题
- private List<String> titleYList;// 计算得出的Y轴标题
然后当然就要考虑我们的view需要的大小了,这个时候onmeasure方法就需要登场了,在这里面根据你在layout中为view分配的width和height,得出实际我们要给画布的width和height
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int measuredHeight = measureHeight(heightMeasureSpec);
- int measuredWidth = measureWidth(widthMeasureSpec);
- setMeasuredDimension(measuredWidth, measuredHeight);
- ScreenX = measuredWidth;
- ScreenY = measuredHeight;
- }
- private int measureHeight(int measureSpec) {
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
- int result = 300;
- if (specMode == MeasureSpec.AT_MOST) {
- result = specSize;
- }
- else if (specMode == MeasureSpec.EXACTLY) {
- result = specSize;
- }
- return result;
- }
- private int measureWidth(int measureSpec) {
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
- int result = 450;
- if (specMode == MeasureSpec.AT_MOST) {
- result = specSize;
- }
- else if (specMode == MeasureSpec.EXACTLY) {
- result = specSize;
- }
- return result;
- }
接着,就要考虑实现折现图的画布了,也就是ondraw方法,先考虑下拆分折线图;我们可以想下,折线图可以拆成几部分呢?可能看上面定义的全局变量能够发现我这里把折线图表拆分为了
1、最左侧的Y轴
2、最底部的X轴
3、图表内部的Y轴和X轴
4、折线图
5、标记点
这样,我们就明确了我们需要在ondraw里面画什么了(各种draw图形)
1、画Y轴
2、画X轴
3、画内部的X,Y轴
4、画折线
5、画点
当然,明确这一点还不够,因为我们知道在画布中draw的时候需要x和y的位置,这个位置怎么来呢,而且怎么根据外面传进来的值换算成画布中的点
这就需要用数学转换下啦:
计算X轴平均后的坐标
- private List<Point> initNumberOfX() {
- int num = (ScreenX - paddingLeft - paddingRight) / (numberOfX - 1);
- List<Point> list = new ArrayList<Point>();
- for (int i = 0; i < numberOfX; i++) {
- Point point = new Point();
- point.y = ScreenY - paddingDown - paddingTop;
- point.x = paddingLeft + num * i;
- list.add(point);
- }
- return list;
计算Y轴平均后的坐标
- private List<Point> initNumberOfY() {
- int num = (ScreenY - paddingDown - paddingTop) / (numberOfY - 1);
- List<Point> list = new ArrayList<Point>();
- for (int i = 0; i < numberOfY; i++) {
- Point point = new Point();
- point.x = ScreenX - paddingLeft - paddingRight;
- point.y = ScreenY - paddingDown - paddingTop - num * i;
- list.add(point);
- }
- return list;
- }
拿到坐标了,这样折现的背景表格框就完全可以画出来了
- if (isDrawX) {
- int appendX = 0;
- if (isAppendX) {
- appendX = appendXLength;
- }
- canvas.drawLine(paddingLeft - appendX, paddingTop + listY.get(0).y, listY.get(0).x
- + paddingLeft,
- paddingTop + listY.get(0).y, paint);
- }
- if (isDrawY) {
- canvas.drawLine(listX.get(0).x, paddingTop, listX.get(0).x, listX.get(0).y + paddingTop
- , paint);
- }
- if (isDrawInsedeY) {// 绘制纵向的
- for (Point point : listX) {
- if (!isDrawX) {
- isDrawX = !isDrawX;
- continue;
- }
- canvas.drawLine(point.x, paddingTop, point.x, point.y + paddingTop, paint);
- }
- }
- if (isDrawInsideX) {// 绘制横向的
- for (Point point : listY) {
- if (!isDrawY) {
- isDrawY = !isDrawY;
- continue;
- }
- int appendX = 0;
- if (isAppendX) {
- appendX = appendXLength;
- }
- canvas.drawLine(paddingLeft - appendX, paddingTop + point.y, point.x + paddingLeft,
- paddingTop + point.y, paint);
- }
- }
接下来为Y轴找到最大值以及它的每个标题
- private void setYTitle(List<Point> listY, Canvas canvas) {
- Paint paint = new Paint();
- paint.setColor(Color.WHITE);
- if (pointList == null) {
- titleYList = new ArrayList<String>();
- for (int i = 1; i <= numberOfY; i++) {
- titleYList.add(String.valueOf(100 / i));
- }
- } else {
- for (int i = 0; i < pointList.size(); i++) {
- for (int j = 0; j < pointList.get(i).size(); j++) {
- if (pointList.get(i).get(j) > maxNumber) {
- maxNumber = pointList.get(i).get(j);
- }
- }
- }
- maxNumber = maxNumber + maxNumber / 3;//最大值
- titleYList = new ArrayList<String>();
- for (int i = 0; i < numberOfY; i++) {
- titleYList.add(String.valueOf((int) (0 + i * (maxNumber / (numberOfY - 1)))));
- }
- }
- for (int i = 0; i < numberOfY; i++) {
- int appendX = 0;
- if (isAppendX) {
- appendX = appendXLength;
- }
- if (i != 0) {
- canvas.drawText(titleYList.get(i), paddingLeft - appendX - paddingLeft / 3,
- paddingTop
- + listY.get(i).y, paint);
- } else {
- canvas.drawText(titleYList.get(i) + yUnit,
- paddingLeft - appendX - paddingLeft / 3, paddingTop
- + listY.get(i).y, paint);
- }
- }
- }
同样,有了坐标,设置X轴的标题也很简单啦,就不复述了
接着就该考虑怎么将外面传递进来的值转换成图里面的坐标了
- private List<List<Point>> countListPosition(List<Point> listX) {
- List<List<Point>> positionList = new ArrayList<List<Point>>();
- if (pointList == null) {
- pointList = new ArrayList<List<Float>>();
- List<Float> pointInList = new ArrayList<Float>();
- for (int i = 0; i < numberOfX; i++) {
- pointInList.add(0f);
- }
- pointList.add(pointInList);
- }
- for (int i = 0; i < pointList.size(); i++) {
- List<Point> positionInList = new ArrayList<Point>();
- for (int j = 0; j < pointList.get(i).size(); j++) {
- Point point = new Point();
- Float z = pointList.get(i).get(j);
- point.x = listX.get(j).x;
- point.y = listX.get(j).y + paddingTop
- - (int) ((listX.get(j).y) * (float) z / (float) maxNumber);
- positionInList.add(point);
- }
- positionList.add(positionInList);
- }
- return positionList;
- }
这样拿到每个点的坐标后是不是整个折线图就可以画出来了
就是各种drawline啊,draw点啊
- private void drawChart(Canvas canvas, List<List<Point>> positionList) {
- Paint paint = new Paint();
- paint.setAntiAlias(true);
- paint.setColor(chartLineColor);
- paint.setStrokeWidth(3);// 默认线宽为3
- Paint shadowPaint = new Paint();
- shadowPaint.setAntiAlias(true);
- shadowPaint.setColor(shadowLineColor);
- shadowPaint.setStrokeWidth(1);// 默认线宽为3
- shadowPaint.setAlpha(178);
- for (int i = 0; i < positionList.size(); i++) {
- if (lineColorList != null && lineColorList.get(i) != null) {
- paint.setColor(lineColorList.get(i));
- }
- for (int j = 0; j < positionList.get(i).size() - 1; j++) {
- canvas.drawLine(positionList.get(i).get(j).x, positionList.get(i).get(j).y + 2,
- positionList.get(i).get(j + 1).x, positionList.get(i).get(j + 1).y + 2,
- shadowPaint);//画折线的阴影
- canvas.drawLine(positionList.get(i).get(j).x, positionList.get(i).get(j).y,
- positionList.get(i).get(j + 1).x, positionList.get(i).get(j + 1).y, paint);//画折现
- }
- }
- }
这样,一个你自己定义的折线图就出来了,里面还有一些填充,覆盖等draw,通过这些美化后最终实现结果就是:
当然:有更好的第三方控件我们也可以使用,例如AChartEngine,这个控件也非常好用滴~
0分源码下载地址:点击打开链接