Android实现仪表盘效果
作者:志尊宝 发布时间:2021-07-26 13:19:37
标签:Android,仪表盘
本文实例为大家分享了Android实现仪表盘效果的具体代码,供大家参考,具体内容如下
仪表盘效果,圆弧可变色,效果图如下:
通过自定义view实现,代码如下:
public class DashboardView extends View {
private int mRadius; // 画布边缘半径(去除padding后的半径)
private int mStartAngle = 150; // 起始角度
private int mSweepAngle = 240; // 绘制角度
private int mMin = 1; // 最小值
private int mMax = 8; // 最大值
private int mSection = 8; // 值域(mMax-mMin)等分份数
private int mPortion = 3; // 一个mSection等分份数
private String mHeaderText = ""; // 表头
private int mCreditValue = (int) 8.12; // 信用分
private int mSolidCreditValue = mCreditValue; // 信用分(设定后不变)
private int mSparkleWidth; // 亮点宽度
private int mProgressWidth; // 进度圆弧宽度
private float mLength1; // 刻度顶部相对边缘的长度
private int mCalibrationWidth; // 刻度圆弧宽度
private float mLength2; // 刻度读数顶部相对边缘的长度
private int mPadding;
private float mCenterX, mCenterY; // 圆心坐标
private Paint mPaint;
private RectF mRectFProgressArc;
private RectF mRectFCalibrationFArc;
private RectF mRectFTextArc;
private Path mPath;
private Rect mRectText;
private String[] mTexts;
private int mBackgroundColor;
private int[] mBgColors;
private boolean isAnimFinish = true;
private float mAngleWhenAnim;
public DashboardView(Context context) {
this(context, null);
}
public DashboardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DashboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mSparkleWidth = dp2px(10);
mProgressWidth = dp2px(3);
mCalibrationWidth = dp2px(10);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mRectFProgressArc = new RectF();
mRectFCalibrationFArc = new RectF();
mRectFTextArc = new RectF();
mPath = new Path();
mRectText = new Rect();
mTexts = new String[]{"", "Ⅳ", "", "Ⅲ", "", "Ⅱ", "", "Ⅰ", ""};
mBgColors = new int[]{ContextCompat.getColor(getContext(), R.color.color_red),
ContextCompat.getColor(getContext(), R.color.color_orange),
ContextCompat.getColor(getContext(), R.color.color_yellow),
ContextCompat.getColor(getContext(), R.color.color_green),
ContextCompat.getColor(getContext(), R.color.color_blue)};
mBackgroundColor = mBgColors[0];
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mPadding = Math.max(
Math.max(getPaddingLeft(), getPaddingTop()),
Math.max(getPaddingRight(), getPaddingBottom())
);
setPadding(mPadding, mPadding, mPadding, mPadding);
mLength1 = mPadding + mSparkleWidth / 2f + dp2px(8);
mLength2 = mLength1 + mCalibrationWidth + dp2px(1) + dp2px(5);
int width = resolveSize(dp2px(220), widthMeasureSpec);
mRadius = (width - mPadding * 2) / 2;
setMeasuredDimension(width, width - dp2px(30));
mCenterX = mCenterY = getMeasuredWidth() / 2f;
mRectFProgressArc.set(
mPadding + mSparkleWidth / 2f,
mPadding + mSparkleWidth / 2f,
getMeasuredWidth() - mPadding - mSparkleWidth / 2f,
getMeasuredWidth() - mPadding - mSparkleWidth / 2f
);
mRectFCalibrationFArc.set(
mLength1 + mCalibrationWidth / 2f,
mLength1 + mCalibrationWidth / 2f,
getMeasuredWidth() - mLength1 - mCalibrationWidth / 2f,
getMeasuredWidth() - mLength1 - mCalibrationWidth / 2f
);
mPaint.setTextSize(sp2px(10));
mPaint.getTextBounds("0", 0, "0".length(), mRectText);
mRectFTextArc.set(
mLength2 + mRectText.height(),
mLength2 + mRectText.height(),
getMeasuredWidth() - mLength2 - mRectText.height(),
getMeasuredWidth() - mLength2 - mRectText.height()
);
}
@SuppressLint("ResourceAsColor")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// canvas.drawColor(mBackgroundColor);
// canvas.drawColor(R.color.color_blue);
/**
* 画进度圆弧背景
*/
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mProgressWidth);
mPaint.setAlpha(80);
mPaint.setColor(mBackgroundColor);
canvas.drawArc(mRectFProgressArc, mStartAngle + 1, mSweepAngle - 2, false, mPaint);
mPaint.setAlpha(255);
if (isAnimFinish) {
/**
* 画进度圆弧(起始到信用值)
*/
mPaint.setShader(generateSweepGradient());
mPaint.setColor(mBackgroundColor);
canvas.drawArc(mRectFProgressArc, mStartAngle + 1,
calculateRelativeAngleWithValue(mCreditValue) - 2, false, mPaint);
/**
* 画指示亮点
*/
float[] point = getCoordinatePoint(
mRadius - mSparkleWidth / 2f,
mStartAngle + calculateRelativeAngleWithValue(mCreditValue)
);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(generateRadialGradient(point[0], point[1]));
mPaint.setColor(mBackgroundColor);
canvas.drawCircle(point[0], point[1], mSparkleWidth / 2f, mPaint);
} else {
/**
* 画进度圆弧(起始到信用值)
*/
mPaint.setShader(generateSweepGradient());
mPaint.setColor(mBackgroundColor);
canvas.drawArc(mRectFProgressArc, mStartAngle + 1,
mAngleWhenAnim - mStartAngle - 2, false, mPaint);
/**
* 画指示亮点
*/
float[] point = getCoordinatePoint(
mRadius - mSparkleWidth / 2f,
mAngleWhenAnim
);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBackgroundColor);
mPaint.setShader(generateRadialGradient(point[0], point[1]));
canvas.drawCircle(point[0], point[1], mSparkleWidth / 2f, mPaint);
}
/**
* 画刻度圆弧
*/
mPaint.setShader(null);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setAlpha(80);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStrokeWidth(mCalibrationWidth);
mPaint.setColor(mBackgroundColor);
canvas.drawArc(mRectFCalibrationFArc, mStartAngle + 3, mSweepAngle - 6, false, mPaint);
/**
* 画长刻度
* 画好起始角度的一条刻度后通过canvas绕着原点旋转来画剩下的长刻度
*/
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(dp2px(2));
mPaint.setAlpha(120);
mPaint.setColor(mBackgroundColor);
float x0 = mCenterX;
float y0 = mPadding + mLength1 + dp2px(1);
float x1 = mCenterX;
float y1 = y0 + mCalibrationWidth;
// 逆时针到开始处
canvas.save();
canvas.drawLine(x0, y0, x1, y1, mPaint);
float degree = mSweepAngle / mSection;
for (int i = 0; i < mSection / 2; i++) {
canvas.rotate(-degree, mCenterX, mCenterY);
canvas.drawLine(x0, y0, x1, y1, mPaint);
}
canvas.restore();
// 顺时针到结尾处
canvas.save();
for (int i = 0; i < mSection / 2; i++) {
canvas.rotate(degree, mCenterX, mCenterY);
canvas.drawLine(x0, y0, x1, y1, mPaint);
}
canvas.restore();
/**
* 画短刻度
* 同样采用canvas的旋转原理
*/
mPaint.setStrokeWidth(dp2px(1));
mPaint.setAlpha(80);
mPaint.setColor(mBackgroundColor);
float x2 = mCenterX;
float y2 = y0 + mCalibrationWidth - dp2px(2);
// 逆时针到开始处
canvas.save();
canvas.drawLine(x0, y0, x2, y2, mPaint);
degree = mSweepAngle / (mSection * mPortion);
for (int i = 0; i < (mSection * mPortion) / 2; i++) {
canvas.rotate(-degree, mCenterX, mCenterY);
canvas.drawLine(x0, y0, x2, y2, mPaint);
}
canvas.restore();
// 顺时针到结尾处
canvas.save();
for (int i = 0; i < (mSection * mPortion) / 2; i++) {
canvas.rotate(degree, mCenterX, mCenterY);
canvas.drawLine(x0, y0, x2, y2, mPaint);
}
canvas.restore();
/**
* 画长刻度读数
* 添加一个圆弧path,文字沿着path绘制
*/
mPaint.setTextSize(sp2px(10));
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAlpha(160);
mPaint.setColor(mBackgroundColor);
for (int i = 0; i < mTexts.length; i++) {
mPaint.getTextBounds(mTexts[i], 0, mTexts[i].length(), mRectText);
// 粗略把文字的宽度视为圆心角2*θ对应的弧长,利用弧长公式得到θ,下面用于修正角度
float θ = (float) (180 * mRectText.width() / 2 /
(Math.PI * (mRadius - mLength2 - mRectText.height())));
mPath.reset();
mPath.addArc(
mRectFTextArc,
mStartAngle + i * (mSweepAngle / mSection) - θ, // 正起始角度减去θ使文字居中对准长刻度
mSweepAngle
);
canvas.drawTextOnPath(mTexts[i], mPath, 0, 0, mPaint);
}
/**
* 画实时度数值
*/
mPaint.setAlpha(255);
mPaint.setTextSize(sp2px(50));
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setColor(mBackgroundColor);
// String value = String.valueOf(mSolidCreditValue);
String value = "";
if (mSolidCreditValue > 6) {
//"Ⅳ", "", "Ⅲ", "", "Ⅱ", "", "Ⅰ",
value = "Ⅰ";
} else if (5 < mSolidCreditValue && mSolidCreditValue <= 6) {
value = "Ⅱ";
} else if (4 < mSolidCreditValue && mSolidCreditValue <= 5) {
value = "Ⅲ";
} else if (3 < mSolidCreditValue && mSolidCreditValue <= 4) {
value = "Ⅳ";
}
canvas.drawText(value, mCenterX, mCenterY + dp2px(30), mPaint);
/**
* 画表头
*/
mPaint.setAlpha(160);
mPaint.setTextSize(sp2px(12));
mPaint.setColor(Color.BLACK);
canvas.drawText(mHeaderText, mCenterX, mCenterY - dp2px(20), mPaint);
/**
* 画描述
*/
mPaint.setAlpha(255);
mPaint.setTextSize(sp2px(20));
mPaint.setColor(mBackgroundColor);
canvas.drawText(calculateCreditDescription(), mCenterX, mCenterY + dp2px(55), mPaint);
/**
* 画评估时间
*/
mPaint.setAlpha(160);
mPaint.setTextSize(sp2px(10));
mPaint.setColor(Color.BLACK);
canvas.drawText(getFormatTimeStr(), mCenterX, mCenterY + dp2px(70), mPaint);
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
Resources.getSystem().getDisplayMetrics());
}
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
Resources.getSystem().getDisplayMetrics());
}
private SweepGradient generateSweepGradient() {
SweepGradient sweepGradient = new SweepGradient(mCenterX, mCenterY,
new int[]{Color.argb(0, 255, 255, 255), Color.argb(200, 255, 255, 255)},
new float[]{0, calculateRelativeAngleWithValue(mCreditValue) / 360}
);
Matrix matrix = new Matrix();
matrix.setRotate(mStartAngle - 1, mCenterX, mCenterY);
sweepGradient.setLocalMatrix(matrix);
return sweepGradient;
}
private RadialGradient generateRadialGradient(float x, float y) {
return new RadialGradient(x, y, mSparkleWidth / 2f,
new int[]{Color.argb(255, 255, 255, 255), Color.argb(80, 255, 255, 255)},
new float[]{0.4f, 1},
Shader.TileMode.CLAMP
);
}
private float[] getCoordinatePoint(float radius, float angle) {
float[] point = new float[2];
double arcAngle = Math.toRadians(angle); //将角度转换为弧度
if (angle < 90) {
point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
} else if (angle == 90) {
point[0] = mCenterX;
point[1] = mCenterY + radius;
} else if (angle > 90 && angle < 180) {
arcAngle = Math.PI * (180 - angle) / 180.0;
point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
} else if (angle == 180) {
point[0] = mCenterX - radius;
point[1] = mCenterY;
} else if (angle > 180 && angle < 270) {
arcAngle = Math.PI * (angle - 180) / 180.0;
point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
} else if (angle == 270) {
point[0] = mCenterX;
point[1] = mCenterY - radius;
} else {
arcAngle = Math.PI * (360 - angle) / 180.0;
point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
}
return point;
}
/**
* 相对起始角度计算所对应的角度大小
*/
private float calculateRelativeAngleWithValue(int value) {
float degreePerSection = 1f * mSweepAngle / mSection;
if (value > 6) {
return 8 * degreePerSection + 2 * degreePerSection / 250 * (value - 6);
} else if (value > 5) {
return 6 * degreePerSection + 2 * degreePerSection / 50 * (value - 5);
} else if (value > 4) {
return 4 * degreePerSection + 2 * degreePerSection / 50 * (value - 4);
} else if (value > 3) {
return 2 * degreePerSection + 2 * degreePerSection / 50 * (value - 3);
} else {
return 2 * degreePerSection / 200 * (value - 1);
}
}
/**
* 信用分对应描述
*/
private String calculateCreditDescription() {
if (mSolidCreditValue > 6) {
return "";
} else if (mSolidCreditValue > 5) {
return "";
} else if (mSolidCreditValue > 4) {
return "";
} else if (mSolidCreditValue > 3) {
return "";
}
return "";
}
private SimpleDateFormat mDateFormat;
private String getFormatTimeStr() {
if (mDateFormat == null) {
mDateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm", Locale.CHINA);
}
return String.format("", mDateFormat.format(new Date()));
// return String.format("监测日期:%s", "2020-05-04");
}
public int getCreditValue() {
return mCreditValue;
}
/**
* 设置值
*
* @param creditValue
*/
public void setCreditValue(int creditValue) {
if (mSolidCreditValue == creditValue || creditValue < mMin || creditValue > mMax) {
return;
}
mSolidCreditValue = creditValue;
mCreditValue = creditValue;
postInvalidate();
}
/**
* 设置并播放动画
*
* @param creditValue 值
*/
public void setCreditValueWithAnim(int creditValue) {
if (creditValue < mMin || creditValue > mMax || !isAnimFinish) {
return;
}
mSolidCreditValue = creditValue;
ValueAnimator creditValueAnimator = ValueAnimator.ofInt(1, mSolidCreditValue);
creditValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCreditValue = (int) animation.getAnimatedValue();
postInvalidate();
}
});
// 计算最终值对应的角度,以扫过的角度的线性变化来播放动画
float degree = calculateRelativeAngleWithValue(mSolidCreditValue);
ValueAnimator degreeValueAnimator = ValueAnimator.ofFloat(mStartAngle, mStartAngle + degree);
degreeValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAngleWhenAnim = (float) animation.getAnimatedValue();
}
});
@SuppressLint("ObjectAnimatorBinding") ObjectAnimator colorAnimator = ObjectAnimator.ofInt(this, "mBackgroundColor", mBgColors[0], mBgColors[0]);
// 实时信用值对应的背景色的变化
long delay = 1000;
if (mSolidCreditValue > 6) {
colorAnimator.setIntValues(mBgColors[0], mBgColors[1], mBgColors[2], mBgColors[3], mBgColors[4]);
delay = 3000;
} else if (mSolidCreditValue > 5) {
colorAnimator.setIntValues(mBgColors[0], mBgColors[1], mBgColors[2], mBgColors[3]);
delay = 2500;
} else if (mSolidCreditValue > 4) {
colorAnimator.setIntValues(mBgColors[0], mBgColors[1], mBgColors[2]);
delay = 2000;
} else if (mSolidCreditValue > 3) {
colorAnimator.setIntValues(mBgColors[0], mBgColors[1]);
delay = 1500;
}
colorAnimator.setEvaluator(new ArgbEvaluator());
colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBackgroundColor = (int) animation.getAnimatedValue();
}
});
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(delay)
.playTogether(creditValueAnimator, degreeValueAnimator, colorAnimator);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isAnimFinish = false;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimFinish = true;
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
isAnimFinish = true;
}
});
animatorSet.start();
}
}
在color中添加颜色值:
<color name="color_red">#FF5722</color>
<color name="color_orange">#FF9800</color>
<color name="color_yellow">#FFC107</color>
<color name="color_green">#3FB830</color>
<color name="color_blue">#00BAED</color>
来源:https://blog.csdn.net/Chen_xiaobao/article/details/117250356


