android酷炫下载进度条

GIF.gif

拿到项目需求时,不要立马就去写代码,先想下实现的思路,大致涉及到哪些技术点,哪种实现方式比较好,就拿上面的效果说,系统的ProgressBar肯定实现不了该效果,那就要自定义view,自定义view大致包含以下几步:
1、自定义属性的声明与获取
2、测量onMeasure
3、布局onLayout(ViewGroup布局容器)
4、绘制onDraw
5、onTouchEvent(涉及用户手势交互)
6、onInterceptTouchEvent(处理父view和子view之间的事件拦截)
7、状态的恢复与保存


TIM截图20190120142614.png

对于该效果:
横向进度条包含:左边一个进度、中间一个文字、右边一个进度
圆形进度条:最外层一个圆、加载进度的圆弧、显示进度的中间文字
并没有涉及到view摆放、用户交互行为,大致就只需要第一、第二、第四、第七步;实现的流程大致确定后,考虑是继承自view还是ProgressBar,上面的效果只是对系统ProgressBar的效果进行改动,继承自ProgressBar就可以了,这样可以使用系统ProgressBar的一些属性和方法,通过getProgress()和setProgress()方法就可以获取和设置加载进度,不用像继承自view,还有给view添加属性动画来实现加载进度,考虑清楚了那就开始撸代码吧。

按照流程第一步自定义属性的定义和获取:

<declare-styleable name="ProgressBarView">
        <attr name="progress_unreach_color" format="color"></attr>
        <attr name="progress_height" format="dimension"></attr>
        <attr name="progress_reach_color" format="color"></attr>
        <attr name="progress_text_color" format="color"></attr>
        <attr name="progress_text_size" format="dimension"></attr>
        <attr name="progress_text_offset" format="dimension"></attr>
    </declare-styleable>
    <declare-styleable name="RoundProgressBar">
        <attr name="radius" format="dimension"></attr>
    </declare-styleable>
/**
     * 获取自定义属性
     *
     * @param attrs
     */
    private void obtainStyledAttrs(AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
        mTextSize = ta.getDimensionPixelSize(R.styleable.ProgressBarView_progress_text_size, sp2px(mTextSize));
        mTextColor = ta.getColor(R.styleable.ProgressBarView_progress_text_color, mTextColor);

        mUnReachColor = ta.getColor(R.styleable.ProgressBarView_progress_unreach_color, mUnReachColor);
        mProgressHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_height, dp2px(mProgressHeight));

        mReachColor = ta.getColor(R.styleable.ProgressBarView_progress_reach_color, mReachColor);

        mTextOffset = (int) ta.getDimension(R.styleable.ProgressBarView_progress_text_offset, dp2px(mTextOffset));
        ta.recycle();
    }

第一步弄好了,就是第二步测量,测量的时候需要注意控件的宽高模式,根据设置的宽高模式来测量和指定控件的宽高;

@Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthVal = MeasureSpec.getSize(widthMeasureSpec);

        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(widthVal, height);
        //计算progressbar真正宽度=控件的宽度-paddingleft-paddingright
        mRealWith = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
    }
