Android绘制圆形进度条
一、背景介绍
我们在项目中,经常会见到圆形进度条,看起来很美观、直观。刚好最近项目中有这样的需求,记录一下,顺便回顾下自定义View的知识。

二、实现思路
自定义View,就是在画布中绘制View,需要重写onDraw方法。该View可以拆分成以下几部分:
1)需要画一个浅绿色的圆
2)需要画一个白色的圆
3)圆圈中有进度数字的显示
4)圆圈中可以自定义顶部和底部不同文案的提示
三、主要方法介绍
1、drawArc:由上图可以看出,该圆需要画出圆弧表示进度,所以选择drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)方法。
1)参数:
oval-用于确定圆弧形状与尺寸的椭圆边界(即椭圆外切矩形)
startAngle-开始角度(以时钟3点钟为0°,逆时针为正方向)
sweepAngle-旋转角度(以时钟3点钟为0°,逆时针为正方向)
useCenter-是否包含圆心
paint-画笔
2)绘制原理
- 当RectF(float left,float top,float right,float bottom)中right-left等于bottom-top时(长=宽),这时画出的就是个圆。

- 以矩形中心为圆心,以3点钟方向为0°,逆时针为正方向,从0°旋转startAngle度,和椭圆相交得到一条直线和一个焦点。

- 从这条直线开始,正方向旋转sweepAngle度,得到另一条直线和焦点,这样就可以得到两个焦点间的圆弧。

