马上要离开现单位了,蛋蛋的忧伤。。。以此纪念吧!
需求:
分析:
1、三条不同颜色的圆弧
2、最外层圆弧颜色根据进度不同渐变
3、外层圆弧内部边缘文字描述
4、中心部分文字:健康指数
5、中间层圆弧进度显示
解析:
1、画三条圆弧角度为120°~300°
/**
* 准备工作。。
*
* @param canvas
*/
private void drawAllArc(Canvas canvas) {
float flagWidth = 0.0f;
flagWidth = (mWidth - strokeOuterWidth);
initPaint(outerColor, strokeOuterWidth, strokeOuterWidth,
strokeOuterWidth, flagWidth, flagWidth);
mColors = new int[] {// 渐变色数组
0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFF00,
0xFFFF0000 };
// 渐变着色器
Shader s = new SweepGradient(0, 0, mColors, null);
mPaint.setShader(s);
// 外层 第一段
canvas.drawArc(oval, startAngle, sweetAngle / 3, false, mPaint);
mPaint.setColor(getContext().getResources().getColor(
R.color.outerColor2));
// 外层 第二段
canvas.drawArc(oval, startAngle + sweetAngle / 3, sweetAngle / 3,
false, mPaint);
mPaint.setColor(getContext().getResources().getColor(
R.color.outerColor3));
// 外层 第三段
canvas.drawArc(oval, startAngle + sweetAngle / 3 * 2, sweetAngle / 3,
false, mPaint);
flagWidth = flagWidth - strokeMidWidth;
initPaint(innerColor,
strokeMidWidth, mOffset + strokeMidWidth + strokeOuterWidth,
mOffset + strokeMidWidth + strokeOuterWidth,
flagWidth - mOffset,
flagWidth - mOffset);
// 画中间层
canvas.drawArc(oval, startAngle, sweetAngle, false, mPaint);
flagWidth = flagWidth - strokeOuterWidth;
initPaint(innerColor, strokeInnerWidth, mOffset*2 + strokeInnerWidth + strokeMidWidth + strokeOuterWidth,
mOffset*2 + strokeInnerWidth + strokeMidWidth + strokeOuterWidth,
flagWidth - mOffset*2,
flagWidth - mOffset*2);
// 画内层
canvas.drawArc(oval, startAngle, sweetAngle, false, mPaint);
}
其中initPaint()方法是对画笔的一些初始化操作,如下:
/**
* 初始化/重置 画笔
*
* @param outerColor
* @param strokeOuterWidth
* @param left
* @param top
* @param right
* @param bottom
*/
private void initPaint(int outerColor, float strokeOuterWidth, float left,
float top, float right, float bottom) {
mPaint.reset();
mPaint.setColor(outerColor);
mPaint.setStrokeWidth(strokeOuterWidth);
mPaint.setAntiAlias(true); // 消除锯齿
mPaint.setStyle(Paint.Style.STROKE); // 设置空心
oval = new RectF(left, top, right, bottom);
}
oval 是它所在的矩形,由此确定位置。
2、颜色渐变
mColors = new int[] {// 渐变色数组
0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFF00,
0xFFFF0000 };
// 渐变着色器
Shader s = new SweepGradient(0, 0, mColors, null);
mPaint.setShader(s);
主要用到 SweepGradient 为 mPaint 设置着色器,第三个参数为颜色数组,以此来确定渐变颜色值。(该方法具体使用请参考官方Api)
3、外层圆弧内部边缘文字描述
/**
* 主要用到api path.addArc 和 canvas.drawTextOnPath
* 具体参数可以上网查查,这个应该可以计算出一套算法的,我这里是一点点试出来的,(好尴尬,不会算法)
*
* @param canvas
*/
private void drawShowTextOnPath(Canvas canvas) {
RectF ovalF = new RectF(strokeOuterWidth, strokeOuterWidth, mWidth
- strokeOuterWidth, mWidth - strokeOuterWidth);
float vOffset = 45;
mTextPath.reset();
mTextPath.addArc(ovalF, startAngle, 5);
canvas.drawTextOnPath("0", mTextPath, 0, vOffset, textPaint);
// 每次要 reset
mTextPath.reset();
mTextPath.addArc(ovalF, startAngle, 150 - 75);
canvas.drawTextOnPath("差", mTextPath, 0, vOffset, textPaint);
mTextPath.reset();
mTextPath.addArc(ovalF, startAngle, 150);
canvas.drawTextOnPath("25", mTextPath, 0, vOffset, textPaint);
mTextPath.reset();
mTextPath.addArc(ovalF, startAngle, 150 + 75);
canvas.drawTextOnPath("中", mTextPath, 0, vOffset, textPaint);
mTextPath.reset();
mTextPath.addArc(ovalF, startAngle, 300);
canvas.drawTextOnPath("50", mTextPath, 0, vOffset, textPaint);
mTextPath.reset();
mTextPath.addArc(ovalF, sweetAngle, 25);
canvas.drawTextOnPath("良", mTextPath, 0, vOffset, textPaint);
mTextPath.reset();
mTextPath.addArc(ovalF, sweetAngle, 100);
canvas.drawTextOnPath("75", mTextPath, 0, vOffset, textPaint);
mTextPath.reset();
mTextPath.addArc(ovalF, sweetAngle, 165);
canvas.drawTextOnPath("优", mTextPath, 0, vOffset, textPaint);
mTextPath.reset();
mTextPath.addArc(ovalF, sweetAngle , 225);
canvas.drawTextOnPath("100", mTextPath, 0, vOffset, textPaint);
}
主要用到api path.addArc 和 canvas.drawTextOnPath,具体使用方法参考官方Api
4、中心部分文字:健康指数
/**
* 画文字--健康指数质量
*
* @param canvas
*/
private void setMidText(Canvas canvas) {
mTextStrPaint.setTextSize(22);
mTextStrPaint.getTextBounds(strText, 0, strText.length(), boundsText);
Log.e("VIEW_LOG_TAG", boundsText.right + "," + boundsText.bottom + ","
+ boundsText.top + "," + boundsText.left);
canvas.drawText(strText, mWidth/2 - (float) (boundsText.right / 2)
+ (float) (boundsText.left / 2), mWidth/2 + mTextOffset, mTextStrPaint);
}
/**
* 画文字--健康指数数量
*
* @param canvas
*/
private void setMidTextNum(Canvas canvas) {
mTextNumPaint.setTextSize(45);
mTextNumPaint.getTextBounds(strTextNum, 0, strTextNum.length(), bounds);
System.out.println(bounds.right + "," + bounds.bottom);
Log.e("VIEW_LOG_TAG", bounds.right + "," + bounds.bottom + ","
+ bounds.top + "," + bounds.left);
canvas.drawText(strTextNum, mWidth/2 - (float) (bounds.right / 2)
+ (float) (bounds.left / 2), mWidth/2, mTextNumPaint);
}
其中难点就是要计算文字显示位置,首先确定文字所在矩形
mTextStrPaint.getTextBounds(strText, 0, strText.length(), boundsText);
然后调用 drawText
android.graphics.Canvas.drawText(String text, float x, float y, Paint paint)
其中 x , y 是确定它所绘制的位置
x = mWidth/2 - (float) (boundsText.right / 2)
+ (float) (boundsText.left / 2);
y = mWidth/2 + mTextOffset;
由总宽度的一半减去文字的一半就可以确定文字开始绘制的位置了。
5、中间层圆弧进度显示
/**
* 根据当前进度画线
*
* @param canvas
*/
private void setAngleProgress(Canvas canvas) {
initPaint(midColor, strokeMidWidth,
mOffset + strokeMidWidth + strokeOuterWidth,
mOffset + strokeMidWidth + strokeOuterWidth,
mWidth - strokeOuterWidth - strokeMidWidth - mOffset,
mWidth - strokeOuterWidth - strokeMidWidth - mOffset);
float progress = this.mProgress * sweetAngle / 100.0f;
// 画中间层
canvas.drawArc(oval, startAngle, progress, false, mPaint);
}
其原理就是在中间弧线上根据进度绘制一条弧线,因为总弧度为300°,所以其转换为当前进度(0-100)* 300 / 100 , 这样就可以计算出需要绘制的进度了。
至此整个过程已经结束了,其中不足之处就是绘制外层圆弧文字的时候计算问题,没有找到规律,对Api使用不是太熟悉,如果有哪位朋友能改善希望告知,不胜感激!
对了,还有适配的问题,在这里我是重写 onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
{
mWidth = specSize;
}
/***
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
{
mHeight = specSize;
}
mWidth = mHeight = Math.max(mWidth, mHeight);
if(mWidth > mScreenWidth || mWidth == 0){
mWidth = mScreenWidth;
}
Log.e("xxx", "EXACTLY" + mWidth + " " + mHeight);
setMeasuredDimension(mWidth,mWidth); // 强制长宽相等
}
如果 xml 里没有指定宽高或者指定的高度大于屏幕宽度的话则
mWidth = mScreenWidth;
然后 setMeasuredDimension(mWidth,mWidth); // 强制长宽相等
代码下载地址demo