自定义简单的ProgressBar

自学了Android这么长时间,一直没有怎么去接触自定义View,这段时间在一个简单的项目中需要用到一个的进度条来展示数据,于是想到了使用ProgressBar来实现,但是使用Android自带的ProgressBar样式比较难实现,于是决定自定义一个View来实现,同时也练习下自定义View的使用。写下来记录下,没怎么写过博文,格式比较乱,见谅!

这个ProgressBar是参考网上的一篇博客的代码修改而来的,因为暂时找不到原博文了,所以无法注明出处,如果有所不妥可以联系我进行删除或者注明。

下面进入正文,先展示下效果:

自定义简单的ProgressBar

原博文是在中间添加了进度文字的显示,因为项目需求,我做了如下修改:

  1. 添加进度条圆角的设置
  2. 添加进度条渐变色的设定
  3. 添加温度的设定(其实就是将%改为了℃
  4. 将文字显示改到进度条末尾
  5. 添加是否显示文字

具体用法如下:

android:layout_width="match_parent"
android:layout_height="wrap_content"
app:progress="30"//已加载进度
app:progress_color="#ff3300"//已加载部分颜色(渐变色的开始色)
app:progress_height="12dp"//已加载部分的高度
app:background_color="#bbbbbb"//未加载部分的颜色
app:background_height="2dp"//未加载部分高度
app:showText="true"//是否显示文字
app:progress_radius="true"//是否显示圆角
app:isGradient="true"//是否显示渐变
app:end_color="#ff9900"//渐变的结束色
app:text_size="20sp"//文本大小
app:isTemperature="true"//是否设置为温度
app:maxValue="50"//最大进度

接下来讲讲我做的修改吧,其实整个过程不难,毕竟是在原有基础上进行修改的。修改过程中,主要是对绘制的位置进行了调整。

设置圆角:主要是需要注意r的设置,因为经过测试我发现,在画直线的时候,是没有将圆角的宽度计算在内的,因此要设置一个偏差值,在不设置圆角时为0,设置圆角时我们需要设置为进度条部分高度的二分之一(半圆),否则后续画线的时候为产生误差,出现已加载部分和未加载部分重叠的情况。

//直线为圆角时的偏差值
int r = 0;
if (isRadius) {
  //设置直线末端为圆角
  mPaint.setStrokeCap(Paint.Cap.ROUND);
  //圆角的宽度
  r = mProgressHeight/2;
}

设置渐变:因为之前实在是没有自己尝试自定义view,因此对如何绘制渐变也是花了不少时间去谷歌,,,反省。绘制渐变的过程中主要是要确定渐变的开始位置和结束位置,其实就是已加载部分的开始位置和结束位置(注意:此时是不需要r这个偏差值的,其实很容易理解,我们设置这个偏差值是因为绘制直线没有将圆角计算在内,但是直线长度是不变的,只是直线绘制的开始位置需要加r,结束位置减r)。

//直线是否为渐变色
if (isGradient){
    //设置线性渐变,开始位置和结束位置于进度条相同
    Shader shader = new LinearGradient(getPaddingLeft(), mHeight / 2, currentProgress, mHeight / 2,mProgressColor, endColor, Shader.TileMode.REPEAT);
    mPaint.setShader(shader);
}

文字的位置和显示改变:文字的是否显示和是否为温度,只要是注意修改文字的宽度,在计算绘制直线的位置就好了。

文本设置:

//设置文本格式
String str = mProgress + "%";
//设置文本为温度
if (isTemperature) str = mProgress+"℃";
//获取文本宽度
float textWidth = mPaint.measureText(str);
//不显示文字,文本宽度为0
if (!showText) textWidth = 0;

已加载部分的长度和位置计算:

float width = getMeasuredWidth() - textWidth - getPaddingLeft() - getPaddingRight() - mProgressPadding * 2 ;
//当前进度应该绘制的结束位置
float currentProgress = getPaddingLeft() + width * mProgress / max;

已加载部分的绘制:

//画进度条,当存在圆角时,需要考虑圆角,在画直线的时候,圆角的长度是不考虑进去的,所以在开始坐标要加上r,结束坐标要减去r
canvas.drawLine(getPaddingLeft()+r, mHeight / 2, currentProgress-r, mHeight / 2, mPaint);

未加载部分的绘制:

int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
//设置画未加载部分的颜色和宽度
mPaint.setColor(mBackgroundColor);
mPaint.setStrokeWidth(mBackgroundHeight);
//清除渐变
mPaint.setShader(null);
//画未加载部分
canvas.drawLine(currentProgress , mHeight / 2, currentProgress+width*(max-mProgress)/max, mHeight / 2, mPaint);

文本的绘制:

//是否存在文字部分
if (showText)
canvas.drawText(str, getMeasuredWidth() - getPaddingRight()-textWidth, mHeight / 2 + y, mPaint);

修改部分基本就是这样了,整个过程,严格来说并不难,但是因为自己对自定义View部分内容的不熟悉,花费了不少时间来查资料,再次感到自己能力还是很欠缺,还需要继续学习,以后还是要尽量坚持多总结一下自己的学习吧!(写个博客好麻烦,,,这个能力也需要锻炼下啊,以后看有没有时间得去学习下Markdown)

附上github地址:https://github.com/YangX29/MyProgressBar/tree/master

最后附上完整代码:

public class MyProgress extends View {
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 默认进度值
     */
    private int mProgress = 0;
    /**
     * 最大值
     */
    private int max = 100;
    /**
     * 是否为温度
     */
    private boolean isTemperature = false;
    /**
     * 进度边距
     */
    private int mProgressPadding = dp2px(5);
    /**
     * 控件高度
     */
    private int mHeight = 0;

    /**
     * 字体大小
     */
    float mTextSize = 18f;

    /**
     * 是否显示文字
     */
    boolean showText = true;

    /**
     * 进度条是否为圆角
     */
    boolean isRadius = false;

    /**
     * 左边进度条颜色
     */
    int mProgressColor = Color.RED;
    /**
     * 文字颜色
     */
    int mTextColor = Color.RED;
    /**
     * 右边进度条颜色
     */
    int mBackgroundColor = Color.RED;
    /**
     * 左边进度条高度
     */
    int mProgressHeight = dp2px(10);
    /**
     * 右边进度条高度
     */
    int mBackgroundHeight = dp2px(10);

    /**
     * 是否为渐变
     */
    boolean isGradient  = false;

    /**
     * 渐变色的结束色
     * @param context
     */
    int endColor = Color.YELLOW;

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

    public MyProgress(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    /**
     * 初始化
     */
    private void init(AttributeSet attrs) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setColor(Color.RED);//画笔颜色
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.MyProgress);
        //获取参数
        mProgressColor = ta.getColor(R.styleable.MyProgress_progress_color, mProgressColor);
        mTextColor = ta.getColor(R.styleable.MyProgress_text_color, mTextColor);
        mBackgroundColor = ta.getColor(R.styleable.MyProgress_background_color, mBackgroundColor);
        mProgressHeight = (int) ta.getDimension(R.styleable.MyProgress_progress_height, mProgressHeight);
        mBackgroundHeight = (int) ta.getDimension(R.styleable.MyProgress_background_height, mBackgroundHeight);
        mTextSize = ta.getDimension(R.styleable.MyProgress_text_size, mTextSize);
        mProgress = ta.getInt(R.styleable.MyProgress_progress, mProgress);
        max = ta.getInt(R.styleable.MyProgress_maxValue, max);
        isTemperature = ta.getBoolean(R.styleable.MyProgress_isTemperature, isTemperature);
        showText = ta.getBoolean(R.styleable.MyProgress_showText, showText);
        isRadius = ta.getBoolean(R.styleable.MyProgress_progress_radius, isRadius);
        isGradient = ta.getBoolean(R.styleable.MyProgress_isGradient, isGradient);
        endColor = ta.getColor(R.styleable.MyProgress_end_color, endColor);
        ta.recycle();
        mPaint.setTextSize(mTextSize);//设定文字大小,后续好测量文字高度
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//得到测量模式
        int widthVal = MeasureSpec.getSize(widthMeasureSpec);//默认用户需要给出明确值,所以不判断模式
        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(widthVal, height);//设置了测量值后,可以获取测量值。
        //mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
    }

    /**
     * 测量高度
     *
     * @param heightMeasureSpec
     * @return
     */
    private int measureHeight(int heightMeasureSpec) {
        int result;
        int mode = MeasureSpec.getMode(heightMeasureSpec);//得到测量模式
        int size = MeasureSpec.getSize(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {//用户给了精确值
            result = size;
        } else { //MeasureSpec.UNSPECIFIED  MeasureSpec.AT_MOST 未指定明确参数
            int textHeight = (int) (mPaint.descent() - mPaint.ascent());//得到文字高度
            result = getPaddingTop() + getPaddingBottom() + Math.max(mHeight, textHeight);//高度等于进度条高度和文字高度中最高的为准,并且加上padding值
            if (mode == MeasureSpec.AT_MOST) {//给定了最大值
                result = Math.min(result, size);
            }
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取高度
        mHeight = getMeasuredHeight();
        //设置文本格式
        String str = mProgress + "%";
        //设置文本为温度
        if (isTemperature) str = mProgress+"℃";
        //获取文本宽度
        float textWidth = mPaint.measureText(str);
        //不显示文字,文本宽度为0
        if (!showText) textWidth = 0;

        //控件宽度-文本宽度-padding=进度条总宽度,即已加载部分长度
float width = getMeasuredWidth() - textWidth - getPaddingLeft() - getPaddingRight() - mProgressPadding * 2 ;
//当前进度应该绘制的结束位置
float currentProgress = getPaddingLeft() + width * mProgress / max;
        //设置进度条的颜色和高度
        mPaint.setColor(mProgressColor);
        mPaint.setStrokeWidth(mProgressHeight);

        //直线为圆角时的偏差值
        int r = 0;
        if (isRadius) {
            //设置直线末端为圆角
            mPaint.setStrokeCap(Paint.Cap.ROUND);
            //圆角的宽度
            r = mProgressHeight/2;
        }

        //直线是否为渐变色
        if (isGradient){
            //设置线性渐变,开始位置和结束位置于进度条相同
            Shader shader = new LinearGradient(getPaddingLeft(), mHeight / 2, currentProgress, mHeight / 2,
                    mProgressColor, endColor, Shader.TileMode.REPEAT);
            mPaint.setShader(shader);
        }
        //画进度条,当存在圆角时,需要考虑圆角,在画直线的时候,圆角的长度是不考虑进去的,所以在开始坐标要加上r,结束坐标要减去r
        canvas.drawLine(getPaddingLeft()+r, mHeight / 2, currentProgress-r, mHeight / 2, mPaint);
        int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
//        mPaint.setColor(mTextColor);
//        canvas.drawText(str, currentProgress + mProgressPadding, mHeight / 2 + y, mPaint);
        //设置画未加载部分的颜色和宽度
        mPaint.setColor(mBackgroundColor);
        mPaint.setStrokeWidth(mBackgroundHeight);
        //清除渐变
        mPaint.setShader(null);
//        canvas.drawLine(currentProgress + textWidth + mProgressPadding * 2, mHeight / 2, getMeasuredWidth() - getPaddingRight(), mHeight / 2, mPaint);
        //画未加载部分
        canvas.drawLine(currentProgress , mHeight / 2, currentProgress+width*(max-mProgress)/max, mHeight / 2, mPaint);

        //设置文字颜色
        mPaint.setColor(mTextColor);
        //是否存在文字部分
        if (showText)
        canvas.drawText(str, getMeasuredWidth() - getPaddingRight()-textWidth, mHeight / 2 + y, mPaint);
    }

    public void setProgress(int mProgress){
        this.mProgress = mProgress;
        invalidate();
    }

    public int getProgress(){
        return mProgress;
    }

    private int dp2px(int val) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, val, getResources().getDisplayMetrics());
    }

    private int sp2px(int val) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, val, getResources().getDisplayMetrics());
    }

}

attrs文件如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <declare-styleable name="MyProgress">

        <attr name="progress_color" format="color" />
        <attr name="text_color" format="color" />
        <attr name="background_color" format="color" />
        <attr name="progress_height" format="dimension" />
        <attr name="background_height" format="dimension" />
        <attr name="text_size" format="dimension" />
        <attr name="progress" format="integer" />
        <attr name="maxValue" format="integer"/>
        <attr name="isTemperature" format="boolean"/>
        <attr name="showText" format="boolean"/>
        <attr name="progress_radius" format="boolean"/>
        <attr name="isGradient" format="boolean"/>
        <attr name="end_color" format="color"/>
        
    </declare-styleable>
    
</resources>