您的位置:首页 > 移动开发 > Android开发

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();
}
});
}

});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: