很久没有发博客了,准备从现在开始多写多发,把自己平时遇见的坑和心得一点点记录下来,算是记录自己的成长吧。
话不多说,今天分享的是圆形进度条的实现思路。源于公司之前要实现的一个控件,当时可是费了我一番心思才找到实现方式。上效果图
这个效果的实现主要是使用到了Canvas的一个属性,叫PorterDuffXfermode,这个属性可以将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,以实现如取交集等许许多多有趣的效果,
有兴趣的人可以参考一下这篇博客
Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解
讲解的十分清楚。
我们来看看实现思路,首先,我们在底部画一个圆
然后,再这个圆的上方画一个矩形,覆盖圆的下半部分,作为进度条,如图
然后,我们将超出圆形部分的矩形消除,只显示相交部分(打叉的部分消除)
然后我们在代码里实现我们的功能
首先,定义我们需要用到的对象,然后初始化它
/**
* 剩余百分比
*/
private float progress;
/**
* 圆的半径
*/
private float radius;
/**
* 背景圆画笔
*/
private Paint circlePaint;
/**
* 进度的画笔
*/
private Paint progressPaint;
public CircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public CircleProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleProgressView(Context context) {
super(context);
init();
}
/**
* 初始化
*/
private void init() {
radius = 400;
circlePaint = new Paint();
// 抗锯齿
circlePaint.setAntiAlias(true);
circlePaint.setColor(0xffff0000);
circlePaint.setStyle(Style.FILL);
progressPaint = new Paint();
progressPaint.setAntiAlias(true);
progressPaint.setColor(0xff00ff00);
progressPaint.setStyle(Style.FILL);
// 设置Mode,使超出界限的部分不显示在屏幕上,并且相交部分显示上部
progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
}
可以看到,我们为用于画进度的画笔progressPaint设置了一个属性,progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
这个属性的意思是,用这个画笔画出来的图形,和下方的图形取交集,并且用它的颜色值替换下方图形的颜色值,用来实现我们上分所说的将超出部分隐藏的需求。
接着,我们看看onDraw(Canvas canvas)方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
// 画背景圆
canvas.drawCircle(radius, radius, radius, circlePaint);
// 画进度
canvas.drawRect(0, 2 * radius * (1 - progress), 2 * radius, 2 * radius, progressPaint);
canvas.restoreToCount(layerId);
}
首先,通过canvas.saveLayer()新建一个透明的layer,新建的layer放置在canvas默认layer的上部,当我们执行了canvas.saveLayer()之后,我们所有的绘制操作都绘制到了我们新建的layer上,而不是canvas默认的layer。
然后我们在这个layer上画背景圆,画代表进度的矩形,然后调用restoreToCount(layerId)将该layer绘制到canvas默认的layer上面。
然后我们的功能就实现啦。
完整的代码如下
/**
* @author liuhuiqiang
*/
public class CircleProgressView extends View {
/**
* 剩余百分比
*/
private float progress;
/**
* 圆的半径
*/
private float radius;
/**
* 背景圆画笔
*/
private Paint circlePaint;
/**
* 进度的画笔
*/
private Paint progressPaint;
public CircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public CircleProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleProgressView(Context context) {
super(context);
init();
}
/**
* 初始化
*/
private void init() {
radius = 400;
circlePaint = new Paint();
// 抗锯齿
circlePaint.setAntiAlias(true);
circlePaint.setColor(0xffff0000);
circlePaint.setStyle(Style.FILL);
progressPaint = new Paint();
progressPaint.setAntiAlias(true);
progressPaint.setColor(0xff00ff00);
progressPaint.setStyle(Style.FILL);
// 设置Mode,使超出界限的部分不显示在屏幕上,并且相交部分显示上部
progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
// 新建一个透明图层
int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
// 画背景圆
canvas.drawCircle(radius, radius, radius, circlePaint);
// 画进度
canvas.drawRect(0, 2 * radius * (1 - progress), 2 * radius, 2 * radius, progressPaint);
canvas.restoreToCount(layerId);
}
public void setProgress(float progress) {
this.progress = progress;
postInvalidate();
}
}
然后我们在这个基础上改一改就可以做出很多绚丽的效果了,比如360的加速球
新手上路,如果有错漏的地方,欢迎指正