Android 自定义圆形进度条

Android简易的圆形进度条

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


Github
项目码云地址


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


    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);
    }
}
画笔半径.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);
        } 
    }
圆.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};
圆.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文件。


    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;
                ......
        }
    }
}
  • 最后,要一些顺时针,逆时针,开始,暂停等,就按照自己的需求,加入逻辑就好了。下载源码可以看到更多具体细节。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容