private int measureHeight(int heightMeasureSpec) {
        int result = 0;
        //获取高度模式
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        //获取宽度模式
        int size = MeasureSpec.getSize(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            //精准模式 用户设置为 比如80dp  match_parent fill_parent
            result = size;
        } else {
            //计算中间文字的高度
            int textHeight = (int) (mPaint.descent() - mPaint.ascent());
            //paddingTop+paddingBottom+ progressbar高度和文字高度的最大值
            result = getPaddingTop() + getPaddingBottom() + Math.max(mProgressHeight, Math.abs(textHeight));
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

测量完成后,就可以进行onDraw绘制了,因为要改变系统ProgressBar效果就不需要super.onDraw(canvas);

@Override
    protected synchronized void onDraw(Canvas canvas) {
        //保存画布
        canvas.save();
        //移动画布
        canvas.translate(getPaddingLeft(), getHeight() / 2);
        //定义变量用来控制是否要绘制右边progressbar  如果宽度不够的时候就不进行绘制
        boolean noNeedUnRech = false;
        //计算左边进度在整个控件宽度的占比
        float radio = getProgress() * 1.0f / getMax();
        //获取左边进度的宽度
        float progressX = radio * mRealWith;
        //中间文字
        String text = getProgress() + "%";
        //获取文字的宽度
        int textWidth = (int) mPaint.measureText(text);
        if (progressX + textWidth > mRealWith) {
            //左边进度+文字的宽度超过progressbar的宽度 重新计算左边进度的宽度 这个时候也就意味着不需要绘制右边进度
            progressX = mRealWith - textWidth;
            noNeedUnRech = true;
        }
        //计算左边进度结束的位置 如果结束的位置小于0就不需要绘制左边的进度
        float endX = progressX - mTextOffset / 2;
        if (endX > 0) {
            //绘制左边进度
            mPaint.setColor(mReachColor);
            mPaint.setStrokeWidth(mProgressHeight);
            canvas.drawLine(0, 0, endX, 0, mPaint);
        }
        mPaint.setColor(mTextColor);
        if (getProgress() != 0) {
            //计算文字基线
            int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
            //绘制文字
            canvas.drawText(text, progressX, y, mPaint);
        }
        if (!noNeedUnRech) {
            //右边进度的开始位置=左边进度+文字间距的一半+文字宽度
            float start;
            if (getProgress() == 0) {
                start = progressX;
            } else {
                start = progressX + mTextOffset / 2 + textWidth;
            }
            mPaint.setColor(mUnReachColor);
            mPaint.setStrokeWidth(mProgressHeight);
            //绘制右边进度
            canvas.drawLine(start, 0, mRealWith, 0, mPaint);
        }
        //重置画布
        canvas.restore();
    }

完整代码:

/**
 * 长方形进度条
 */
public class ProgressBarView extends ProgressBar {
    //字体大小
    protected int mTextSize = 12;
    //字体颜色
    protected int mTextColor = Color.BLACK;
    //没有到达(右边progressbar的颜色)
    protected int mUnReachColor = Color.GREEN;
    //progressbar的高度
    protected int mProgressHeight = 6;
    //progressbar进度的颜色
    protected int mReachColor = mTextColor;
    //字体间距
    protected int mTextOffset = 10;
    protected Paint mPaint;
    //progressbar真正的宽度
    protected int mRealWith;

    public ProgressBarView(Context context) {
        this(context, null);
    }

    public ProgressBarView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ProgressBarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取自定义属性
        obtainStyledAttrs(attrs);

        mPaint = new Paint();
        mPaint.setTextSize(mTextSize);
    }

    /**
     * 获取自定义属性
     *
     * @param attrs
     */
    private void obtainStyledAttrs(AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
        mTextSize = ta.getDimensionPixelSize(R.styleable.ProgressBarView_progress_text_size, sp2px(mTextSize));
        mTextColor = ta.getColor(R.styleable.ProgressBarView_progress_text_color, mTextColor);

        mUnReachColor = ta.getColor(R.styleable.ProgressBarView_progress_unreach_color, mUnReachColor);
        mProgressHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_height, dp2px(mProgressHeight));

        mReachColor = ta.getColor(R.styleable.ProgressBarView_progress_reach_color, mReachColor);

        mTextOffset = (int) ta.getDimension(R.styleable.ProgressBarView_progress_text_offset, dp2px(mTextOffset));
        ta.recycle();
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthVal = MeasureSpec.getSize(widthMeasureSpec);

        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(widthVal, height);
        //计算progressbar真正宽度=控件的宽度-paddingleft-paddingright
        mRealWith = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        //保存画布
        canvas.save();
        //移动画布
        canvas.translate(getPaddingLeft(), getHeight() / 2);
        //定义变量用来控制是否要绘制右边progressbar  如果宽度不够的时候就不进行绘制
        boolean noNeedUnRech = false;
        //计算左边进度在整个控件宽度的占比
        float radio = getProgress() * 1.0f / getMax();
        //获取左边进度的宽度
        float progressX = radio * mRealWith;
        //中间文字
        String text = getProgress() + "%";
        //获取文字的宽度
        int textWidth = (int) mPaint.measureText(text);
        if (progressX + textWidth > mRealWith) {
            //左边进度+文字的宽度超过progressbar的宽度 重新计算左边进度的宽度 这个时候也就意味着不需要绘制右边进度
            progressX = mRealWith - textWidth;
            noNeedUnRech = true;
        }
        //计算左边进度结束的位置 如果结束的位置小于0就不需要绘制左边的进度
        float endX = progressX - mTextOffset / 2;
        if (endX > 0) {
            //绘制左边进度
            mPaint.setColor(mReachColor);
            mPaint.setStrokeWidth(mProgressHeight);
            canvas.drawLine(0, 0, endX, 0, mPaint);
        }
        mPaint.setColor(mTextColor);
        if (getProgress() != 0) {
            //计算文字基线
            int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
            //绘制文字
            canvas.drawText(text, progressX, y, mPaint);
        }
        if (!noNeedUnRech) {
            //右边进度的开始位置=左边进度+文字间距的一半+文字宽度
            float start;
            if (getProgress() == 0) {
                start = progressX;
            } else {
                start = progressX + mTextOffset / 2 + textWidth;
            }
            mPaint.setColor(mUnReachColor);
            mPaint.setStrokeWidth(mProgressHeight);
            //绘制右边进度
            canvas.drawLine(start, 0, mRealWith, 0, mPaint);
        }
        //重置画布
        canvas.restore();

    }

    private int measureHeight(int heightMeasureSpec) {
        int result = 0;
        //获取高度模式
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        //获取宽度模式
        int size = MeasureSpec.getSize(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            //精准模式 用户设置为 比如80dp  match_parent fill_parent
            result = size;
        } else {
            //计算中间文字的高度
            int textHeight = (int) (mPaint.descent() - mPaint.ascent());
            //paddingTop+paddingBottom+ progressbar高度和文字高度的最大值
            result = getPaddingTop() + getPaddingBottom() + Math.max(mProgressHeight, Math.abs(textHeight));
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    protected int dp2px(int dip) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
    }

    protected int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }
}

