参考自:http://blog.csdn.net/vv_bug/article/details/71694562
1.新建DashboardView类继承View,一些准备工作
public class DashboardView extends View{
//弧形开始的角度
private static final int startAngle = 180;
//弧面所跨的弧度
private static final int sweepAngle = 180;
//里面数字的单位
private static final String unint = "mmhg";
//每隔多少画一个刻度
private static final int angPre = 2;
//总刻度
private static final int totalDial = 90;
//进度条的底色
private static final int PROGRESS_COLOR = 0x55000000;
//画普通的线用的笔
private Paint linePaint;
//画文字用的笔
private Paint textPaint;
//画进度条用的笔
private Paint progressPaint;
//里面半圆的半径
private int innerRadius = dp2px(100);
//最内层的padding
private int innerPadding = dp2px(6);
//外两层的padding
private int outerPadding = dp2px(10);
//进度条的宽度
private int progressLineW = dp2px(8);
//最里面跟最外面的线的宽度
private int innerLineWidth = dp2px(1);
//刻度线的宽度
private int outerLineWidth = dp2px(2);
//刻度线的高度
private int outerLineHeight = dp2px(10);
//文字的size
private float textSize = sp2px(18);
//单位文字的size
private float textSizeUnit = sp2px(13);
//当前进度
private float currProgress = 0.5f;
//起始值
private float start = 0;
//最终值
private float end = 150;
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);
obtainAttributes(attrs);
}
/**
* 获取自定义的属性
*/
private void obtainAttributes(AttributeSet attrs) {
init();
}
/**
* 初始化
*/
private void init() {
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(Color.WHITE);
progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setColor(Color.WHITE);
progressPaint.setStrokeWidth(progressLineW);
progressPaint.setStrokeCap(Paint.Cap.ROUND);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
textPaint.setStyle(Paint.Style.STROKE);
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(textSize);
}
}
然后先是onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//最里面圆的半径计算
//半径=控件的宽度/2-最里层的padding-刻度线的高度-里第二层的padding-进度条的宽度-第二层padding-最外层线宽度
innerRadius = getMeasuredWidth() / 2 - innerPadding - outerLineHeight -
outerPadding - progressLineW - outerPadding - innerLineWidth;
//因为view的设置的高度是wrap_content,所以我们要重新计算高度
//高度=刻度线的高度+padding+最里面一层的半径+padding+进度条宽度+padding+最外层线宽度
int height = (outerLineHeight + innerPadding + innerRadius + outerPadding + progressLineW + outerPadding + innerLineWidth);
//重新生成高度
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
然后是onDraw
@Override
protected void onDraw(Canvas canvas) {
drawInnerArc(canvas);
}
画最里层的圆弧
*先定义一个矩形,矩形其实为一个圆的大小
*画圆弧 180度开始 到 360度结束 这样就有一个半圆
private void drawInnerArc(Canvas canvas) {
//定义一个矩形区域
RectF rectF = new RectF();
int width = getWidth();
//矩形的top为 padding + 刻度线的高度+padding+进度条宽度 +padding +最外层弧线线宽度
//left为控件的宽度/2 - 半圆半径
int top = outerLineHeight + innerPadding + innerLineWidth + outerPadding * 2 + progressLineW;
rectF.set(width / 2 - innerRadius, top, width / 2 + innerRadius, top + innerRadius * 2);
linePaint.setStrokeWidth(innerLineWidth);
//弧线的起始位置为180度的位置,扫过的弧度为180
canvas.drawArc(rectF, startAngle, sweepAngle, false, linePaint);
}
看下效果
然后画刻度
*用角度的方法来画,利用三角函数算出刻度两端的点的值
*算出来的以圆弧的中心点的坐标系,真正的坐标系为左上角,所以还要加上getWidth()/2或者getHeight()
private void drawDial(int startAngle, int allAngle, int dialCount, int per, int longLength, int shortLength, int radius, Canvas canvas) {
linePaint.setStrokeWidth(outerLineWidth);
int length;
int angle;
//根据需要显示的刻度总个数遍历
for (int i = 0; i <= dialCount; i++) {
//每一个刻度对应的起始角度为180度+(总度数/个数)*对应刻度的位置
angle = (int) ((allAngle) / (dialCount * 1f) * i) + startAngle;
//线条的起始点位置
int[] startP;
//线条的end点的位置
int[] endP;
//当i%per==0,每一个需要显示短刻度的时候(因为设计稿第一个为短的刻度条)
if (i % per == 0) {
//短刻度条的长度为长刻度条的一半
length = shortLength;
//获取刻度条起始点位置
startP = getPointFromAngleAndRadius(angle, radius - length);
endP = getPointFromAngleAndRadius(angle, radius - length * 2);
} else {
length = longLength;
startP = getPointFromAngleAndRadius(angle, radius);
endP = getPointFromAngleAndRadius(angle, radius - length);
}
//画出对应的刻度条
canvas.drawLine(startP[0], startP[1], endP[0], endP[1], linePaint);
}
}
/**
* 根据刻度条相应的角度算出点位置
* @param angle
* @param radius
* @return
*/
private int[] getPointFromAngleAndRadius(int angle, int radius) {
//根据三角函数公式可以知道,横坐标值为(刻度条+innnerradius)也就是刻度条对应圆的半径
//乘以一个cos(angle),因为我们是以(getWidth() / 2,控件的高度)位置建的坐标系
//而真正的坐标系的位置为控件左上角,所以算出的值后需要+getWidth() / 2或者getHeight()
double x = radius * Math.cos(angle * Math.PI / 180) + getWidth() / 2;
double y = radius * Math.sin(angle * Math.PI / 180) + getHeight();
return new int[]{(int) x, (int) y};
}
看下效果
接下来画文字
主要是确定文字的位置
private void drawText(Canvas canvas) {
//当前文字对应的值为(0+(150-0)*当前进度)
String currText = String.valueOf((int) (start + (end - start) * currProgress));
//因为数字字体大而单位数字小
textPaint.setTextSize(textSize);
//测量数字文字对应的长度
float numWidth = textPaint.measureText(currText);
//重新设置笔的size
textPaint.setTextSize(textSizeUnit);
//为了获取单位文字的高度
Rect rect = new Rect();
//获取单位文字的最小矩形范围
textPaint.getTextBounds(unint, 0, unint.length(), rect);
//单位文字的宽度
float unitWidth = textPaint.measureText(unint);
//从新设置笔的大小
textPaint.setTextSize(textSize);
//文字的basex为(控件的宽度/2-(数字文字的长度+单位文字的长度)/2)
float baseX = getWidth()/2 - (unitWidth + numWidth) / 2;
//文字的centery为(最外层线的宽度+padding+进度条宽度+padding+padding+最里面半圆半径的一半)
float centerY = innerLineWidth + outerPadding + progressLineW + outerPadding + outerLineHeight + innerPadding + innerRadius / 2;
//(主要解决文字在半圆的中心文字)根据centery算出文字的basey
float baseY = centerY - (textPaint.ascent() + textPaint.descent()) / 2;
//设置数字文字为粗体
textPaint.setFakeBoldText(true);
//画出数字文字
canvas.drawText(currText + "", baseX, baseY, textPaint);
//重新设置画笔
textPaint.setTextSize(textSizeUnit);
textPaint.setFakeBoldText(false);
//画出单位文字(跟数字文字底部有一个偏移量所以basey-了一个(单位文字的高度的1/6))
canvas.drawText(unint, baseX + numWidth + dp2px(1), baseY - rect.height() / 6, textPaint);
}
看下效果
画出最外面的弧线跟进度条
*画弧的时候,弧的画笔宽度的一半会和矩形相切
private void drawOuterStaticLine(Canvas canvas) {
//最外层的弧线
RectF rectF1 = new RectF();
int width = getWidth();
rectF1.set(innerLineWidth, innerLineWidth, width - innerLineWidth, getHeight() * 2 - innerLineWidth);
linePaint.setStrokeWidth(innerLineWidth);
canvas.drawArc(rectF1, startAngle, sweepAngle, false, linePaint);
//静态的进度条
progressPaint.setColor(PROGRESS_COLOR);
RectF rectF2 = new RectF();
rectF2.set(innerLineWidth + outerPadding + outerPadding / 2, innerLineWidth + outerPadding + outerPadding / 2,
width - (innerLineWidth + outerPadding + outerPadding / 2), getHeight() * 2 - ((innerLineWidth + outerPadding + outerPadding / 2)));
canvas.drawArc(rectF2, startAngle, sweepAngle, false, progressPaint);
}
看下效果
最后就是设置进度的方法了
public void setProgress(float progress){
if (currProgress == progress) {
return;
}
this.currProgress = progress;
if (Looper.myLooper() == Looper.getMainLooper()) {
invalidate();
} else {
postInvalidate();
}
}
测试
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#a7ff0000"
android:gravity="center_horizontal"
android:orientation="vertical">
<com.lzz.dashboard.DashboardView
android:id="@+id/dashboardView"
android:background="@color/colorAccent"
android:layout_marginTop="40dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
DashboardView dashboardView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dashboardView = (DashboardView) findViewById(R.id.dashboardView);
startAni();
}
private void startAni() {
//对应dashboardView中的setProgress()方法
ObjectAnimator a= ObjectAnimator.ofFloat(dashboardView,"progress",0f,1f);
a.setInterpolator(new AccelerateDecelerateInterpolator());
a.setDuration(3000);
a.setRepeatCount(ValueAnimator.INFINITE);
a.setRepeatMode(ValueAnimator.REVERSE);
a.start();
}
}