Android DemoAndroid专题

android酷炫下载进度条

2019-01-20  本文已影响146人  刘孙猫咪
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);
    }
}

源码地址

上一篇下一篇

猜你喜欢

热点阅读