圆形加载进度条效果的话,文字和颜色可以共用横向进度条的,所有就让圆形进度条直接继承自横向进度条;

/**
 * 原型进度条
 */
public class RoundProgressBar extends ProgressBarView {
    //半径
    private int mRadius = 30;
    private int mMaxPaintWidth;

    public RoundProgressBar(Context context) {
        this(context, null);
    }

    public RoundProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RoundProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //绘制圆形进度条的宽度   这里设置为长方形进度条高度的1.5倍
        mRealWith = (int) (mProgressHeight * 1.5f);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
        mRadius = (int) typedArray.getDimension(R.styleable.RoundProgressBar_radius, dp2px(mRadius));
        typedArray.recycle();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mMaxPaintWidth = mProgressHeight;
        //计算控件的精准值
        int expect = mRadius * 2 + mMaxPaintWidth + getPaddingLeft() + getPaddingRight();
        int width = resolveSize(expect, widthMeasureSpec);
        int height = resolveSize(expect, heightMeasureSpec);
        int readWidth = Math.min(width, height);
        mRadius = (readWidth - getPaddingRight() - getPaddingLeft() - mMaxPaintWidth) / 2;
        setMeasuredDimension(readWidth, readWidth);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        //获取当前进度
        String text = getProgress() + "%";
        //测量文字的宽度
        float textWidth = mPaint.measureText(text);
        //文字的高度
        float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;
        //保存画布
        canvas.save();
        //平移画布位置
        canvas.translate(getPaddingLeft() + mMaxPaintWidth / 2, getPaddingTop() + mMaxPaintWidth / 2);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mUnReachColor);
        mPaint.setStrokeWidth(mRealWith);
        //绘制圆 要注意绘制圆的x y 因为上面对画布进行了平移所以这里就不需要计算了,如果画布没有进行平移需要计算 x y
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
        //绘制圆弧
        mPaint.setColor(mReachColor);
        mPaint.setStrokeWidth(mReachColor);
        //计算圆弧的扫过的幅度
        float sweepAngle = getProgress() * 1.0f / getMax() * 360;
        RectF rectF = new RectF(0, 0, mRadius * 2, mRadius * 2);
        canvas.drawArc(rectF, 0, sweepAngle, false, mPaint);

        //绘制中间文字
        mPaint.setColor(mTextColor);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight, mPaint);
        canvas.restore();
    }
}

自定义的代码就基本完成了,在调用的时候也比较简单,不需要像继承自View那样给View设置属性动画又调用invalidate进行重绘,直接调用setProgress方法设置当前的进度就可以了;

public class MainActivity extends AppCompatActivity {
    private ProgressBarView progressBarView,progressBarView1;
    private RoundProgressBar roundProgressBar,roundProgressBar1;
    private static final int MSG_UPDATA = 0X110;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int progress = progressBarView.getProgress();
            progressBarView.setProgress(++progress);
            progressBarView1.setProgress(++progress);
            roundProgressBar.setProgress(++progress);
            roundProgressBar1.setProgress(++progress);
            if (progress >= 100) {
                handler.removeMessages(MSG_UPDATA);
            }
            handler.sendEmptyMessageDelayed(MSG_UPDATA, 100);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        progressBarView = findViewById(R.id.progress_bar_view);
        roundProgressBar = findViewById(R.id.round_progressbar);
        progressBarView1=findViewById(R.id.progress_bar_view1);
        roundProgressBar1=findViewById(R.id.round_progressbar1);

        handler.sendEmptyMessageDelayed(MSG_UPDATA, 1000);
    }
}

源码地址

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

推荐阅读更多精彩内容

  • 前言 最近做项目碰到一个这样的一个需求:需要一个环形的进度条表示一个下载请求的进度加载。同时要以各种不同的图标展现...
    chengww阅读 3,259评论 0 16
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,982评论 4 60
  • 一只小蚂蚁 划着小船儿 他要去远方 找她媳妇儿
    七根阅读 245评论 0 3
  • 老王,带儿子去买菜呢。 啊。是呀,这不是周末嘛,买点肉啥的给咱小乐乐补补。 奥,那挺好的,我先回去了奥!你们慢点走...
    第九夫人阅读 545评论 1 4
  • 《中餐厅2》第十期播出王俊凯特色麻辣烫获赞! 湖南卫视青春合伙人经营体验节目《中餐厅2》第二季第十期即将播出上线。...
    37餐饮阅读 178评论 0 0