自定义progressbar

progressbar 自定义progressbar 进度条 圆形progressbar


世界因为有了互相帮助,变得美好!
程序的世界因为有了分享,一切都变得更容易!
一直以来,各种开源项目给了我们工作极大的便利,作为一个懂得感恩的人,我也想分享一些东西,可能网上类似的分享已经有很多了,但还是准备写一些东西,希望做些微贡献。
本人独自负责上线应用赛百姿App近一年,其中也积累了一些个人觉得值得分享的东西,准备按计划写个系列。有需要的可以下载app查看,可针对各位想要了解的地方作针对性讲解分享。

详细自定义View流程,其他自定义view以此类推,后面还会讲一个炫酷的自定义view


GitHub传送门
AppSimple

先看效果以及怎
6456519-fa88908d416361f0.gif
==xml配置==
    <com.example.myapplication.View.SProgress
                android:id="@+id/sprogress"
                android:layout_width="match_parent"
                android:layout_height="12dp"
                android:layout_centerVertical="true"
                android:layout_toLeftOf="@+id/frentallystart"
                android:padding="15dp"
                app:backColor="#ffff8787"
                app:minProgress="0"
                app:progressColor="#ffff3030"
                app:progressendcolor="#ffff6969"
                app:startprogress="20"
                app:textColor="#ffffffff"
                app:textsize="9sp" />
                
                
==代码配置==      
    
                new SProgress(MainActivity.this)
                        .setProgress(0)
                        .setColor(colors[a][b], colors[b][a], 0xffff0000)
                        .setTextSize(12)
                        .animatorToProgress(99);

第一步:确定所需属性,在attrs.xml中定义相应属性。

  • 根据需求需要以下属性
<declare-styleable name="SProgress">
    //进度开始颜色
     <attr name="progressColor" format="color"/>
    //进度结束颜色
    <attr name="progressendcolor" format="color"/>
    //背景色
    <attr name="backColor" format="color"/>
    //文字颜色
    <attr name="textColor" format="color"/>
    //文字大小
    <attr name="textsize" format="dimension"/>
    //最小进度
    <attr name="minProgress" format="float"/>
    //起始进度
     <attr name="startprogress" format="float"/>
</declare-styleable>

第二步:获取属性 初始化相关参数

  • 获取属性
   private void obtainData(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SProgress);
        progressColor = a.getColor(R.styleable.SProgress_progressColor, progressColor);
        progressendcolor = a.getColor(R.styleable.SProgress_progressendcolor, progressendcolor);
        backColor = a.getColor(R.styleable.SProgress_backColor, backColor);
        textColor = a.getColor(R.styleable.SProgress_textColor, textColor);
        textsize = a.getDimension(R.styleable.SProgress_textsize, textsize);
        minProgress = a.getFloat(R.styleable.SProgress_minProgress, 0);
        progress = a.getFloat(R.styleable.SProgress_startprogress, 0);
        a.recycle();
   
    }
  • 初始化、
    private void init() {
    //背景画笔
        bgpaint = new Paint();
        bgpaint.setColor(backColor);
        bgpaint.setAntiAlias(true);

    //进度画笔
        proPaint = new Paint();
        proPaint.setColor(progressColor);
        //设置为渐变进度效果
        LinearGradient linearGradient = new LinearGradient(0, 0, width, height, progressColor, progressendcolor, Shader.TileMode.CLAMP);
        proPaint.setShader(linearGradient);
        proPaint.setAntiAlias(true);

    //文字画笔
        textPaint = new Paint();
        textPaint.setColor(textColor);
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(textsize);
        //其他一些初始化
        textbaseline = getFontstart(textPaint);
        rectbg =new RectF();
        rectprogress =new RectF();
    }

第三步:View的测量

  • EXACTLY模式下设置测量的值
  • 其他情况给定预设最小值
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //有准确值就设置,没有给定最小值
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = Math.min(width, widthSize);
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = Math.min(heightSize, height);

        }
        //记录下padding值,以支持内padding
        padding.set(getPaddingLeft(),getPaddingTop(),getPaddingRight(),getPaddingBottom());
        setMeasuredDimension(width, height);

    }

第四部:draw

  • 先画背景,计算背景矩形的位置drawRoundRect
//除去上下左右的内padding中间为显示进度区域
  rectbg.set(padding.left, padding.top, (float) width - padding.right, (float) height - padding.bottom);
