Android 自定义仪表盘
2017-07-04 10:08
218 查看
最近需要写一个用到各种图表的项目,比较过后决定用hellocharts框架,感觉足够简洁,后来发现这框架里没有仪表盘这个控件,但又不想换其他框架,于是在网上搜索一番,找到一个仪表盘学习demo,尝试后初步改成了所需样式。界面如下:
因为为demo,界面比较丑陋,需要再美化。另代码设计也有很大改进精简空间,待正式项目中再进行修改,此为学习所用。
所参考文章:http://blog.csdn.net/qq_26411333/article/details/52399831
原文是可以触摸改变指针指向,因需求不同,将此功能删除了。
改动后的view不同的是可以设置初始值和终点值,可以设置需要的刻度段数,可以通过设置改变指针指向,当然所有可变量都可抽出设置方法,可跟需求改动。
代码改动过程中,有若干点需要注意:
1、在分多种颜色绘制时,总是出现最后一个颜色覆盖掉之前不同的颜色,查询才知道
canvas.drawPath(linePath, linePaint)中path是包含从绘制起所有路径,所以在最后改变时,也会将之前所有路径改为同一风格,换成其他draw方法即可。
2、绘制过程中,会用到一些三角函数公式,此坐标是以指针起点固定点为原点,水平向右为x轴正方向,竖直向下为y轴正方向,所以第一二三四象限是顺时针定义的。因为外圆内院半径固定,可由任何角度根据三角函数公式获取所需值。PathMeasure等类也有所封装。
全部代码:
package xr.hellochartsdemo.ui.activity.dashboard; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import android.graphics.RectF; import android.renderscript.Sampler; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import java.math.BigDecimal; import static android.R.attr.x; import static android.R.attr.y; /** * Created by wxy . * 仪表盘 */ public class DashboardView extends View { private int width; private int height; private Paint outerCirclePaint;//外层圆的画笔 private Paint outerCirclePaint2;//外层圆的画笔2 private Paint outerCirclePaint3;//外层圆的画笔3 private Paint innerCirclePaint;//内层圆的画笔 private Paint linePaint;//线段画笔 private Paint arrowPaint;//指针画笔 private Paint textPaint;//标注文字 private Paint textPaint2;//目标指针文字 private Path outerCirclePath;//外层圆的Path private Path innerCirclePath;//内层圆的Path private Path linePath;//线段的Path private Path arrowPath;//指针的Path private Path measureArrowPath;//arrowPath借助该Path来保持一定的长度 private RectF outRectF;//用于绘制外层圆 通过四个坐标参数来确定一个矩形的区域。 private RectF innerRectF;//用于绘制内层圆 private int count = 10;//画count根线 private static int outerR = 100;//外部圆环的半径 private static int innerR = (int) (outerR * 0.9f);//内部圆环的半径 private int shortageAngle = 60;//缺失的部分的角度 private int startAngle;//开始的角度 private int sweepAngle;//扫过的角度 private int endAngle; private float[] leftEndPoint;//左侧边界的坐标 private float[] rightEndPoint;//右侧边界的坐标 private float leftEndTan;//左侧边界的tan值 private float rightEndTan;//右侧边界的tan值 private float nowX = 0;//触摸位置的横坐标 private float nowY = 0;//触摸位置的纵坐标 private static float percent = 0.9f;//指针与内层圆的比值 private float arrowLength = innerR * percent;//指针的长度 double aimSweepAngel = 0;//起始角度 起点 float startValue = 0;//默认起始值 float endValue = 100;//默认终止值 private int textCount = count;//标注文字个数 默认 private PathMeasure arrowMeasure;//用于指针的测量 private boolean isColorful = true; private double aimValue = startValue; public DashboardView(Context context) { super(context); initPaint(); initAngle(); } public DashboardView(Context context, AttributeSet attrs) { super(context, attrs); initPaint(); initAngle(); } public DashboardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); initAngle(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; //让指针一开始指向正上方 nowX = 0; nowY = -1; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(width / 2, height / 2); drawOuterCircle(canvas); drawInnerCircle(); drawLine(canvas); drawArrow(canvas); drawText(canvas); drawAimText(canvas); } /** * 外层圆圈 */ private void drawOuterCircle(Canvas canvas) { //一般绘制圆圈的方法,不做介绍了 outerCirclePath = new Path(); if (outRectF == null) { outRectF = new RectF(-outerR, -outerR, outerR, outerR); } outerCirclePath.addArc(outRectF, startAngle, sweepAngle); canvas.drawArc(outRectF, startAngle, sweepAngle, false, outerCirclePaint); if (isColorful) {//是否要表盘是彩色的 canvas.drawArc(outRectF, startAngle, sweepAngle / 5, false, outerCirclePaint); canvas.drawArc(outRectF, startAngle + sweepAngle / 5, 3 * sweepAngle / 5, false, outerCirclePaint2); canvas.drawArc(outRectF, startAngle + sweepAngle / 5 + 3 * sweepAngle / 5, sweepAngle / 5, false, outerCirclePaint3); } } /** * 内层圆圈 */ private void drawInnerCircle() { //一般绘制圆圈的方法,不做介绍了 innerCirclePath = new Path(); if (innerRectF == null) { innerRectF = new RectF(-innerR, -innerR, innerR, innerR); } innerCirclePath.addArc(innerRectF, startAngle, sweepAngle); } /** * 画直线,组成一个类似于弧形的形状 * * @param canvas */ private void drawLine(Canvas canvas) { linePath = new Path(); //用于外层圆的测量 //PathMeasure是一个用来测量Path的类 //创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。 PathMeasure outMeasure = new PathMeasure(outerCirclePath, false); float outlength = outMeasure.getLength(); float[] outPos = new float[2]; //用于内层圆的测量 PathMeasure inMeasure = new PathMeasure(innerCirclePath, false); float inlength = inMeasure.getLength(); float[] inPos = new float[2]; //确定左侧末尾的坐标以及tan值 if (leftEndPoint == null) { leftEndPoint = new float[2]; //通过getPosTan拿到内层圆的左侧末尾坐标 inMeasure.getPosTan(0, leftEndPoint, null); //因为指针要短一点;所以x,y都乘以percent才是指针真正的左侧末尾坐标 leftEndPoint[0] = leftEndPoint[0] * percent; leftEndPoint[1] = leftEndPoint[1] * percent; //确定指针在左侧末尾时的tan值 leftEndTan = leftEndPoint[1] / leftEndPoint[0]; } //确定右侧末尾的坐标以及tan值 if (rightEndPoint == null) { rightEndPoint = new float[2]; //通过getPosTan拿到内层圆的右侧末尾坐标 inMeasure.getPosTan(inlength, rightEndPoint, null); //因为指针要短一点;所以x,y都乘以percent才是指针真正的右侧末尾坐标 rightEndPoint[0] = rightEndPoint[0] * percent; rightEndPoint[1] = rightEndPoint[1] * percent; //确定指针在右侧末尾时的tan值 rightEndTan = rightEndPoint[1] / rightEndPoint[0]; } //用来画多条线段,组成弧形 for (int i = 0; i <= count; i++) { //外层圆当前的弧长 float outNowLength = outlength * i / (count * 1.0f); //当前弧长下对应的坐标outPos outMeasure.getPosTan(outNowLength, outPos, null); //内层圆当前的弧长 float inNowLength = inlength * i / (count * 1.0f); //当前弧长下对应的坐标inPos inMeasure.getPosTan(inNowLength, inPos, null); //moveTo到内层圆弧上的点 linePath.moveTo(outPos[0], outPos[1]); //lineTo到外层圆弧上的点 linePath.lineTo(inPos[0], inPos[1]); if (isColorful) { if (i <= count / 5) { linePaint.setColor(Color.GREEN); } else if (i >= count - count / 5) { linePaint.setColor(Color.RED); } else { linePaint.setColor(Color.BLUE); } } //问题:最后的颜色 会覆盖掉之前的颜色?? //分析:在此循环中 linePath一直在添加直线路径 当i=9时 已经包含所有路径 //证明:把canvas.drawPath(linePath, linePaint); 放循环外 亦可绘制全部小刻标 //结论:所以此方法只要求得outPos、inPos坐标即可 不必此方法 //Path主要用于绘制复杂的图形轮廓,比如折线,圆弧以及各种复杂图案 // canvas.drawPath(linePath, linePaint); canvas.drawLine(outPos[0], outPos[1], inPos[0], inPos[1], linePaint); } // canvas.drawPath(linePath, linePaint); } /** * 绘制指针 * * @param canvas */ private void drawArrow(Canvas canvas) { // double f = Math.toRadians(aimSweepAngel + shortageAngle / 2 - 90); double f = Math.toRadians(aimSweepAngel + startAngle); nowX = (float) Math.cos(f) * arrowLength; nowY = (float) Math.sin(f) * arrowLength; arrowPath = new Path(); arrowPath.reset(); //这时,指针的末尾位置最终确定了,可以绘制了 arrowPath.moveTo(0, 0); arrowPath.lineTo(nowX, nowY); canvas.drawPath(arrowPath, arrowPaint); } /** * 显示指针指向的值 * @param canvas */ private void drawAimText(Canvas canvas) { //中心点下 canvas.drawText(formatDouble(aimValue)+ "", -10 , outerR , textPaint2); } /** * 设置仪表刻度个数 * @param n */ public void setTextCount(int n) { textCount = n; } /** * 显示仪表刻度值 * @param canvas */ public void drawText(Canvas canvas) { float angle = startAngle; double value = startValue; for (int i = 0; i <= textCount; i++) { angle = startAngle + i * sweepAngle / textCount; value = startValue + i * formatFloat((endValue - startValue) / textCount); if (isColorful) { if (i <= textCount / 5) { textPaint.setColor(Color.GREEN); } else if (i >= textCount - textCount / 5) { textPaint.setColor(Color.RED); } else { textPaint.setColor(Color.BLUE); } } drawtext(canvas, angle, value + ""); } } //float处理 private float formatFloat(float f) { BigDecimal bigDecimal = new BigDecimal(f); return bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).floatValue(); } private double formatDouble(double d) { BigDecimal bigDecimal = new BigDecimal(d); return bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); } private void drawtext(Canvas canvas, double angle, String value) { double f = Math.toRadians(angle); float x1 = (float) Math.cos(f) * (innerR - 5); if (x1 > 0) { x1 -= 26; } else if (x1 == 0) { x1 -= 10; } float y1 = (float) Math.sin(f) * (innerR - 5); canvas.drawText(value + "", x1, y1, textPaint); } public void setArrowData(double f) {//根据值转换成角度 再转换成坐标 aimValue = f; double percent = (aimValue - startValue) / (endValue - startValue); double angel = percent * sweepAngle; aimSweepAngel = angel; //0 -> sweepAngle } public void setStartingValue(float startingValue) { this.startValue = startingValue; } public void setEndValue(float endValue) { this.endValue = endValue; } /** * 初始化画笔 */ private void initPaint() { if (outerCirclePaint == null) { outerCirclePaint = new Paint(); outerCirclePaint.setStyle(Paint.Style.STROKE); outerCirclePaint.setStrokeWidth(4); outerCirclePaint.setColor(Color.GREEN); outerCirclePaint.setAntiAlias(true);//抗锯齿 } if (outerCirclePaint2 == null) { outerCirclePaint2 = new Paint(); outerCirclePaint2.setStyle(Paint.Style.STROKE); outerCirclePaint2.setStrokeWidth(4); outerCirclePaint2.setColor(Color.BLUE);//blue outerCirclePaint2.setAntiAlias(true);//抗锯齿 } if (outerCirclePaint3 == null) { outerCirclePaint3 = new Paint(); outerCirclePaint3.setStyle(Paint.Style.STROKE); outerCirclePaint3.setStrokeWidth(4); outerCirclePaint3.setColor(Color.RED);// outerCirclePaint3.setAntiAlias(true);//抗锯齿 } if (innerCirclePaint == null) { innerCirclePaint = new Paint(); innerCirclePaint.setStyle(Paint.Style.STROKE); // outerCirclePaint.setColor(Color.BLACK); innerCirclePaint.setColor(Color.BLUE); innerCirclePaint.setAntiAlias(true);//抗锯齿 } if (linePaint == null) { linePaint = new Paint(); linePaint.setStyle(Paint.Style.STROKE); linePaint.setStrokeWidth(2); linePaint.setColor(0xff1d8ffe); linePaint.setAntiAlias(true); } if (arrowPaint == null) { arrowPaint = new Paint(); arrowPaint.setStyle(Paint.Style.FILL_AND_STROKE); arrowPaint.setColor(Color.BLUE); arrowPaint.setStrokeWidth(2); arrowPaint.setAntiAlias(true); } if (textPaint == null) { textPaint = new Paint(); textPaint.setStyle(Paint.Style.STROKE); textPaint.setColor(Color.BLACK); textPaint.setTextSize(12); textPaint.setStrokeWidth(1); textPaint.setAntiAlias(true); } if (textPaint2 == null) { textPaint2 = new Paint(); textPaint2.setStyle(Paint.Style.STROKE); textPaint2.setColor(Color.CYAN); textPaint2.setTextSize(18); textPaint2.setStrokeWidth(1); textPaint2.setAntiAlias(true); } } /** * 根据shortageAngle来调整圆弧的角度 */ private void initAngle() { sweepAngle = 360 - shortageAngle; startAngle = 90 + shortageAngle / 2; endAngle = 90 - shortageAngle / 2; } }
调用代码:
dashboardView = (DashboardView) findViewById(R.id.dashboardView); btn_next = (Button) findViewById(R.id.btn_next); dashboardView.setStartingValue(0); dashboardView.setEndValue(10); dashboardView.setTextCount(5); dashboardView.invalidate(); btn_next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //随机 new Handler().post(new Runnable() { @Override public void run() { d = Math.random() * 10; dashboardView.setArrowData(d); dashboardView.invalidate(); } }); } });
相关文章推荐
- 手把手带你画一个 时尚仪表盘 Android 自定义View
- 手把手带你画一个 时尚仪表盘 Android 自定义View
- android自定义View(仪表盘),多属性可设置
- Android 自定义View-时尚仪表盘
- 如何把Android的一款自定义仪表盘控件整合到你的项目里
- Android自定义仪表盘
- Android绘图系列(三)——自定义View绘制仪表盘
- 手把手带你画一个 时尚仪表盘 Android 自定义View
- Android自定义View仿支付宝芝麻信用分仪表盘
- android 自定义View 仪表盘 DashboardView 的实现
- Android自定义view之仿支付宝芝麻信用仪表盘
- Android自定义view之仿支付宝芝麻信用仪表盘 ---by ccy
- 手把手带你画一个 时尚仪表盘 Android 自定义View
- Android自定义View之仪表盘
- <Android 进阶(一)> 自定义View之仪表盘进度条
- Android自定义View 圆形刻度罗盘 仪表盘 指针动态改变
- Android自定义仪表盘
- Android自定义仪表盘视图
- 时尚仪表盘 Android 自定义View
- Android自定义仪表盘View(汽车速度仪,刻度盘等),多种自定义模式,满足多种需求