package com.xw.sample.dashboardviewdemo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
/**
* DashboardView style 4,仿汽车速度仪表盘
* Created by woxingxiao on 2016-12-19.
*/
public class DashboardView4 extends View {
private int mRadius; // 扇形半径
private int mStartAngle = 150; // 起始角度
private int mSweepAngle = 240; // 绘制角度
private int mMin = 0; // 最小值
private int mMax = 180; // 最大值
private int mSection = 9; // 值域(mMax-mMin)等分份数
private int mPortion = 2; // 一个mSection等分份数
private String mHeaderText = "km/h"; // 表头
private int mVelocity = mMin; // 实时速度
private int mStrokeWidth; // 画笔宽度
private int mLength1; // 长刻度的相对圆弧的长度
private int mLength2; // 刻度读数顶部的相对圆弧的长度
private int mPLRadius; // 指针长半径
private int mPSRadius; // 指针短半径
private int mPadding;
private float mCenterX, mCenterY; // 圆心坐标
private Paint mPaint;
private RectF mRectFArc;
private RectF mRectFInnerArc;
private Rect mRectText;
private String[] mTexts;
private int[] mColors;
public DashboardView4(Context context) {
this(context, null);
}
public DashboardView4(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DashboardView4(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mStrokeWidth = dp2px(3);
mLength1 = dp2px(8) + mStrokeWidth;
mLength2 = mLength1 + dp2px(4);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mRectFArc = new RectF();
mRectFInnerArc = new RectF();
mRectText = new Rect();
mTexts = new String[mSection + 1]; // 需要显示mSection + 1个刻度读数
for (int i = 0; i < mTexts.length; i++) {
int n = (mMax - mMin) / mSection;
mTexts[i] = String.valueOf(mMin + i * n);
}
mColors = new int[]{ContextCompat.getColor(getContext(), R.color.color_green),
ContextCompat.getColor(getContext(), R.color.color_yellow),
ContextCompat.getColor(getContext(), R.color.color_red)};
}
@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);
int width = resolveSize(dp2px(260), widthMeasureSpec);
mRadius = (width - mPadding * 2 - mStrokeWidth * 2) / 2;
// 由起始角度确定的高度
float[] point1 = getCoordinatePoint(mRadius, mStartAngle);
// 由结束角度确定的高度
float[] point2 = getCoordinatePoint(mRadius, mStartAngle + mSweepAngle);
int height = (int) Math.max(point1[1] + mRadius + mStrokeWidth * 2,
point2[1] + mRadius + mStrokeWidth * 2);
setMeasuredDimension(width, height + getPaddingTop() + getPaddingBottom());
mCenterX = mCenterY = getMeasuredWidth() / 2f;
mRectFArc.set(
getPaddingLeft() + mStrokeWidth,
getPaddingTop() + mStrokeWidth,
getMeasuredWidth() - getPaddingRight() - mStrokeWidth,
getMeasuredWidth() - getPaddingBottom() - mStrokeWidth
);
mPaint.setTextSize(sp2px(16));
mPaint.getTextBounds("0", 0, "0".length(), mRectText);
mRectFInnerArc.set(
getPaddingLeft() + mLength2 + mRectText.height() + dp2px(30),
getPaddingTop() + mLength2 + mRectText.height() + dp2px(30),
getMeasuredWidth() - getPaddingRight() - mLength2 - mRectText.height() - dp2px(30),
getMeasuredWidth() - getPaddingBottom() - mLength2 - mRectText.height() - dp2px(30)
);
mPLRadius = mRadius - dp2px(30);
mPSRadius = dp2px(25);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(ContextCompat.getColor(getContext(), R.color.color_dark));
/**
* 画圆弧
*/
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.color_light));
canvas.drawArc(mRectFArc, mStartAngle, mSweepAngle, false, mPaint);
/**
* 画长刻度
* 画好起始角度的一条刻度后通过canvas绕着原点旋转来画剩下的长刻度
*/
double cos = Math.cos(Math.toRadians(mStartAngle - 180));
double sin = Math.sin(Math.toRadians(mStartAngle - 180));
float x0 = (float) (mPadding + mStrokeWidth + mRadius * (1 - cos));
float y0 = (float) (mPadding + mStrokeWidth + mRadius * (1 - sin));
float x1 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - mLength1) * cos);
float y1 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - mLength1) * sin);
canvas.save();
canvas.drawLine(x0, y0, x1, y1, mPaint);
float angle = mSweepAngle * 1f / mSection;
for (int i = 0; i < mSection; i++) {
canvas.rotate(angle, mCenterX, mCenterY);
canvas.drawLine(x0, y0, x1, y1, mPaint);
}
canvas.restore();
/**
* 画短刻度
* 同样采用canvas的旋转原理
*/
canvas.save();
mPaint.setStrokeWidth(mStrokeWidth / 2f);
float x2 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - 2 * mLength1 / 3f) * cos);
float y2 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - 2 * mLength1 / 3f) * sin);
canvas.drawLine(x0, y0, x2, y2, mPaint);
angle = mSweepAngle * 1f / (mSection * mPortion);
for (int i = 1; i < mSection * mPortion; i++) {
canvas.rotate(angle, mCenterX, mCenterY);
if (i % mPortion == 0) { // 避免与长刻度画重合
continue;
}
canvas.drawLine(x0, y0, x2, y2, mPaint);
}
canvas.restore();
/**
* 画长刻度读数
*/
mPaint.setTextSize(sp2px(16));
mPaint.setStyle(Paint.Style.FILL);
float α;
float[] p;
angle = mSweepAngle * 1f / mSection;
for (int i = 0; i <= mSection; i++) {
α = mStartAngle + angle * i;
p = getCoordinatePoint(mRadius - mLength2, α);
if (α % 360 > 135 && α % 360 < 225) {
mPaint.setTextAlign(Paint.Align.LEFT);
} else if ((α % 360 >= 0 && α % 360 < 45) || (α % 360 > 315 && α % 360 <= 360)) {
mPaint.setTextAlign(Paint.Align.RIGHT);
} else {
mPaint.setTextAlign(Paint.Align.CENTER);
}
mPaint.getTextBounds(mHeaderText, 0, mTexts[i].length(), mRectText);
int txtH = mRectText.height();
if (i <= 1 || i >= mSection - 1) {
canvas.drawText(mTexts[i], p[0], p[1] + txtH / 2, mPaint);
} else if (i == 3) {
canvas.drawText(mTexts[i], p[0] + txtH / 2, p[1] + txtH, mPaint);
} else if (i == mSection - 3) {
canvas.drawText(mTexts[i], p[0] - txtH / 2, p[1] + txtH, mPaint);
} else {
canvas.drawText(mTexts[i], p[0], p[1] + txtH, mPaint);
}
}
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dp2px(10));
mPaint.setShader(generateSweepGradient());
canvas.drawArc(mRectFInnerArc, mStartAngle + 1, mSweepAngle - 2, false, mPaint);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(null);
/**
* 画表头
* 没有表头就不画
*/
if (!TextUtils.isEmpty(mHeaderText)) {
mPaint.setTextSize(sp2px(16));
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.getTextBounds(mHeaderText, 0, mHeaderText.length(), mRectText);
canvas.drawText(mHeaderText, mCenterX, mCenterY - mRectText.height() * 3, mPaint);
}
/**
* 画指针
*/
float θ = mStartAngle + mSweepAngle * (mVelocity - mMin) / (mMax - mMin); // 指针与水平线夹角
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.color_dark_light));
int r = mRadius / 8;
canvas.drawCircle(mCenterX, mCenterY, r, mPaint);
mPaint.setStrokeWidth(r / 3);
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.color_light));
float[] p1 = getCoordinatePoint(mPLRadius, θ);
canvas.drawLine(p1[0], p1[1], mCenterX, mCenterY, mPaint);
float[] p2 = getCoordinatePoint(mPSRadius, θ + 180);
canvas.drawLine(mCenterX, mCenterY, p2[0], p2[1], mPaint);
/**
* 画实时度数值
*/
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.colorPrimary));
mPaint.setStrokeWidth(dp2px(2));
int xOffset = dp2px(22);
if (mVelocity >= 100) {
drawDigitalTube(canvas, mVelocity / 100, -xOffset);
drawDigitalTube(canvas, (mVelocity - 100) / 10, 0);
drawDigitalTube(canvas, mVelocity % 100 % 10, xOffset);
} else if (mVelocity >= 10) {
drawDigitalTube(canvas, -1, -xOffset);
drawDigitalTube(canvas, mVelocity / 10, 0);
drawDigitalTube(canvas, mVelocity % 10, xOffset);
} else {
drawDigitalTube(canvas, -1, -xOffset);
drawDigitalTube(canvas, -1, 0);
drawDigitalTube(canvas, mVelocity, xOffset);
}
}
/**
* 数码管样式
*/
// 1
// ——
// 2 | | 3
// —— 4
// 5 | | 6
// ——
// 7
private void drawDigitalTube(Canvas canvas, int num, int xOffset) {
float x = mCenterX + xOffset;
float y = mCenterY + dp2px(40);
int lx = dp2px(5);
int ly = dp2px(10);
int gap = dp2px(2);
// 1
mPaint.setAlpha(num == -1 || num == 1 || num == 4 ? 25 : 255);
canvas.drawLine(x - lx, y, x + lx, y, mPaint);
// 2
mPaint.setAlpha(num == -1 || num == 1 || num == 2 || num == 3 || num == 7 ? 25 : 255);
canvas.drawLine(x - lx - gap, y + gap, x - lx - gap, y + gap + ly, mPaint);
// 3
mPaint.setAlpha(num == -1 || num == 5 || num == 6 ? 25 : 255);
canvas.drawLine(x + lx + gap, y + gap, x + lx + gap, y + gap + ly, mPaint);
// 4
mPaint.setAlpha(num == -1 || num == 0 || num == 1 || num == 7 ? 25 : 255);
canvas.drawLine(x - lx, y + gap * 2 + ly, x + lx, y + gap * 2 + ly, mPaint);
// 5
mPaint.setAlpha(num == -1 || num == 1 || num == 3 || num == 4 || num == 5 || num == 7
|| num == 9 ? 25 : 255);
canvas.drawLine(x - lx - gap, y + gap * 3 + ly,
x - lx - gap, y + gap * 3 + ly * 2, mPaint);
// 6
mPaint.setAlpha(num == -1 || num == 2 ? 25 : 255);
canvas.drawLine(x + lx + gap, y + gap * 3 + ly,
x + lx + gap, y + gap * 3 + ly * 2, mPaint);
// 7
mPaint.setAlpha(num == -1 || num == 1 || num == 4 || num == 7 ? 25 : 255);
canvas.drawLine(x - lx, y + gap * 4 + ly * 2, x + lx, y + gap * 4 + ly * 2, 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());
}
public float[] getCoordinatePoint(int 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 SweepGradient generateSweepGradient() {
SweepGradient sweepGradient = new SweepGradient(mCenterX, mCenterY,
mColors,
new float[]{0, 140 / 360f, mSweepAngle / 360f}
);
Matrix matrix = new Matrix();
matrix.setRotate(mStartAngle - 3, mCenterX, mCenterY);
sweepGradient.setLocalMatrix(matrix);
return sweepGradient;
}
public int getVelocity() {
return mVelocity;
}
public void setVelocity(int velocity) {
if (mVelocity == velocity || velocity < mMin || velocity > mMax) {
return;
}
mVelocity = velocity;
postInvalidate();
}
}