在实际开发中,有时候会用到圆形的进度条来显示所占的百分比或者进度,但是Android原生SDK中并没有提供这样的控件,这就需要我们自定义了。
首先,我们分析一下自定义这样一个控件都需要用到哪些属性,长宽自然就不必说了,还有进度、线条的宽度、颜色等等。我们现在values文件夹中创建attrs.xml文件,下面是所需要的属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="color" format="color"/>
<attr name="lineWidth" format="dimension"/>
<attr name="progress" format="integer"/>
<attr name="duration" format="integer"/>
<attr name="useAnim" format="boolean"/>
<declare-styleable name="CircleProgressBar">
<attr name="color"/>
<attr name="lineWidth"/>
<attr name="progress"/>
<attr name="duration"/>
<attr name="useAnim"/>
</declare-styleable>
</resources>
我们可以看到这里有我们需要的几个属性,duration和useAnim的作用我们稍后再讲。创建CircleProgressBar类继承View,并创建构造函数,然后获取控件的属性。
private Paint paint;
private int color;
private int lineWidth;
private int progress;
private int duration;
private boolean useAnim;
public CircleProgressBar(Context context) {
this(context, null);
}
public CircleProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar, defStyleAttr, 0);
color = array.getColor(R.styleable.CircleProgressBar_color, Color.BLACK);
lineWidth = array.getDimensionPixelOffset(R.styleable.CircleProgressBar_lineWidth, 0);
progress = array.getInt(R.styleable.CircleProgressBar_progress, 30);
duration = array.getInt(R.styleable.CircleProgressBar_duration, 2000);
useAnim = array.getBoolean(R.styleable.CircleProgressBar_useAnim, true);
array.recycle();
paint = new Paint();
}
先抛出几个异常:
private void throwException() {
if (getWidth() != getHeight()) {
throw new RuntimeException("width and height must be equal");
}
if (progress > 100) {
throw new RuntimeException("progress can't be bigger than hundred");
}
if (progress < 0) {
throw new RuntimeException("progress can't be smaller than zero");
}
}
可以看到,如果控件的宽高不相等就会抛出一个异常,这也是为了节省一下时间。然后重写onDraw方法,调用throwException。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
throwException();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(Color.parseColor("#e9e9e9"));
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - lineWidth / 2, paint);
paint.setColor(color);
RectF oval = new RectF(lineWidth / 2, lineWidth / 2, getWidth() - lineWidth / 2, getHeight() - lineWidth / 2);
canvas.drawArc(oval, -90, progress * 360 / 100, false, paint);
}
我们先来分析一下上面的代码,先设置画笔的颜色,然后画一个圆,这里的圆是作为进度条的背景颜色用的,drawCircle中的参数分别是圆心的坐标,圆的半径还有画笔,这里需要注意的一点是由于进度条的线条是有宽度的,所以圆的半径并不是简单的用控件的宽度除以2,而是圆心到线条中间一点的位置,所以是控件宽度的一半减去线条宽度的一半。然后画弧度,这里弧线所在矩形的四条边的位置原理和画圆的一样。
我们在布局文件中使用一下,可以看到刚才自定义的属性全部都可以提示出来。
设置颜色和宽度,运行一下就是这个样子了:
那前面的duration和useAnim属性是做什么的呢,我们经常看到有些进度条设置时是有动画的,前面这两个属性就是做这个的,我们来看一下绘制进度的这句代码
canvas.drawArc(oval, -90, progress * 360 / 100, false, paint);
这里我们是用当前进度去设置所画进度的角度,如果我们需要在设置进度时有一个动画过程,那这个progress是一个渐变的,所以我们需要用到属性动画,顾名思义,我们用这个动画是不断改变控件的属性的,所以我么需要在设置进度将progress从0到某个进度进行变化:
public void setProgress(int p) {
if (useAnim)
startAnim(p);
else
invalidate();
}
private void startAnim(int p) {
ValueAnimator animator = ValueAnimator.ofInt(0, p);
animator.setDuration(duration);
animator.start();
animator.addUpdateListener(valueAnimator -> {
progress = (int) valueAnimator.getAnimatedValue();
invalidate();
});
}
这里我们用到了ValueAnimator,在动画监听中将progress从设置的区间中取出,然后刷新,这样就形成了一个动画的效果。
网友评论