//为了让进度条为椭圆型,设置roundrect 边弧度为高度的一半

        canvas.drawRoundRect(rectbg, (height - padding.top-padding.bottom) / 2, (height - padding.top-padding.bottom) / 2, bgpaint);
  • 画上层进度 由动画控制变量progress,调用invalidate()重绘以达到动画效果 一下两种不同方式供参考
    --由于进度很小时画的圆角矩形会超过背景圆角矩形的范围,需要用手段让进度只显示在背景内,有如下两种实现方式
  • clipPath实现
  if(path==null) {
            path = new Path();
            path.addRoundRect(rectbg,(height - padding.top-padding.bottom) / 2, (height - padding.top-padding.bottom) / 2,Path.Direction.CW);
        }
        int save = canvas.save();
        canvas.clipPath(path);
        rectprogress.set(padding.left, padding.top, padding.left + ((width - padding.left-padding.right) * (progress < minProgress ? minProgress : progress) / 100), height - padding.bottom);
        canvas.drawRoundRect(rectprogress, (float) ((height - padding.top-padding.bottom) / 2), (float) ((height - padding.top-padding.bottom) / 2), proPaint);
        canvas.restoreToCount(save);
  • setXfermode实现
  • 进度要相交于背景矩形内setXfermode 中的PorterDuff.Mode.SRC_ATOP 是我们想要的相交效果
  • image
    image
Canvas c=newCanvas(BmpDST);
//清空bitmap
c.drawColor(Color.BLACK,PorterDuff.Mode.CLEAR);
//画上矩形
c.drawRoundRect(rectprogress,(float) ((height - padding.top-padding.bottom) / 2), (float) ((height - padding.top-padding.bottom) / 2),proPaint);
//模式合成
int layerId=canvas.saveLayer(0,0nwidthheightnnull,Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,0,0,proaint);
       proPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        rectprogress.set(padding.left, padding.top, padding.left + ((width - padding.left-padding.right) * (progress < minProgress ? minProgress : progress) / 100), height - padding.bottom);

        canvas.drawRoundRect(rectprogress, (float) ((height - padding.top-padding.bottom) / 2), (float) ((height - padding.top-padding.bottom) / 2), proPaint);
        proPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
  • 画文字
  • 我们的文字要的效果是"当前进度12.5%",在strings.xml中定义字符串样式
<string name="sprogress_string">当前进度%.1f%%</string>
  • 根据当前进度画文本

   float length = textPaint.measureText(getText(progress));

   canvas.drawText(getText(progress), width / 2 - length / 2, height / 2 - textbaseline, textPaint);
   
   //获取格式化文本方法
    private String getText(float progress) {
        String progresstext = String.format(getResources().getString(R.string.sprogress_string), progress);
        return progresstext;
    }
    //由于文字的的y是指baseline
 //文字y为控件height/2+文字中间到baseline的距离
      public float getFontstart(Paint paint) {
        Paint.FontMetrics fm = paint.getFontMetrics();
        return (fm.descent + fm.ascent) / 2 - fm.leading;
    }
   
  • 1.基准点是baseline
    2.ascent:是baseline之上至字符最高处的距离
    3.descent:是baseline之下至字符最低处的距离
    4.leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
    5.top:是指的是最高字符到baseline的值,即ascent的最大值
    6.bottom:是指最低字符到baseline的值,即descent的最大值


    image

第五步:动画动起来

  • 用valueanimator来执行动画
  • 通过改变变量progress、的值重绘以达到动画效果目的
  • 要求最后结果带小数,的所以中间的数字转换为int,最后执行完显示float
    private void startAnimator(float pors) {
        ValueAnimator animator = ValueAnimator.ofFloat((progress < minProgress ? minProgress : progress), pors < minProgress ? minProgress : pors);
        int i = (int) (pors - progress) * maxDuring / 100;
        animator.setDuration(i < minDuring ? minDuring : i);
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                //结束时显示原值带小数,中间变换显示不带小数

                if (animation.getAnimatedFraction() == 1)
                    progress = (float) animation.getAnimatedValue();
                else
                    progress = (int) ((float)animation.getAnimatedValue());
                invalidate();
            }
        });
        animator.start();
    }

第六步:适配代码中也能使用

  • 我喜欢各种属性都以这种格式设置,以方便链式调用
public SProgress setTextSize(float size) {
        textsize = dp2px(size);
        return this;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容