Android简易的圆形进度条
自定义View基础入门看2个系列文章,非常优秀的文章。
安卓自定义View教程目录
HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解
Github
项目码云地址
-
最近因为项目需要,点击按钮,实现转一圈的进度条,感觉需求比较简单,就自己做了一个自定义View,很简易,分享给大家。
功力有限,只能一步步完成功能,一步步改代码,最后完成。
-
先看看效果,图片有压缩,丢帧,不是很清晰
- 先初始化画笔,画个圆圈
public class CircleProgressView extends View
{
//控件的宽
private int width;
//控件的高
private int height;
//区域
private RectF rectF;
//底层画笔
private Paint paint_base_progress;
//进度条的画笔
private Paint paint_progress;
//画笔宽度
private int strokeWidth = ConvertUtils.dp2px(5);
//绘制半径
private int radius;
public CircleProgressView(Context context)
{
this(context, null);
}
public CircleProgressView(Context context, @Nullable AttributeSet attrs)
{
this(context, attrs, 0);
}
/**
* 由于要使用xml配置控件属性,所以要3个参数构造方法
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
paint_base_progress = initPaint();
paint_progress = initPaint();
}
/**
* 初始化画笔
*/
private Paint initPaint()
{
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
//笔画圆润,用Paint.Cap.ROUND
paint.setStrokeCap(Paint.Cap.BUTT);
//抗锯齿
paint.setAntiAlias(true);
return paint;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
if (rectF == null)
{
//半径
if (radius == 0)
{
radius = (width > height ? width : height) / 2;
}
rectF = new RectF(width / 2 - radius + strokeWidth / 2
, height / 2 - radius + strokeWidth / 2
, width / 2 + radius - strokeWidth / 2
, height / 2 + radius - strokeWidth / 2);
}
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
//因为圆开始角度是x轴,所以给他旋转270°
canvas.rotate(270f, width / 2, height / 2);
canvas.drawArc(rectF, 0f, 360f, false, paint_base_progress);
}
}
- 这里蓝色就是RectF的大小,黑色就是圆环,画笔其实是中间经过内容的,所以,在确定RectF大小的时候,要算上画笔的一半。
- 接下来给这个圆圈加个渐变色吧
//底层扫描渐变色
private Shader shader_base_progress;
//底层条浅的色
private int base_progress_light_color;
//底层条深的色
private int base_progress_height_color;
//底层颜色数组
private int base_progress_colors[];
//底层颜色分配
private float base_progress_positions[];
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
...
if (shader_base_progress == null)
{
//初始化颜色,如果不设置颜色参数,就会使用这个颜色参数
base_progress_light_color = Color.WHITE;
base_progress_height_color = Color.BLACK;
base_progress_colors = new int[]{base_progress_light_color, base_progress_height_color, base_progress_light_color};
//这个参数就是颜色的分配,第一种颜色到0.1的位置,第二种到0.9的位置,第三种到1的位置,不要超过1。
base_progress_positions = new float[]{0.1f, 0.95f, 1f};
shader_base_progress = new SweepGradient(width / 2, height / 2
, base_progress_colors, base_progress_positions);
paint_base_progress.setShader(shader_base_progress);
}
}
base_progress_colors = new int[]{ Color.WHITE, Color.BLACK, Color.RED};
//这个参数就是颜色的分配,第一种颜色到0.1的位置,第二种到0.9的位置,第三种到1的位置,不要超过1。
base_progress_positions = new float[]{0.1f, 0.8f, 1f};
- 可以看出参数变化之后,颜色配色比例不同的效果,为了过渡的自然,所以我一般喜欢最后的0.05段补上和第一段一样的颜色。
- 这个是底色,然后进度条的写法和这个底色一样样,只是颜色变一变就行啦。
- 下一步,想办法让这个进度条一点点转动,不是整个旋转,底层的圆不动,外层的进度条一点点加,需要使用Handler
//顺时针
private boolean progress_clockwise = true;
//速度,刷新频率最快60帧,参数为每次刷新的间隔,单位:毫秒
private long speed = 17;
//转一圈的周期,单位:毫秒
private long period = 3000;
//循环
private boolean progress_recycler = false;
//暂停
private boolean progress_pause = false;
//开始
private static final int START = 1111;
//结束
private static final int COMPLETE = 1000;
private Handler handler = new Handler(new Handler.Callback()
{
@Override
public boolean handleMessage(Message msg)
{
switch (msg.what)
{
case START:
//顺时针转
if (progress_clockwise)
{
//每圈360°不变,共period辣么长的ms,360f/period算出每ms转的角度,再用结果*speek就是每次刷新转的角度
progress_angle += 360f / period * speed ;
}
//逆时针转
else
{
progress_angle -= 360f / period * speed ;
}
//转满一圈就停止,并且恢复底色,绝对值>360
if (Math.abs(progress_angle) > 360)
{
drawing = false;
handler.sendEmptyMessage(COMPLETE);
} else
{
drawing = true;
invalidate();
//我试过,1ms,10ms,速度都是一样,但是100ms,1000ms,就明显不一样
//打游戏打得多,我怀疑是刷新频率最快60帧,约16.67ms≈17ms,果然如此
handler.sendEmptyMessageDelayed(START, speed );
}
break;
case COMPLETE:
invalidate();
break;
}
return false;
}
});
/**
* 进度条开始
*/
public void startProgress()
{
progress_angle = 0f;
//每次都清除
handler.removeMessages(START);
handler.sendEmptyMessageDelayed(START, speed);
}
@Override
protected void onDraw(Canvas canvas)
{
...
if (drawing)
{
canvas.drawArc(rectF, 0f, base_angle, false, paint_base_progress);
canvas.drawArc(rectF, 0f, progress_angle, false, paint_progress);
} else
{
canvas.drawArc(rectF, 0f, base_angle, false, paint_base_progress);
}
}
- 只要调用startProgress 方法,就会开始转啦。停止也简单,只要handler.remove掉就停下来了。
- 个人喜好写一些能用的工具。。。。所以要写成可以用xml配置或者动态代码变化的控件,而不是每次用的时候,改View。
-
res/values/ 目录下新建一个attrs.xml文件。
这里列出几个,format的类型很多种,color,就是可以用colors.xml下的资源,dimension就是长宽,dp
,px之类的参数,所以以此类推,其他类型大家也很好理解的。定义好参数之后,我们就在三个参数的构造方法那里获取就可以了;因为xml不一定有配置一些参数,这边也不会走对应的初始化,所以关键参数最好弄一些默认值。
public CircleProgressView(Context context, AttributeSet attrs, int defStyleAttr)
{
.....
//获取自定义样式的属性
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleProgressView, defStyleAttr, 0);
for (int i = 0; i < typedArray.getIndexCount(); i++)
{
int attr = typedArray.getIndex(i);
switch (attr)
{
case R.styleable.CircleProgressView_base_progress_light_color:
base_progress_light_color = typedArray.getColor(attr, Color.WHITE);
break;
case R.styleable.CircleProgressView_progress_radius:
//转化为px,TypedValue也可以将DIP(DP)转PX
radius = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP
, 0, getResources().getDisplayMetrics()));
break;
case R.styleable.CircleProgressView_progress_clockwise:
progress_clockwise = typedArray.getBoolean(attr, true);
break;
case R.styleable.CircleProgressView_progress_period:
period = typedArray.getInteger(attr, 3000);
break;
......
}
}
}
- 最后,要一些顺时针,逆时针,开始,暂停等,就按照自己的需求,加入逻辑就好了。下载源码可以看到更多具体细节。