效果图:
在实现这个功能是,可以先补充下求值弧的周边和角度根据周长来算出x,y轴的相关数学知识。
根据这个效果图,可以拆分成6个主要点:画半圆刻度,根据半圆的相关位置绘制刻度的具体值和相应的文本值,绘制颜色渐变圆弧,绘制渐变色的透明和指针
1、绘制半圆刻度:5个重要值:圆形的代表的最大值,最小刻度的值,大刻度的值,大刻度的数量,画笔,小刻度的半圆直径和大刻度的半圆直径。
相关的值确定好后,就开始着手画图
在onDraw方法里面进行绘制:
不对,在绘制之前先写好小算法:
根据圆心和半径的值算出x轴和y轴的坐标的算法A:(算法的具体我就不说了,不知道的话可以科普下数学知识)
/**
* 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标
*/
public float[] getCoordinatePoint(int radius, float cirAngle) {
float[] point = new float[2];
double arcAngle = Math.toRadians(cirAngle); //将角度转换为弧度
if (cirAngle < 90) {
point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
} else if (cirAngle == 90) {
point[0] = mCenterX;
point[1] = mCenterY + radius;
} else if (cirAngle > 90 && cirAngle < 180) {
arcAngle = Math.PI * (180 - cirAngle) / 180.0;
point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
} else if (cirAngle == 180) {
point[0] = mCenterX - radius;
point[1] = mCenterY;
} else if (cirAngle > 180 && cirAngle < 270) {
arcAngle = Math.PI * (cirAngle - 180) / 180.0;
point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
} else if (cirAngle == 270) {
point[0] = mCenterX;
point[1] = mCenterY - radius;
} else {
arcAngle = Math.PI * (360 - cirAngle) / 180.0;
point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
}
Log.e("getCoordinatePoint","radius="+radius+",cirAngle="+cirAngle+",point[0]="+point[0]+",point[1]="+point[1]);
return point;
}
/**
* 通过实时数值得到指针角度这个方法很重要
*/
private float getAngleFromResult(float result) {
if (result > mMaxValue)
return 360.0f;
return mSweepAngle * (result - mMinValue) / (mMaxValue - mMinValue) + mStartAngle;
}
根据大刻度的相关属性值,绘制大刻度和大刻度的具体值
for (int i = 0; i <= mBigSliceCount; i++) {
//绘制大刻度
float angle = i * mBigScaleAngle + mStartAngle;
float[] point1 = getCoordinatePoint(mBigScaleRadius, angle);
float[] point2 = getCoordinatePoint(mSmallScaleRadius, angle);
canvas.drawLine(point1[0], point1[1], point2[0], point2[1], mPaintScale);
//绘制圆盘上的数字
mPaintScaleText.setTextSize(mScaleTextSize);
String number = mGraduations[i]+"%";
mPaintScaleText.getTextBounds(number, 0, number.length(), mRectScaleText);
if (angle % 360 > 135 && angle % 360 < 215) {
mPaintScaleText.setTextAlign(Paint.Align.LEFT);
} else if ((angle % 360 >= 0 && angle % 360 < 45) || (angle % 360 > 325 && angle % 360 <= 360)) {
mPaintScaleText.setTextAlign(Paint.Align.RIGHT);
} else {
mPaintScaleText.setTextAlign(Paint.Align.CENTER);
}
float[] numberPoint = getCoordinatePoint(mNumScaleRadius, angle);
if (i == 0 ) {
//其实刻度和最大刻度不绘制
canvas.drawText(number, numberPoint[0], numberPoint[1] + (mRectScaleText.height() / 2), mPaintScaleText);
} else if (i == mBigSliceCount){
canvas.drawText(number, numberPoint[0]+dpToPx(5), numberPoint[1]+ (mRectScaleText.height() / 2), mPaintScaleText);
} else{
canvas.drawText(number, numberPoint[0], numberPoint[1] + mRectScaleText.height(), mPaintScaleText);
}
}
然后根据小刻度的相关属性来绘制小刻度
//绘制小的子刻度
mPaintScale.setStrokeWidth(dpToPx(1));
for (int i = 0; i < mSmallScaleCount; i++) {
if (i % mScaleCountInOneBigScale != 0) {
float angle = i * mSmallScaleAngle + mStartAngle;
float[] point1 = getCoordinatePoint(mRadius, angle);
float[] point2 = getCoordinatePoint(mSmallScaleRadius, angle);
mPaintScale.setStrokeWidth(dpToPx(1));
canvas.drawLine(point1[0], point1[1], point2[0], point2[1], mPaintScale);
}
}
完成这两步,你就可以得到
以上两步完成就好办了
接下来就是重点,绘制圆角结合点的半圆饼表
:根据不同的数据的占比,算出改数据所占的半圆的弧度的角度,然后根据所占的角度来绘制相应的半圆柱
可以分两步走,
1、先定义一个相应的画笔类,在画笔里面顺便和渐变的参数设置进去
private void drawArcInAngle(Canvas canvas,int[] color,
RectF rectF,float startAngle,
float angleLength,float[] position) {
Paint paint = new Paint();
SweepGradient sweepGradient;
if (color.length == 1){
paint.setColor(color[0]);
}else {
sweepGradient = new SweepGradient(mCenterX, mCenterY,color,position);
paint.setShader(sweepGradient);//设置渐变 从X轴正方向取color数组颜色开始渐变
}
/** 结合处为圆弧*/
paint.setStrokeJoin(Paint.Join.ROUND);
/** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/
paint.setStrokeCap(Paint.Cap.ROUND);
/** 设置画笔的填充样式 Paint.Style.FILL :填充内部;Paint.Style.FILL_AND_STROKE :填充内部和描边; Paint.Style.STROKE :仅描边*/
paint.setStyle(Paint.Style.STROKE);
/**抗锯齿功能*/
paint.setAntiAlias(true);
/**设置画笔宽度*/
paint.setStrokeWidth(borderWidth);
/**绘制圆弧的方法
* * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,
参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,
参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。
参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。
参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线,
参数五是Paint对象;
*/
canvas.drawArc(rectF, startAngle, angleLength, false, paint);
}
2、算出矩阵参数
RectF rectF = null;
if (borderWidth > 0) {
int r = mSmallScaleRadius - borderWidth / 2 - dpToPx(5) ;
rectF = new RectF(mCenterX - r, mCenterY - r, mCenterX + r, mCenterY + r);
}
再然后就是绘制圆饼图
oldValue = bean.getX();
if (bean.getX() == mMaxValue){
drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,mSweepAngle-oldAngle,null);
}else {
drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,getAngleFromResult(bean.getX())-mStartAngle-oldAngle,null);
}
然后再在相应的数值中间绘制备注文本,一起的代码就是如下
for (WarnPanelBean bean : warnPanelBeans){
float angle = ((bean.getX()-oldValue)/2+oldValue) * (mSweepAngle/mMaxValue)+ mStartAngle;
float[] numberPoint = getCoordinatePoint(mNumScaleRadius, angle);
//绘制圆盘上的备注
mPaintScaleText.setTextSize(mScaleTextSize+2);
String number = bean.getRemark();
mPaintScaleText.getTextBounds(number, 0, number.length(), mRectScaleText);
if (angle % 360 > 135 && angle % 360 < 215) {
mPaintScaleText.setTextAlign(Paint.Align.LEFT);
} else if ((angle % 360 >= 0 && angle % 360 < 45) || (angle % 360 > 325 && angle % 360 <= 360)) {
mPaintScaleText.setTextAlign(Paint.Align.RIGHT);
} else {
numberPoint[1] += 10 ;
mPaintScaleText.setTextAlign(Paint.Align.CENTER);
}
canvas.drawText(number, numberPoint[0], numberPoint[1], mPaintScaleText);
oldValue = bean.getX();
if (bean.getX() == mMaxValue){
drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,mSweepAngle-oldAngle,null);
}else {
drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,getAngleFromResult(bean.getX())-mStartAngle-oldAngle,null);
}
oldAngle = getAngleFromResult(bean.getX())-mStartAngle;
}
上面几步完成之后,下面的就是小问题了,
//绘制中心点的圆
mPaintCirclePointer.setStyle(Paint.Style.FILL);
mPaintCirclePointer.setStrokeWidth(dpToPx(4));
canvas.drawCircle(mCenterX, mCenterY, mCircleRadius/2, mPaintCirclePointer);
//绘制三角形指针
path.reset();
mPaintCirclePointer.setStyle(Paint.Style.FILL);
float[] point1 = getCoordinatePoint(mCircleRadius / 2 - yOffset, initAngle + 90);
path.moveTo(point1[0], point1[1]);
float[] point2 = getCoordinatePoint(mCircleRadius / 2 - yOffset, initAngle - 90);
path.lineTo(point2[0], point2[1]);
float[] point3 = getCoordinatePoint(mPointerRadius, initAngle);
path.lineTo(point3[0], point3[1] );
path.close();
canvas.drawPath(path, mPaintCirclePointer);
// 绘制三角形指针底部的圆弧效果
canvas.drawCircle((point1[0] + point2[0]) / 2, (point1[1] + point2[1]) / 2-yOffset, mCircleRadius+dpToPx(2), mPaintCirclePointer);
然后就是绘制具体值得值,就是图标的60%大字
//绘制实时值
canvas.drawText(trimFloat(mRealTimeValue)+" "+ mUnitText, mCenterX, mCenterY +realTimeTextYOffset, mPaintValue);
对了,再有的就是根据具体值来绘制相应的扇形渐变蒙版
mPaintRibbon = new Paint();
mPaintRibbon.setAntiAlias(true);
mPaintRibbon.setStrokeJoin(Paint.Join.ROUND);//结合处弧形
mPaintRibbon.setStrokeWidth(mRibbonWidth);
mPaintRibbon.setAlpha(120);
mPaintRibbon.setStyle(Paint.Style.STROKE);
mSweepGradient = new SweepGradient(mCenterX, mCenterY,colors,null);
mPaintRibbon.setShader(mSweepGradient);//设置渐变 从X轴正方向取color数组颜色开始渐变
if (mRibbonWidth > 0) {
//int r = mRadius - mRibbonWidth / 2 + dpToPx(1) ;
int r = mRadius - mRibbonWidth / 2 + dpToPx(1) ;
mRectRibbon = new RectF(mCenterX - r, mCenterY - r, mCenterX + r, mCenterY + r);
}
// 绘制色带
canvas.drawArc(mRectRibbon, mStartAngle, getAngleFromResult(mRealTimeValue)-mStartAngle, false, mPaintRibbon);
然后就达到了图上的效果