2、drawText(String text, float x, float y, Paint paint):文本的绘制方法。
参数:
text-文本
x-该文本的左边与屏幕左边的距离
y-该文本baseline在屏幕上的位置
paint-画笔
需要注意的是,参数y不是表示竖直方向上的位置,而是该文本baseline在屏幕上的位置。
根据官方API说明,Paint的TextAlign属性决定了text相对于起始坐标x的相对位置。默认left,文本从x的右边开始绘制,如果是center,则x坐标在文本的中间。
baseline的介绍参考:http://www.xyczero.com/blog/article/20/
四、画圆
1)画一个背景圆
//1-圆弧的位置:整圆,再绘制进度圆弧
mArcCirclePaint.setColor(mCircleBackgroundColor);
mArcCirclePaint.setStrokeWidth(mStrokeWidth);
//屏幕宽度
int width = getMeasuredWidth();
RectF rectF = new RectF();
rectF.left = (width-mWidth)/2;//左上角X
rectF.top = mWidth*0.1f;//左上角Y
rectF.right = (width-mWidth)/2+mWidth;//右上角X
rectF.bottom = mWidth*0.9f;//右上角Y
if ((rectF.right - rectF.left) > (rectF.bottom- rectF.top)){//正方形矩形,保证画出的圆不会变成椭圆
float space = (rectF.right - rectF.left) - (rectF.bottom- rectF.top);
rectF.left += space/2;
rectF.right -= space/2;
}
canvas.drawArc(rectF,270,360,false,mArcCirclePaint);//第2个参数:时钟3点处为0度,逆时针为正方向
2)画进度圆
使用同一个Paint,改变其颜色,在画布上绘制一样大小的圆,只是旋转角度值不一样。
mArcCirclePaint.setColor(mProgressColor); //设置边角为圆 mArcCirclePaint.setStrokeCap(Paint.Cap.ROUND); mArcCirclePaint.setStrokeWidth(mInnerStrokeWidth); canvas.drawArc(rectF,270,mAngleValue,false,mArcCirclePaint);
3)绘制文本
不同文本只是位置不一样,计算好位置就可以绘制出文本了。
//2-文本的位置:居中显示
int centerX = width/2;
//计算文本宽度
int textWidth = (int) mTextPaint.measureText(mText, 0, mText.length());
//计算baseline:垂直方向居中
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
int textX = centerX-textWidth/2;
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
canvas.drawText(mText,textX,baseline,mTextPaint);
if (mTopText != null && !mTopText.equals("")) {
textWidth = (int) mTextPaint.measureText(mTopText, 0, mTopText.length());
textX = centerX - textWidth / 2;
mTextPaint.setTextSize(mTopTextSize);
mTextPaint.setColor(mTopTextColor);
canvas.drawText(mTopText, textX, baseline - 20, mTextPaint);
}
if (mBottomText != null && !mBottomText.equals("")) {
textWidth = (int) mTextPaint.measureText(mBottomText, 0, mBottomText.length());
textX = centerX - textWidth / 2;
// mTextPaint.reset();
// mTextPaint.setAntiAlias(true);
// mTextPaint.setLinearText(true);
mTextPaint.setTextSize(mTopTextSize);
mTextPaint.setColor(mBottomTextColor);
canvas.drawText(mBottomText, textX, baseline + 20, mTextPaint);
}
五、总结
其实很多的自定义View都是在画布canvas中画出来的,看着复杂(其实难在位置的计算),但是只要将其拆分成几部分,一一画出再组合就好了。
附上源码:工程demo
package com.example.ViewDemo;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;
/**
* 自定义View方式三:重新绘制,继承View
* 第一步:画出外圆drawArc
* 第二步:画出进度圆drawArc
* 第三步:画出文本:中间文本,顶部文本,底部文本drawText
* Created by cjy on 17/6/14.
*/
public class ArcCircleView extends View {
private Context mContext;
/**
* 文本画笔
*/
private Paint mTextPaint;
/**
* 圆弧画笔
*/
private Paint mArcCirclePaint;
/**
* 宽度
*/
private float mWidth = 100.0f;
/**
* 文本
*/
private String mText ="0%";
/**
* 底部文本
*/
private String mTopText ="";
/**
* 底部文本
*/
private String mBottomText ="";
/**
* 弧度
*/
private int mAngleValue = 0;
/**
* 圆的背景色:默认浅绿色
*/
private int mCircleBackgroundColor = 0x4c11af9c;
/**
* 进度的颜色,默认白色
*/
private int mProgressColor = 0xffffffff;
/**
* 顶部文本的颜色,默认白色
*/
private int mTopTextColor = 0xffffffff;
/**
* 底部文本的颜色,默认白色
*/
private int mBottomTextColor = 0xffffffff;
/**
* 文本的颜色,默认白色
*/
private int mTextColor = 0xffffffff;
/**
* 边宽
*/
private int mStrokeWidth = 8;
/**
* 进度圆边宽
*/
private int mInnerStrokeWidth = 7;
/**
* 文本大小
*/
private int mTextSize = 12;
/**
* 顶部文本大小
*/
private int mTopTextSize = 10;
public ArcCircleView(Context context) {
super(context);
init(context);
}
public ArcCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ArcCircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context){
mContext = context;
mTextPaint = new Paint();
//设置抗锯齿
mTextPaint.setAntiAlias(true);
//使文本看起来更清晰
mTextPaint.setLinearText(true);
mArcCirclePaint = new Paint();
mArcCirclePaint.setAntiAlias(true);
mArcCirclePaint.setStyle(Paint.Style.STROKE);
}
public void setWidth(float width){
mWidth = width;
invalidate();
}
public void setText(String text){
mText = text;
invalidate();
}
public void setTopText(String text){
mTopText = text;
invalidate();
}
public void setBottomText(String text){
mBottomText = text;
invalidate();
}
public void setTextColor(int textColor) {
this.mTextColor = mContext.getResources().getColor(textColor);
invalidate();
}
public void setBottomTextColor(int bottomTextColor) {
this.mBottomTextColor = mContext.getResources().getColor(bottomTextColor);
invalidate();
}
public void setTopTextColor(int topTextColor) {
this.mTopTextColor = mContext.getResources().getColor(topTextColor);
invalidate();
}
public void setProgressColor(int progressColor) {
this.mProgressColor = mContext.getResources().getColor(progressColor);
invalidate();
}
public void setCircleBackgroundColor(int circleBackgroundColor) {
this.mCircleBackgroundColor = mContext.getResources().getColor(circleBackgroundColor);
invalidate();
}
public void setStrokeWidth(int strokeWidth){
this.mStrokeWidth = strokeWidth;
invalidate();
}
public void setInnerStrokeWidth(int innerStrokeWidth){
this.mInnerStrokeWidth = innerStrokeWidth;
invalidate();
}
public void setTextSize(int textSize){
this.mTextSize = textSize;
invalidate();
}
public void setTopTextSize(int topTextSize){
this.mTopTextSize = topTextSize;
invalidate();
}
/**
* 设置进度
* @param progress
*/
public void setProgress(float progress){
int angleValue = (int) ((progress * 1.0)/100 * 360);
if (angleValue != 0 && progress <= 100){
mAngleValue = angleValue;
mText = String.valueOf(progress)+"%";
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//1-圆弧的位置:整圆,再绘制进度圆弧
mArcCirclePaint.setColor(mCircleBackgroundColor);
mArcCirclePaint.setStrokeWidth(mStrokeWidth);
//屏幕宽度
int width = getMeasuredWidth();
RectF rectF = new RectF();
rectF.left = (width-mWidth)/2;//左上角X
rectF.top = mWidth*0.1f;//左上角Y
rectF.right = (width-mWidth)/2+mWidth;//右上角X
rectF.bottom = mWidth*0.9f;//右上角Y
if ((rectF.right - rectF.left) > (rectF.bottom- rectF.top)){//正方形矩形,保证画出的圆不会变成椭圆
float space = (rectF.right - rectF.left) - (rectF.bottom- rectF.top);
rectF.left += space/2;
rectF.right -= space/2;
}
canvas.drawArc(rectF,270,360,false,mArcCirclePaint);//第2个参数:时钟3点处为0度,逆时针为正方向
mArcCirclePaint.setColor(mProgressColor);
//设置边角为圆
mArcCirclePaint.setStrokeCap(Paint.Cap.ROUND);
mArcCirclePaint.setStrokeWidth(mInnerStrokeWidth);
canvas.drawArc(rectF,270,mAngleValue,false,mArcCirclePaint);
//2-文本的位置:居中显示
int centerX = width/2;
//计算文本宽度
int textWidth = (int) mTextPaint.measureText(mText, 0, mText.length());
//计算baseline:垂直方向居中
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
int textX = centerX-textWidth/2;
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
canvas.drawText(mText,textX,baseline,mTextPaint);
if (mTopText != null && !mTopText.equals("")) {
textWidth = (int) mTextPaint.measureText(mTopText, 0, mTopText.length());
textX = centerX - textWidth / 2;
mTextPaint.setTextSize(mTopTextSize);
mTextPaint.setColor(mTopTextColor);
canvas.drawText(mTopText, textX, baseline - 20, mTextPaint);
}
if (mBottomText != null && !mBottomText.equals("")) {
textWidth = (int) mTextPaint.measureText(mBottomText, 0, mBottomText.length());
textX = centerX - textWidth / 2;
// mTextPaint.reset();
// mTextPaint.setAntiAlias(true);
// mTextPaint.setLinearText(true);
mTextPaint.setTextSize(mTopTextSize);
mTextPaint.setColor(mBottomTextColor);
canvas.drawText(mBottomText, textX, baseline + 20, mTextPaint);
}
}
}

浙公网安备 33010602011771号