效果预览
代码实现
public class AutoMeterView extends View {
private DisplayMetrics displayMetrics;
private TextPaint mPaint;
private int lineWidth = 10;
private int outlineWidth = 20;
private int textSize = 16;
private float sumDegree = (360-90.0f);
float degreeOffset = 0;
private ValueAnimator mDegreeAnimator;
public AutoMeterView(Context context) {
this(context,null);
}
public AutoMeterView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public AutoMeterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
displayMetrics = context.getResources().getDisplayMetrics();
initPaint();
}
private void initPaint() {
// 实例化画笔并打开抗锯齿
mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG );
mPaint.setAntiAlias(true);
mPaint.setTextSize(dpTopx(textSize));
}
private float dpTopx(int dp){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources().getDisplayMetrics());
}
public void setSumDegree(float sumDegree) {
this.sumDegree = sumDegree;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if(widthMode!=MeasureSpec.EXACTLY){
widthSize = displayMetrics.widthPixels/2;
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(heightMode!=MeasureSpec.EXACTLY){
heightSize = displayMetrics.widthPixels/2;
}
widthSize = heightSize = Math.min(widthSize,heightSize);
setMeasuredDimension(widthSize,heightSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getWidth();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dpTopx(outlineWidth));
float strokeWidth = mPaint.getStrokeWidth();
int outRadius = (int) (Math.min(width/2,height/2));
int innerRadius = (int) (outRadius - 3*strokeWidth);
int innerDotRadius = (int) (outRadius - 2*strokeWidth);
float perDegree = sumDegree/100;
canvas.save();
canvas.translate(width/2,height/2);
canvas.rotate(90+45);
mPaint.setColor(0xff54d68c);
RectF rectF = new RectF(-width/2+strokeWidth/2,-height/2+strokeWidth/2,width/2-strokeWidth/2,height/2-strokeWidth/2);
canvas.drawArc(rectF,0, 20*perDegree,false,mPaint);
mPaint.setColor(0xffff9922);
canvas.drawArc(rectF,20*perDegree, 60*perDegree,false,mPaint);
mPaint.setColor(Color.RED);
canvas.drawArc(rectF,80*perDegree, 20*perDegree,false,mPaint);
mPaint.setStrokeWidth(dpTopx(lineWidth));
strokeWidth = mPaint.getStrokeWidth();
outRadius = (int) (Math.min(width/2,height/2));
innerRadius = (int) (outRadius - 4*strokeWidth);
innerDotRadius = (int) (outRadius - 3*strokeWidth);
for (int i=0;i<=100;i++){
float innerLineRadius = innerDotRadius;
if(i%10==0){
mPaint.setStrokeWidth(strokeWidth/4);
innerLineRadius = innerRadius;
}else{
mPaint.setStrokeWidth(strokeWidth/5);
}
float startX = (float) (innerLineRadius*Math.cos(Math.toRadians(i*perDegree)));
float startY = (float)(innerLineRadius*Math.sin(Math.toRadians(i*perDegree)));
float endX = (float)(outRadius*Math.cos(Math.toRadians(i*perDegree)));
float endY = (float)(outRadius*Math.sin(Math.toRadians(i*perDegree)));
mPaint.setColor(getPaintColor(i));
canvas.drawLine(startX,startY,endX,endY,mPaint);
}
canvas.restore();
canvas.save();
mPaint.setStyle(Paint.Style.FILL);
canvas.translate(width/2,height/2);
canvas.rotate(-90-45);
//该方法旋转的是坐标系,而不是图层
for (int i=0;i<=10;i++){
if(i>0) {
canvas.rotate(perDegree * 10);
//每次旋转坐标系一定的位置
}
mPaint.setColor(getPaintColor(i*10));
String text = String.valueOf(i*10);
Rect bounds = new Rect();
mPaint.getTextBounds(text, 0, text.length(), bounds);
float textBaseline = innerRadius-strokeWidth - bounds.height() + getTextPaintBaseline(mPaint);
canvas.drawText(text,0-bounds.width()/2,-textBaseline,mPaint);
//从y轴负方向开始绘制
}
canvas.restore();
canvas.save();
canvas.translate(width/2,height/2);
mPaint.setColor(getPaintColor(degreeOffset));
int tail = (int) dpTopx(outlineWidth);
float startX = (float) (-tail*Math.cos(Math.toRadians(90+45+degreeOffset*perDegree)));
float startY = (float)(-tail*Math.sin(Math.toRadians(90+45+degreeOffset*perDegree)));
float endX = (float)((innerRadius -tail )*Math.cos(Math.toRadians(90+45+degreeOffset*perDegree)));
float endY = (float)((innerRadius -tail )*Math.sin(Math.toRadians(90+45+degreeOffset*perDegree)));
canvas.drawLine(startX,startY,endX,endY,mPaint);
canvas.drawCircle(0,0,tail/2,mPaint);
canvas.restore();
}
private int getPaintColor(float degreeOffset) {
if (degreeOffset <= 20) {
return 0xff54d68c;
} else if (degreeOffset > 20 && degreeOffset < 80) {
return 0xffff9922;
} else {
return Color.RED;
}
}
/**
* 基线到中线的距离=(Descent+Ascent)/2-Descent
注意,实际获取到的Ascent是负数。公式推导过程如下:
中线到BOTTOM的距离是(Descent+Ascent)/2,这个距离又等于Descent+中线到基线的距离,即(Descent+Ascent)/2=基线到中线的距离+Descent。
*/
public static float getTextPaintBaseline(Paint p) {
Paint.FontMetrics fontMetrics = p.getFontMetrics();
return (fontMetrics.descent - fontMetrics.ascent) / 2 -fontMetrics.descent;
}
public void setDegreeOffset(float targetDegreeOffset) {
if(targetDegreeOffset<0){
targetDegreeOffset = 0;
}
if(targetDegreeOffset>100){
targetDegreeOffset = 100;
}
if(this.degreeOffset==targetDegreeOffset){
return;
}
if(getWidth()==0|| getHeight()==0){
this.degreeOffset = targetDegreeOffset;
return;
}
if(mDegreeAnimator!=null){
mDegreeAnimator.cancel();
}
float perDegree = sumDegree/100;
float bounceOffset = 0;
if(this.degreeOffset>targetDegreeOffset){
bounceOffset = targetDegreeOffset - perDegree*5;
if(bounceOffset<0){
bounceOffset = 0;
}
}else{
bounceOffset = targetDegreeOffset + perDegree*5;
if(bounceOffset>100){
bounceOffset = 100;
}
}
mDegreeAnimator = ValueAnimator.ofFloat(this.degreeOffset,bounceOffset,targetDegreeOffset);
mDegreeAnimator.setDuration(500);
mDegreeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
postUpdateDegree((Float) animation.getAnimatedValue());
}
});
mDegreeAnimator.start();
}
private void postUpdateDegree(float animatedValue) {
this.degreeOffset = animatedValue;
postInvalidate();
}
}