自定义圆环继承view也可以完成,不过我这里选择继承sufaceview,sufaceview 通常用于频繁的view绘制,比如股票市场动态的折线图,对于这种高频操作,sufaceview要比view来的更合适一些。
sufaceview特性:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。
先贴上效果图:

代码如下:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* 进度圆环
*/
public class RingView extends SurfaceView implements SurfaceHolder.Callback{
/** 画笔*/
private Paint paint;
/** 是否绘制*/
private boolean isDraw;
/** 绘图线程*/
private MyThread myThread;
/** 画布宽*/
private int ringwidth;
/** 画布高*/
private int ringheight;
private RectF rectF;
private SurfaceHolder surfaceHolder;
/** 画布*/
private Canvas canvas;
/** 目标进度 默认为80 */
private int targetvalue=80;
/** 当前进度*/
private int nowvalue=0;
/** 绘制进度文字*/
private String text;
/** 包裹文字的矩形*/
private Rect rect;
/**文字的高度 */
private int textheight;
/**
* 设置目标进度 (0-100)
* @param targetvalue
*/
public void SetPlan(int targetvalue){
if(targetvalue!=0)
this.targetvalue=targetvalue;
}
public RingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public RingView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RingView(Context context) {
super(context);
init();
}
private void init() {
surfaceHolder=getHolder();
surfaceHolder.addCallback(this);
myThread=new MyThread();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ringwidth=measure(widthMeasureSpec,true);
ringheight=measure(heightMeasureSpec,false);
setMeasuredDimension(ringwidth,ringheight);
}
@SuppressWarnings("unused")
private int measure(int MeasureSpecSize, boolean b) {
int result=200;
int size=MeasureSpec.getSize(MeasureSpecSize);
int mode=MeasureSpec.getMode(MeasureSpecSize);
if(mode==MeasureSpec.EXACTLY){
result=size;
}else if(mode==MeasureSpec.AT_MOST){
if(true){
result=Math.max(size,result);
}else{
result=Math.min(size,result);
}
}
return result;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
ringwidth=w;
ringheight=h;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
isDraw=true;
paint=new Paint();
paint.setStyle(Style.STROKE);
paint.setTextSize(60);
paint.setTextAlign(Paint.Align.CENTER);
myThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isDraw=false;
}
class MyThread extends Thread{
@Override
public void run() {
if(isDraw){
drawRing();
}
}
}
/**
* 绘制圆环
*/
public void drawRing() {
int radius=Math.min(ringheight/2, ringwidth/2);
rectF=new RectF(ringwidth/2-radius, ringheight/2-radius, ringwidth/2+radius, ringheight/2+radius);
while(nowvalue<targetvalue){
if(!isDraw)
break;
nowvalue+=1;
text=nowvalue+" %";
canvas=surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);
paint.setColor(Color.GREEN);
canvas.drawArc(rectF, 0, 360, false, paint);
paint.setColor(Color.RED);
canvas.drawArc(rectF, 0,(float) (nowvalue*3.6), false, paint);
MeasureText();
canvas.drawText(text,ringwidth/2, ringheight/2+textheight/2, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 计算文字高
*/
private void MeasureText(){
rect=new Rect();
paint.getTextBounds(text, 0, text.length()-1, rect);
textheight=rect.bottom-rect.top;
}
}
最后还要多说一点,关于 canvas.drawText(text, x, y, paint);我之前一直认为其中的两个参数xy指的是绘制文字的中心点,我会通过paint.getTextBound()把文字设置到Rect矩形内,通过rect.width()及rect.height()来获取文字的宽高,最后在通过整个画布的中心点来调整文字的位置,达到文字居中的效果,然而实际上最后的效果并不是我想要的。其实,x默认是这个字符串的左边在屏幕的位置即origin,如果设置了paint.setTextAlign(Paint.Align.CENTER);那就是字符的中心(文字横向居中,但是纵向不会居中),y是指定这个字符baseline在屏幕上的位置,至于baseline 的位置,这里贴上一张图:

在来张清晰的(来自郭神微信公众号):

看到网上有人对此设计进行吐槽,觉得太麻烦,事实上确实有一点,不过看到这张图,不知道大家有没有觉得像我们以前用的英语本里面的格式线~~至于view的坐标这里也贴上一张图片,有兴趣可以看看:
