Android 自定义圆形进度条

Android简易的圆形进度条

自定义View基础入门看2个系列文章,非常优秀的文章。
安卓自定义View教程目录
HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解

Github
项目码云地址

  • 最近因为项目需要,点击按钮,实现转一圈的进度条,感觉需求比较简单,就自己做了一个自定义View,很简易,分享给大家。

    《Android 自定义圆形进度条》 Android技术棒

  • 功力有限,只能一步步完成功能,一步步改代码,最后完成。

  • 先看看效果,图片有压缩,丢帧,不是很清晰

    《Android 自定义圆形进度条》 效果.GIF

  • 先初始化画笔,画个圆圈
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);
    }
}

《Android 自定义圆形进度条》 画笔半径.jpg

  • 这里蓝色就是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);
        } 
    }

《Android 自定义圆形进度条》 圆.jpg

 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};

《Android 自定义圆形进度条》 圆.jpg

  • 可以看出参数变化之后,颜色配色比例不同的效果,为了过渡的自然,所以我一般喜欢最后的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文件。

    《Android 自定义圆形进度条》 attrs.jpg

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 底层进度条浅颜色 -->
    <attr name="base_progress_light_color" format="color" />
    <!-- 进度条宽度 -->
    <attr name="progress_width" format="dimension" />
    <!-- 顺时针转 -->
    <attr name="progress_clockwise" format="boolean" />
    <!-- 周期 -->
    <attr name="progress_period" format="integer" />
    <!-- 循环播放 -->
    <attr name="progress_recycler" format="boolean"/>

    <declare-styleable name="CircleProgressView">
        <!-- 底层进度条浅颜色 -->
        <attr name="base_progress_light_color" />
        <!-- 进度条宽度 -->
        <attr name="progress_width" />
        <!-- 顺时针转 -->
        <attr name="progress_clockwise" />
        <!-- 周期 -->
        <attr name="progress_period" />
        <!-- 循环播放 -->
        <attr name="progress_recycler" />

    </declare-styleable>
</resources>
  • 这里列出几个,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;
                ......
        }
    }
}
  • 最后,要一些顺时针,逆时针,开始,暂停等,就按照自己的需求,加入逻辑就好了。下载源码可以看到更多具体细节。
    原文作者:Good包籽
    原文地址: https://www.jianshu.com/p/5810070f0099
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