猜你喜欢
- 本文实例讲述了C#获取指定PDF文件页数的方法。分享给大家供大家参考。具体如下:using System;using System.IO;u
- Java 方法执行时的动态分派和静态分派是 Java 实现多态的本质背景Java 的动态分派和静态分派也是 Java 方法的执行原理。 Ja
- c语言关闭socket的两种方式一、shutdown()#include<sys/socket.h>int shutdown(i
- 本文实例为大家分享了java实现飞机游戏的具体代码,供大家参考,具体内容如下MyGameFrame类:主要的调用类package sc.wh
- 多对一(Many-to-One)映射多对一(many-to-one)关联是最常见的关联关系,其中一个对象可以与多个对象相关联。例如,一个相同
- 我就废话不多说了,大家还是直接看代码吧~<!-- 查询物品的id --><select id="checkIte
- 前言上节在谈论Bean的实例化过程时,在说明实例化后阶段时只是粗略地看了一下populateBean,并未展开分析。本节接着populate
- 如何在listview中插入图片,相信大家很想知道,下面就为大家分享具体步骤:第一步:在窗体中拖入ListView控件和imageList控
- 阿里、华为、腾讯Java技术面试题精选,具体内容如下JVM的类加载机制是什么?有哪些实现方式?类加载机制:类的加载指的是将类的.class文
- 前言两个数据结构:顺序表和链表数据结构是一门学科,和语言无关。数据 + 结构:一种描述和组织数据的方式。1. 顺序表顺序表是用一段物理地址连
- 前提:① 已经提供了一个wsdl接口② 该接口能正常调用总体分为两种方式:1.使用cxf的wsdl2java工具生成本地类(使用方式就是本地
- 关于java中遍历map具体哪四种方式,请看下文详解吧。方式一 这是最常见的并且在大多数情况下也是最可取的遍历方式。在键值都需要时使用。 M
- 前言在工作中有遇到这样的需求,需要把hmtl的富文本内容,进行深色模式适配。原先的富文本内容是在直接在webview上进行展示。解决思路:替
- 一、引言在前面的文章中,我们是使用“锁”的方式实现了线程间的通信,这种通信方式比较笨重。除了锁之外,
- 简单介绍synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:1. 修饰一个代码块,被修饰的代码块称为同步语句
- 不得不说opencv是个强大的东东,以前做一个项目的一个模块时使用到进行图形处理,这次是想将一个视频的播放放慢,以前在网上看到opencv有
- 计算器项目,要求实现加、减、乘、除、求倒数、求平方根等简单运算。真机调试结果如下图:布局文件:main_activity.xml<?x
- 一. 为什么需要比较对象上一节介绍了优先级队列,在优先级队列中插入的元素必须能比较大小,如果不能比较大小,如插入两个学生类型的元素,会报Cl
- 项目需求最近项目中有一个需求就是让Java代码去代替人工操作,自动生成PPT,具体就是查询数据库数据,然后根据模板文件(PPT),将数据库数
- 依赖SpringBoot版本:2.4.2 <dependencies> &