Android自定义View之圆形比例图(进度条)

               在我们APP中,经常会用到如下的一张比例图:         
                            Android自定义View之圆形比例图(进度条)_第1张图片
                这个比例图可以清楚的展示一个项目所占的比例,同时也可以变成一个圆形进度条:
                       Android自定义View之圆形比例图(进度条)_第2张图片
                                                                                 
                显而易见,这是一个自定义View,那么该如何创建这样一个自定义View呢?我们先来简单的回顾一下自定义View的流程,分三步,onMesure(),onLayout,onDraw()。这个自定义View比较简单,分为三部分:中间的圆,中间显示的文字、外圈的弧线。既然有了这样的思路,我们只要在onDraw()方法中一个一个的去绘制就可以了。
          接下来,我们就从代码入手一步一步自定义这个View。
          1、我们先来看下需要用到的属性:
//PercentCircleView的宽高
private float mWidth;
private float mHeight;
//圆心坐标
private float mCircleXY;
//圆的半径
private float mRadius;
//弧线的外接矩形
private RectF rectF;
//画笔
private Paint paint;
private Paint textPaint;
private Paint circlePaint;
//最大进度
private int mMaxProgress = 100;
//当前进度
private int mProgress = 30;
//外层弧线的宽
private int mCircleLineStrokeWidth;
               2、看看我们的onDraw()方法里面做了哪些工作:
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //得到视图的宽高取最小值给宽
    mWidth = getWidth();
    mHeight = getHeight();
    mWidth = Math.min(mWidth, mHeight);

    //确定圆心,半径,外弧的宽度
    mCircleXY = mWidth / 2;
    mRadius = mCircleXY / 2;
    mCircleLineStrokeWidth = (int) (mRadius / 2);

    paint.setAntiAlias(true);
    paint.setColor(Color.rgb(0x00, 0xff, 0x00));
    canvas.drawColor(Color.TRANSPARENT);
    paint.setStrokeWidth(mCircleLineStrokeWidth);
    paint.setStyle(Paint.Style.STROKE);

    circlePaint.setAntiAlias(true);
    circlePaint.setColor(Color.rgb(0x00, 0xff, 0x00));

    //绘制弧线,需要制定其椭圆的外接矩形
    rectF.left = mCircleLineStrokeWidth / 2;
    rectF.top = mCircleLineStrokeWidth / 2;
    rectF.right = mWidth - mCircleLineStrokeWidth / 2;
    rectF.bottom = mWidth - mCircleLineStrokeWidth / 2;

    //绘制圆
    canvas.drawCircle(mCircleXY,mCircleXY,mRadius,circlePaint);
    //绘制弧线
    canvas.drawArc(rectF,-90,((float) mProgress/mMaxProgress) * 360,false,paint);
    //绘制文字
    String text = mProgress + "%";
    float showTextSize = mWidth / 10;
    textPaint.setTextSize(showTextSize);
    textPaint.setColor(Color.rgb(0xff, 0x00, 0x00));
    canvas.drawText(text,0,text.length(),mCircleXY - (showTextSize * text.length())/4,mCircleXY + showTextSize / 3 ,textPaint);
}
               3、我们对外提供一个设置比例(进度)的方法,调用者可以通过调用此方法来改变比例(进度):
//对外提供设置进度的方法
public void setmProgress(int  progress){
    if(progress >= 0) {
        mProgress = progress;
    } else {
        mProgress = 0;
    }
    this.invalidate();
}
              4、在布局文件中使用:

          现在,运行一下我们的程序,结果如下图:
          Android自定义View之圆形比例图(进度条)_第3张图片
            到此为止,基本已经成型了,但是此时layout_width 和 layout_height 使用 wrap_content无效,对于直接继承View的控件,如果不对wrap_content做特殊的处理,那么使用wrap_content就相当于使用match_parent,我这了不予测试,读者可以自行测试,为什么会这样呢,通过解读View的onMeasure()方法的源码可知,如果View在布局中使用Wrap_content,那么它的specMode为AT_MOST模式,在这种模式下,它的宽高等于specSize,在这种情况下,View的specSize是parentSize,而parentSize是父容器中目前可以使用的大小,也就是父容器当前剩余的空间大小。这下就明白了,View的宽高就等于父容器当前剩余空间的大小,这种效果和在布局中使用match_parent完全一致。如何解决这个问题呢?也很简单,重写onMeasure()方法,,指定一个wrap_content模式的默认宽高:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //取出宽高的测量模式和大小
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    //处理layout_width 和 layout_height 为 wrap_content的这种情况,使wrap_content有效
    if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(200,200);
    } else if(widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(200,heightSpecSize);
    } else if(heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpecSize,200);
    }
}
          此时layout_width 和 layout_height 使用 wrap_content已生效。为了追求自定义View的完美,你还可以让你的View支持padding,如果不在onDraw()方法中处理padding,那么padding属性是无法起作用的。
        5、在MainActivity中,我们通过Handler发送延迟消息来模拟进度的增长:
 private int progress;

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if(progress < 100) {
                progress ++;
                circleView.setmProgress(progress);
                sendEmptyMessageDelayed(0,200);
            } else {
                removeCallbacksAndMessages(null);
            }
        }
    };
    private PercentCircleView circleView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        circleView = (PercentCircleView) findViewById(R.id.circle);
//        circleView.setmProgress(80);
        handler.sendEmptyMessageDelayed(0,200);
    }

            最后,代码传送门


你可能感兴趣的:(安卓开发,Android自定义View)