效果图(完整代码在文章末尾贴出)
图表分析
确定控件width、height,Math.min(width, height)获取当中的最小值,设定为圆形的直径(也可减去一个数值作为预留的边距,但要注意在以后的绘图中计算进去)
绘制背景色圆形:canvas.drawCircle(float centerX, float centerY, float radius, Paint paint); 参数说明:(圆心坐标点x, 圆心坐标点y, 圆形半径, 画笔)
绘制进度弧线:canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint); 具体使用说明移步http://www.cnblogs.com/tjudzj/p/4387145.html
绘制前景色圆形:前景色圆形半径 = 背景色半径 - 进度条的宽度
代码说明
创建java文件继承自View, 并实现构造方法
public class CircleRingGraph extends View {
public CircleRingGraph(Context context, AttributeSet attrs) {
super(context, attrs);
initDefineAttr(context, attrs);
initPaint();
}
public CircleRingGraph(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDefineAttr(context, attrs);
initPaint();
}
}
initDefineAttr(context, attrs);
该方法内部获取xml文件里的自定义的属性信息
如何添加自定义的属性信息:
- 在values文件夹下创建attrs.xml文件
- 声明自定义属性信息的名称、数值类型(name、format),如下:
<!-- 圆形进度条类型图表的相关属性 -->
<declare-styleable name="CircleRingGraph">
<!-- 进度条的背景色 -->
<attr name="progressBackgroundColor" format="color" />
<!-- 进度条颜色 -->
<attr name="progressColor" format="color" />
<!-- 中心圆形的颜色 -->
<attr name="circleHeartColor" format="color" />
<!-- 进度条的宽度 -->
<attr name="progressWidth" format="dimension" />
<!-- 进度条是否显示圆形头部 -->
<attr name="progressRoundCap" format="boolean" />
<!-- 圆心显示的文字 -->
<attr name="centerText" format="string" />
<!-- 圆心显示的文字颜色 -->
<attr name="centerTextColor" format="color" />
<!-- 圆心显示的文字大小 -->
<attr name="centerTextSize" format="dimension" />
</declare-styleable>
- 在自定义View里边获取并设置xml文件里的自定义属性信息,即initDefineAttr(context, attrs) 方法里的内容
private void initDefineAttr(Context context, AttributeSet attrs) {
if (attrs == null)
return;
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleRingGraph);
int count = typedArray.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.CircleRingGraph_progressBackgroundColor:
colorOut = typedArray.getColor(attr, context.getResources().getColor(R.color.circle_out_blue));
break;
case R.styleable.CircleRingGraph_progressColor:
colorPro = typedArray.getColor(attr, context.getResources().getColor(R.color.circle_progress_blue));
break;
case R.styleable.CircleRingGraph_circleHeartColor:
colorIn = typedArray.getColor(attr, context.getResources().getColor(R.color.circle_in));
break;
case R.styleable.CircleRingGraph_progressRoundCap:
roundCap = typedArray.getBoolean(attr, Boolean.FALSE);
break;
case R.styleable.CircleRingGraph_progressWidth:
strokeWidth = typedArray.getDimension(attr, DEFAULT_STROKE_WIDTH);
break;
case R.styleable.CircleRingGraph_centerText:
centerText = typedArray.getString(attr);
break;
case R.styleable.CircleRingGraph_centerTextColor:
centerTextColor = typedArray.getColor(attr, context.getResources().getColor(R.color.colorBlack));
break;
case R.styleable.CircleRingGraph_centerTextSize:
centerTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
typedArray.getDimensionPixelSize(attr, 16), displayMetrics);
break;
default:
break;
}
}
typedArray.recycle();
}
initPaint() 用来设置背景圆、进度条、前景圆、文本数值的Paint的基本信息,放在initDefineAttr() 之后是因为要将自定义的一些属性值设置给Paint。
private void initPaint() {
paintOut = new Paint();
paintOut.setAntiAlias(true);
paintOut.setStyle(Paint.Style.FILL);
paintOut.setColor(colorOut);
paintIn = new Paint();
paintIn.setAntiAlias(true);
paintIn.setStyle(Paint.Style.FILL);
paintIn.setColor(colorIn);
paintPro = new Paint();
paintPro.setAntiAlias(true);
if (roundCap)
paintPro.setStrokeCap(Paint.Cap.ROUND);
paintPro.setStyle(Paint.Style.STROKE);
paintPro.setStrokeWidth(strokeWidth);
paintPro.setColor(colorPro);
paintTxt = new Paint();
paintTxt.setAntiAlias(true);
paintTxt.setTextSize(centerTextSize);
paintTxt.setTextAlign(Paint.Align.CENTER);
paintTxt.setColor(centerTextColor);
}
onSizeChanged()内主要是获取并设置控件的基本数值信息
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
radiusOut = Math.min(w, h) / 2 - DEFAULT_PADDING;
radiusIn = radiusOut - strokeWidth;
centerX = w / 2;
centerY = h / 2;
float left = centerX - radiusOut + strokeWidth / 2;
float top = centerY - radiusOut + strokeWidth / 2;
float right = left + radiusOut * 2 - strokeWidth;
float bottom = top + radiusOut * 2 - strokeWidth;
rectF = new RectF(left, top, right, bottom);
textBaseLineX = centerX;
textBaseLineY = centerY + centerTextSize / 3;
super.onSizeChanged(w, h, oldw, oldh);
}
onSizeChanged() 当中用到的一些变量的解释
radiusOut :背景色圆形的半径
DEFAULT_PADDING:默认预留的控件的Padding
radiusIn :前景色圆形的半径
centerX :控件的中心坐标点 x
centerY:控件的中心坐标点 y
strokeWidth:进度条的宽度
rectF :弧形进度条所占的正方形区域
textBaseLineX :中心数值的基线坐标点 x
textBaseLineY :中心数值的基线坐标点 y
当所有的信息都齐活之后,接下来就是绘制了,绘制当然是用onDraw() 方法了, 废话少说,看代码
@Override
protected void onDraw(Canvas canvas) {
drawCircleRing(canvas);
super.onDraw(canvas);
}
/**
* draw circle ring
*/
private void drawCircleRing(Canvas canvas) {
// 画灰色的圆
canvas.drawCircle(centerX, centerY, radiusOut, paintOut);
// 画进度环
if (progress != 0f) {
canvas.drawArc(rectF, -90, progress, false, paintPro);
}
// 画内填充圆
canvas.drawCircle(centerX, centerY, radiusIn, paintIn);
//画中心位置文字
canvas.drawText((int) progress + "", textBaseLineX, textBaseLineY, paintTxt);
}
看起来soEasy对不对?对的,就是这么简单,不过要注意一点canvas.drawArc()的时候,因为我默认设置的是progress = 0;所以加上if(progress != 0f)的判断,不然界面一进来就默认绘制一段进度条
现在问题来了,既然是带动效的,那一定要能够动起来的。好,那就来加点特效,加动画!
public void doAnimation() {
ValueAnimator animator = ValueAnimator.ofObject(new ProgressEvaluator(), 0f, progress);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
progress = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(1000);
animator.setInterpolator(new OvershootInterpolator());
animator.start();
}
private class ProgressEvaluator implements TypeEvaluator<Float> {
@Override
public Float evaluate(float fraction, Float startValue, Float endValue) {
return startValue + fraction * (endValue - startValue);
}
}
看代码页依然没什么难的,重点有三:
1、 ProgressEvaluator() 的实现,主要在于startValue -> endValue 的关系公式的实现
2、 onAnimationUpdate(ValueAnimator animation) 方法内记得调用invalidate() 来刷新界面,不然怎么会动呢?
3、 动效设置 animator.setInterpolator(new OvershootInterpolator()) Android默认提供了集中动效插值器,各位可以自己尝试下效果。
有关动画的详细的知识,请移步: http://blog.csdn.net/harvic880925/article/details/50995268
本篇完整代码
package customview;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import com.example.zpf.animmenu.R;
/**
* Created by zpf on 2016/11/7.
*/
public class CircleRingGraph extends View {
/**
* 默认的Padding
*/
private final float DEFAULT_PADDING = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
16, getResources().getDisplayMetrics());
/** 默认的进度条的宽度 */
private final float DEFAULT_STROKE_WIDTH = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
16, getResources().getDisplayMetrics());
/**
* centerX:中心坐标点x
* centerY:中心坐标点y
* radiusOut:背景色圆形的半径
* radiusIn:前景色圆形的半径
* */
private float centerX, centerY, radiusOut, radiusIn;
/**
* paintOut:背景色圆形画笔
* paintIn:前景色圆形画笔
* paintPro:进度条的画笔
* paintTxt:中心数据值文本的画笔
* */
private Paint paintOut, paintIn, paintPro, paintTxt;
/**
* colorOut: 进度条背景色
* colorIn: 圆心背景色
* colorPro: 进度条的颜色
*/
private int colorOut = getResources().getColor(R.color.colorGray),
colorIn = getResources().getColor(R.color.circle_in),
colorPro = getResources().getColor(R.color.circle_progress_blue);
/**
* 用来画圆弧的RectF
*/
private RectF rectF;
/**
* 进度条的宽度
*/
private float strokeWidth = DEFAULT_STROKE_WIDTH;
/**
* 显示圆形进度条的头部
*/
private boolean roundCap = Boolean.FALSE;
/**
* 进度条显示的进度
*/
private float progress = 0;
/**
* 圆心位置显示的Text
* centerText: 圆心位置的文本信息
* colorCenterText: TextView文字颜色
* centerTextSize: TextView文字大小
*/
private String centerText;
private int centerTextColor = getResources().getColor(R.color.colorBlack);
private float centerTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
getResources().getDisplayMetrics());
/**
* textBaseLineX:绘制文本的基线x坐标点
* textBaseLineY:绘制文本的基线y坐标点
* */
private float textBaseLineX, textBaseLineY;
public CircleRingGraph(Context context) {
super(context);
}
public CircleRingGraph(Context context, AttributeSet attrs) {
super(context, attrs);
initDefineAttr(context, attrs);
}
public CircleRingGraph(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDefineAttr(context, attrs);
}
public float getProgress() {
return progress;
}
public void setProgress(float progress) {
this.progress = progress;
doAnimation();
}
/**
* 获取自定义的属性值
*/
private void initDefineAttr(Context context, AttributeSet attrs) {
if (attrs == null)
return;
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleRingGraph);
int count = typedArray.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.CircleRingGraph_progressBackgroundColor:
colorOut = typedArray.getColor(attr, context.getResources().getColor(R.color.circle_out_blue));
break;
case R.styleable.CircleRingGraph_progressColor:
colorPro = typedArray.getColor(attr, context.getResources().getColor(R.color.circle_progress_blue));
break;
case R.styleable.CircleRingGraph_circleHeartColor:
colorIn = typedArray.getColor(attr, context.getResources().getColor(R.color.circle_in));
break;
case R.styleable.CircleRingGraph_progressRoundCap:
roundCap = typedArray.getBoolean(attr, Boolean.FALSE);
break;
case R.styleable.CircleRingGraph_progressWidth:
strokeWidth = typedArray.getDimension(attr, DEFAULT_STROKE_WIDTH);
break;
case R.styleable.CircleRingGraph_centerText:
centerText = typedArray.getString(attr);
break;
case R.styleable.CircleRingGraph_centerTextColor:
centerTextColor = typedArray.getColor(attr, context.getResources().getColor(R.color.colorBlack));
break;
case R.styleable.CircleRingGraph_centerTextSize:
centerTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
typedArray.getDimensionPixelSize(attr, 16), displayMetrics);
break;
default:
break;
}
}
typedArray.recycle();
initPaint();
}
/**
* init Paint
*/
private void initPaint() {
paintOut = new Paint();
paintOut.setAntiAlias(true);
paintOut.setStyle(Paint.Style.FILL);
paintOut.setColor(colorOut);
paintIn = new Paint();
paintIn.setAntiAlias(true);
paintIn.setStyle(Paint.Style.FILL);
paintIn.setColor(colorIn);
paintPro = new Paint();
paintPro.setAntiAlias(true);
if (roundCap)
paintPro.setStrokeCap(Paint.Cap.ROUND);
paintPro.setStyle(Paint.Style.STROKE);
paintPro.setStrokeWidth(strokeWidth);
paintPro.setColor(colorPro);
// setLayerType(LAYER_TYPE_SOFTWARE, null);
//setMaskFilter
// paintPro.setMaskFilter(new BlurMaskFilter(1, BlurMaskFilter.Blur.INNER));
//set shadow
// paintPro.setShadowLayer(40, 0, 0, getResources().getColor(R.color.black_alpha_3));
//雷达渐变
// Shader shaderPro = new SweepGradient(centerX, centerY,
// getResources().getColor(R.color.colorPink), getResources().getColor(R.color.colorGreen));
// paintPro.setShader(shaderPro);
//线性渐变
// Shader shaderLinear = new LinearGradient(0, 0, 200, 0, Color.WHITE, Color.RED,
// Shader.TileMode.CLAMP);
// paintPro.setShader(shaderLinear);
paintTxt = new Paint();
paintTxt.setAntiAlias(true);
paintTxt.setTextSize(centerTextSize);
paintTxt.setTextAlign(Paint.Align.CENTER);
paintTxt.setColor(centerTextColor);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
radiusOut = Math.min(w, h) / 2 - DEFAULT_PADDING;
radiusIn = radiusOut - strokeWidth;
centerX = w / 2;
centerY = h / 2;
float left = centerX - radiusOut + strokeWidth / 2;
float top = centerY - radiusOut + strokeWidth / 2;
float right = left + radiusOut * 2 - strokeWidth;
float bottom = top + radiusOut * 2 - strokeWidth;
rectF = new RectF(left, top, right, bottom);
textBaseLineX = centerX;
textBaseLineY = centerY + centerTextSize / 3;
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
drawCircleRing(canvas);
super.onDraw(canvas);
}
/**
* draw circle ring
*/
private void drawCircleRing(Canvas canvas) {
// 画灰色的圆
canvas.drawCircle(centerX, centerY, radiusOut, paintOut);
// 画进度环
if (progress != 0f) {
canvas.drawArc(rectF, -90, progress, false, paintPro);
}
// 画内填充圆
canvas.drawCircle(centerX, centerY, radiusIn, paintIn);
//画中心位置文字
canvas.drawText((int) progress + "", textBaseLineX, textBaseLineY, paintTxt);
}
public void doAnimation() {
ValueAnimator animator = ValueAnimator.ofObject(new ProgressEvaluator(), 0f, progress);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
progress = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(1000);
animator.setInterpolator(new OvershootInterpolator());
animator.start();
}
private class ProgressEvaluator implements TypeEvaluator<Float> {
@Override
public Float evaluate(float fraction, Float startValue, Float endValue) {
return startValue + fraction * (endValue - startValue);
}
}
}
自定义的额属性相关的标签内容如下:
<!-- 圆形进度条类型图表的相关属性 -->
<declare-styleable name="CircleRingGraph">
<!-- 进度条的背景色 -->
<attr name="progressBackgroundColor" format="color" />
<!-- 进度条颜色 -->
<attr name="progressColor" format="color" />
<!-- 中心圆形的颜色 -->
<attr name="circleHeartColor" format="color" />
<!-- 进度条的宽度 -->
<attr name="progressWidth" format="dimension" />
<!-- 进度条是否显示圆形头部 -->
<attr name="progressRoundCap" format="boolean" />
<!-- 圆心显示的文字 -->
<attr name="centerText" format="string" />
<!-- 圆心显示的文字颜色 -->
<attr name="centerTextColor" format="color" />
<!-- 圆心显示的文字大小 -->
<attr name="centerTextSize" format="dimension" />
</declare-styleable>
如有问题,欢迎留言评论!