先上一张效果图:
先分析实现方案:
1.自定义属性,比如渐变色的两个色值、仪表盘下面的字、中间的刻度值等等。
2.重写onMeasure()方法设置仪表盘的大小。(由于这是一个圆但是下方还有一个圆角矩形所以需要高度比宽度大那么一丢丢)
3.绘制外层圆弧 固定颜色。
4.绘制渐变圆弧。
5.绘制下方圆角矩形(有两个矩形一个纯白色大的矩形,一个渐变色小的矩形)
6.绘制背景文字。
7.绘制中间的数值和百分号。
8.加上动画 让值和圆弧动起来。
1.自定义属性:(可以根据自己的新要求去定义)
public DashboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DashboardView);
startColor = typedArray.getColor(R.styleable.DashboardView_start_color, Color.parseColor("#FC9A4F"));
endColor = typedArray.getColor(R.styleable.DashboardView_end_color, Color.parseColor("#EF7038"));
outerColor = typedArray.getColor(R.styleable.DashboardView_outer_color, Color.parseColor("#D9D9D9"));
circularValue = typedArray.getInt(R.styleable.DashboardView_circular_value, 0);
text = typedArray.getString(R.styleable.DashboardView_text_content);
//圆弧的大小
paintSize = (int) typedArray.getDimension(R.styleable.DashboardView_circular_size,SizeTransformationUtils.dip2px(context, 13));
typedArray.recycle();
}
2.重写onMeasure()方法设置仪表盘的大小。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
if (width > height) {
width = height;
} else {
height = width;
}
setMeasuredDimension(width + paintSize, height + 2 * paintSize);
}
3.绘制外层圆弧 固定颜色。
private void drawOuterCircular(Canvas canvas) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(paintSize);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(outerColor);
canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, 360 - 2 * startAngle, false, paint);
}
4.绘制渐变圆弧。
private void drawInnerCircular(Canvas canvas) {
Paint paint = new Paint();
paint.setStrokeWidth(paintSize);
paint.setStyle(Paint.Style.STROKE);
//设置成圆头
paint.setStrokeCap(Paint.Cap.ROUND);
Shader shader = new SweepGradient((height + paintSize) / 2, (height + paintSize) / 2, startColor, endColor);
//相当于开始渐变的角度从6点钟方向开始渐变,默认是3点钟方向开始的。
Matrix matrix = new Matrix();
matrix.setRotate(90, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
shader.setLocalMatrix(matrix);
paint.setShader(shader);
canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, totalAngle / total * circularValue, false, paint);
}
这里用到了两个不常用的玩意:
1.设置画笔的画帽的样子 默认是直角 因为我们的圆弧需要圆头所以设置如下:
paint.setStrokeCap(Paint.Cap.ROUND);
2.设置渐变shader 安卓中实现该类的实现类有好几个我们用到的正好是SweepGradient这个实现类,效果就是从3点钟方向扫描渐变:
Shader shader = new SweepGradient((height + paintSize) / 2, (height + paintSize) / 2, startColor, endColor);
paint.setShader(shader);
由于我们需要从6点钟的方向开始渐变所以
Matrix matrix = new Matrix();
matrix.setRotate(90, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
shader.setLocalMatrix(matrix);
5.绘制下方圆角矩形(有两个矩形一个纯白色大的矩形,一个渐变色小的矩形)
private void drawTextBackGround(Canvas canvas) {
//绘制白色背景
Paint paint = new Paint();
paint.setColor(Color.WHITE);
canvas.drawRoundRect(whiteRect, paintSize, paintSize, paint);
//设置渐变
Shader shader = new LinearGradient(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
height - rect.height() / 5 * 2 + paintSize * 0.38f,
width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
height + rect.height() / 5 * 3 - paintSize * 0.38f, startColor, endColor, Shader.TileMode.CLAMP);
paint.setShader(shader);
//绘制渐变色背景
gradientsRect = new RectF(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
height - rect.height() / 5 * 2 + paintSize * 0.38f,
width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
height + rect.height() / 5 * 3 - paintSize * 0.38f);
canvas.drawRoundRect(gradientsRect, paintSize, paintSize, paint);
}
你看这里又用到了Shader 这个类 但是我们这次用的是LinearGradient实现类 效果是从左往右渐变。
6.绘制背景文字。
private void drawContentText(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(SizeTransformationUtils.sptopx(context, 13));
Rect rect = new Rect(0, 0, paintSize * 6, (int) (paintSize * 2.53f));
paint.getTextBounds(text, 0, text.length(), rect);
//此处Y值应该是基线的位置
canvas.drawText(text, width / 2 - rect.width() / 2 + paintSize / 2, (float) (height - rect.height() / 2.75 + paintSize), paint);
}
7.绘制中间的数值和百分号。
private void drawCircularValue(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.parseColor("#333333"));
paint.setTextSize(SizeTransformationUtils.sptopx(context, 32));
Rect rectValue = new Rect();
Rect rect = new Rect();
String value = circularValue + "";
String str = "%";
paint.getTextBounds(value, 0, value.length(), rectValue);
paint.getTextBounds(str, 0, str.length(), rect);
//此处Y值应该是基线的位置
canvas.drawText(value, width / 2 - (rectValue.width() / 2 + rect.width() / 2) + paintSize / 2, height / 2 + paintSize, paint);
paint.setTextSize(SizeTransformationUtils.sptopx(context, 12));
//绘制百分号
canvas.drawText(str, width / 2 + rectValue.width() / 2, height / 2 + paintSize, paint);
}
8.加上动画 让值和圆弧动起来。
private void startAnimator() {
ValueAnimator animator = ValueAnimator.ofInt(0, circularValue);
animator.setDuration(3000);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circularValue = (int) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}
9.向外部提供方法:
/**
* 设置总值和当前值(给外部调用的)
*
* @param total
* @param circularValue
*/
public void setTotalAndValue(int total, int circularValue) {
this.total = total;
this.circularValue = circularValue;
startAnimator();
}
/**
* 设置渐变色的颜色值
*
* @param startColor
* @param endColor
*/
public void setColor(int startColor, int endColor) {
this.startColor = startColor;
this.endColor = endColor;
invalidate();
}
以下贴上全部代码:
DashboardView.class
package com.example.customview;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.Nullable;
import com.example.toollibrary.SizeTransformationUtils;
/**
* 仪表盘自定义View
* <p>
* 1.自定义属性 1.外层不变的圆弧颜色 2.动态会改变的圆弧颜色 3.下方的模块名称 4.内部数值。
* 3.测量大小,把View设置成正方形
* 3.绘制外面的圆弧。
* 4.绘制渐变的圆弧。
* 5.绘制下方文字和背景。
* 6.绘制内部数值和百分号
*/
public class DashboardView extends View {
private int startColor;
private int endColor;
private int outerColor;
//下面的文本
private String text;
//圆弧中间的数值
private int circularValue;
//view的宽高
private int width;
private int height;
//画笔大小
private int paintSize;
private Context context;
private RectF gradientsRect;
private RectF whiteRect;
private RectF rect;
private float startAngle, totalAngle;
private int total = 100;
public DashboardView(Context context) {
this(context, null);
}
public DashboardView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DashboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DashboardView);
startColor = typedArray.getColor(R.styleable.DashboardView_start_color, Color.parseColor("#FC9A4F"));
endColor = typedArray.getColor(R.styleable.DashboardView_end_color, Color.parseColor("#EF7038"));
outerColor = typedArray.getColor(R.styleable.DashboardView_outer_color, Color.parseColor("#D9D9D9"));
circularValue = typedArray.getInt(R.styleable.DashboardView_circular_value, 0);
text = typedArray.getString(R.styleable.DashboardView_text_content);
paintSize = (int) typedArray.getDimension(R.styleable.DashboardView_circular_size,SizeTransformationUtils.dip2px(context, 13));
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
if (width > height) {
width = height;
} else {
height = width;
}
setMeasuredDimension(width + paintSize, height + 2 * paintSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
rect = new RectF(0, 0, paintSize * 6, paintSize * 2.53f);
whiteRect = new RectF(width / 2 - rect.width() / 2 + paintSize / 2, height - rect.height() / 5 * 2, width / 2 + rect.width() / 2 + paintSize / 2, height + rect.height() / 5 * 3);
startAngle = (float) Math.toDegrees(Math.sin((whiteRect.right - whiteRect.left) / 2 / (height / 2)));
totalAngle = 360 - (float) Math.toDegrees(Math.sin((whiteRect.right - whiteRect.left) / 2 / (height / 2))) * 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawOuterCircular(canvas);
drawInnerCircular(canvas);
drawTextBackGround(canvas);
drawContentText(canvas);
drawCircularValue(canvas);
}
/**
* 绘制中间的值
*
* @param canvas
*/
private void drawCircularValue(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.parseColor("#333333"));
paint.setTextSize(SizeTransformationUtils.sptopx(context, 32));
Rect rectValue = new Rect();
Rect rect = new Rect();
String value = circularValue + "";
String str = "%";
paint.getTextBounds(value, 0, value.length(), rectValue);
paint.getTextBounds(str, 0, str.length(), rect);
//此处Y值应该是基线的位置
canvas.drawText(value, width / 2 - (rectValue.width() / 2 + rect.width() / 2) + paintSize / 2, height / 2 + paintSize, paint);
paint.setTextSize(SizeTransformationUtils.sptopx(context, 12));
//绘制百分号
canvas.drawText(str, width / 2 + rectValue.width() / 2, height / 2 + paintSize, paint);
}
/**
* 绘制文字圆角背景
*
* @param canvas
*/
private void drawTextBackGround(Canvas canvas) {
//绘制白色背景
Paint paint = new Paint();
paint.setColor(Color.WHITE);
canvas.drawRoundRect(whiteRect, paintSize, paintSize, paint);
//设置渐变
Shader shader = new LinearGradient(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
height - rect.height() / 5 * 2 + paintSize * 0.38f,
width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
height + rect.height() / 5 * 3 - paintSize * 0.38f, startColor, endColor, Shader.TileMode.CLAMP);
paint.setShader(shader);
//绘制渐变色背景
gradientsRect = new RectF(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
height - rect.height() / 5 * 2 + paintSize * 0.38f,
width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
height + rect.height() / 5 * 3 - paintSize * 0.38f);
canvas.drawRoundRect(gradientsRect, paintSize, paintSize, paint);
}
/**
* 绘制下面的文字
*
* @param canvas
*/
private void drawContentText(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(SizeTransformationUtils.sptopx(context, 13));
Rect rect = new Rect(0, 0, paintSize * 6, (int) (paintSize * 2.53f));
paint.getTextBounds(text, 0, text.length(), rect);
//此处Y值应该是基线的位置
canvas.drawText(text, width / 2 - rect.width() / 2 + paintSize / 2, (float) (height - rect.height() / 2.75 + paintSize), paint);
}
/**
* 绘制内圆弧
*
* @param canvas
*/
private void drawInnerCircular(Canvas canvas) {
Paint paint = new Paint();
paint.setStrokeWidth(paintSize);
paint.setStyle(Paint.Style.STROKE);
//设置成圆头
paint.setStrokeCap(Paint.Cap.ROUND);
Shader shader = new SweepGradient((height + paintSize) / 2, (height + paintSize) / 2, startColor, endColor);
//相当于开始渐变的角度从6点钟方向开始渐变,默认是3点钟方向开始的。
Matrix matrix = new Matrix();
matrix.setRotate(90, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
shader.setLocalMatrix(matrix);
paint.setShader(shader);
canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, totalAngle / total * circularValue, false, paint);
}
/**
* 绘制外圆弧(给外部调用的)
*
* @param canvas
*/
private void drawOuterCircular(Canvas canvas) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(paintSize);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(outerColor);
canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, 360 - 2 * startAngle, false, paint);
}
/**
* 设置总值和当前值(给外部调用的)
*
* @param total
* @param circularValue
*/
public void setTotalAndValue(int total, int circularValue) {
this.total = total;
this.circularValue = circularValue;
startAnimator();
}
/**
* 设置渐变色的颜色值
*
* @param startColor
* @param endColor
*/
public void setColor(int startColor, int endColor) {
this.startColor = startColor;
this.endColor = endColor;
invalidate();
}
/**
* 开启动画
*/
private void startAnimator() {
ValueAnimator animator = ValueAnimator.ofInt(0, circularValue);
animator.setDuration(3000);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circularValue = (int) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}
}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DashboardView">
<attr name="outer_color" format="color" />
<attr name="start_color" format="color" />
<attr name="end_color" format="color" />
<attr name="text_content" format="string" />
<attr name="circular_value" format="integer" />
<attr name="circular_size" format="dimension" />
</declare-styleable>
</resources>
使用代码:xml
<com.example.customview.DashboardView
android:id="@+id/dv_memory"
android:layout_width="0dp"
android:layout_height="118dp"
android:layout_gravity="center"
android:layout_weight="1"
app:circular_value="97"
app:end_color="#EF7038"
app:outer_color="#D9D9D9"
app:start_color="#FC9A4F"
app:text_content="运行内存" />
java代码:
dvMemory.setTotalAndValue(100,80);
就完成了仪表盘的绘制和使用。