Android自定义圆形进度条

4,543 阅读3分钟

Android自定义圆形进度条

github地址:https://github.com/opq1289/CircleProgressView

效果图:
无动画:

在这里插入图片描述

有动画:

整圆:

在这里插入图片描述
切割圆:
在这里插入图片描述

具体步骤:

1. 绘制最基础的两个圆

定义两个画笔:

//进度条画笔
mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeWidth(30);
mProgressPaint.setColor(Color.parseColor("#d81b60"));
//背景圆画笔
mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackgroundPaint.setStyle(Paint.Style.STROKE);
mBackgroundPaint.setStrokeWidth(30);
mBackgroundPaint.setColor(Color.parseColor("#f1f1f1"));

画圆:

mRectf = new RectF(0, 0, 200, 200);
canvas.drawCircle(100, 100, 100, mBackgroundPaint);
canvas.drawArc(mRectf, 0, 120, false, mProgressPaint);

在这里插入图片描述

边缘被切割,增加参数进度条宽度,优化边缘:

private void initPaint() {
    //进度条画笔
    mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mProgressPaint.setStyle(Paint.Style.STROKE);
    mProgressPaint.setStrokeWidth(mProgressWidth);
    mProgressPaint.setColor(Color.parseColor("#d81b60"));
    //背景圆画笔
    mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mBackgroundPaint.setStyle(Paint.Style.STROKE);
    mBackgroundPaint.setStrokeWidth(mProgressWidth);
    mBackgroundPaint.setColor(Color.parseColor("#f1f1f1"));
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mRectf = new RectF(mProgressWidth / 2, mProgressWidth / 2, 200 - mProgressWidth / 2, 200 - mProgressWidth / 2);
    canvas.drawCircle(100, 100, 100 - mProgressWidth / 2, mBackgroundPaint);
    canvas.drawArc(mRectf, 0, 120, false, mProgressPaint);
}

在这里插入图片描述

2. 计算尺寸

main_activity.xml中的布局是:

android:layout_width="100dp"
android:layout_height="100dp"

显示布局边界后发现view的宽高是屏幕宽高:

在这里插入图片描述

重新设置view的尺寸:

//设置默认最小尺寸
private int mDefaultWidth = CommonUtil.dp2px(getContext(), 10);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = measureWidth(widthMeasureSpec);
    int height = measureHeight(heightMeasureSpec);
    mViewWidth = Math.min(width, height);
    setMeasuredDimension(mViewWidth, mViewWidth);
}
private int measureWidth(int widthMeasureSpec) {
    int width;
    int size = MeasureSpec.getSize(widthMeasureSpec);
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    switch (mode) {
        case MeasureSpec.EXACTLY:
            width = size < mProgressWidth ? mProgressWidth : size;
            break;
        case MeasureSpec.AT_MOST:
            width = mDefaultWidth * 2;
            break;
        default:
            width = CommonUtil.getScreenWidthInPx(getContext());
            break;
    }
    return width;
}

private int measureHeight(int heightMeasureSpec) {
    int height;
    int size = MeasureSpec.getSize(heightMeasureSpec);
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    switch (mode) {
        case MeasureSpec.EXACTLY:
            height = size < mProgressWidth ? mProgressWidth : size;
            break;
        case MeasureSpec.AT_MOST:
            height = mDefaultWidth * 2;
            break;
        default:
            height = CommonUtil.getScreenHeightInPx(getContext());
            break;
    }
    return height;
}

然后修改绘制:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mRectf = new RectF(mProgressWidth / 2, mProgressWidth / 2, mViewWidth - mProgressWidth / 2, mViewWidth - mProgressWidth / 2);
    canvas.drawCircle(mViewWidth / 2, mViewWidth / 2, mViewWidth / 2 - mProgressWidth / 2, mBackgroundPaint);
    canvas.drawArc(mRectf, 0, 120, false, mProgressPaint);
}

在这里插入图片描述

至此,静态进度条就画好了。

将参数设置为动态,通过方法和属性设置。

<declare-styleable name="CircleProgressView">
    <attr name="progressWidth" format="dimension"/>
    <attr name="progressColor" format="reference"/>
    <attr name="backgroundColor" format="reference"/>
    <attr name="startAngle" format="integer"/>
</declare-styleable>

public CircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initPaint();
    TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleProgressView, defStyleAttr, 0);
    mProgressWidth = (int) typedArray.getDimension(R.styleable.CircleProgressView_progressWidth, mDefaultWidth);
    mProgressColor = (int) typedArray.getDimension(R.styleable.CircleProgressView_progressColor, ContextCompat.getColor(getContext(), R.color.colorAccent));
    mStartAngle = typedArray.getInt(R.styleable.CircleProgressView_startAngle, 0);
    mEndAngle = typedArray.getInt(R.styleable.CircleProgressView_startAngle, 360);
    mBackgroundColor = (int) typedArray.getDimension(R.styleable.CircleProgressView_backgroundColor, ContextCompat.getColor(getContext(), R.color.grey_f1));
    typedArray.recycle();

    mProgressPaint.setStrokeWidth(mProgressWidth);
    mProgressPaint.setColor(mProgressColor);

    mBackgroundPaint.setStrokeWidth(mProgressWidth);
    mBackgroundPaint.setColor(mBackgroundColor);
}

3. 添加动画

public void setProgress(float progress) {
    mValueAnimator = ValueAnimator.ofFloat(progress);
    mValueAnimator.setDuration(1000);
    mValueAnimator.setInterpolator(new LinearInterpolator());
    mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mProgress = (float) animation.getAnimatedValue();
            invalidate();
        }
    });
    mValueAnimator.start();
}

增加进度监听:

public interface OnProgressChangedListener {
    void onProgressChanged(float currentProgress);
}

public void setOnProgressChangedListener(OnProgressChangedListener listener) {
    mListener = listener;
}

在布局中增加文字,设置居中:

在这里插入图片描述

4. 优化代码

增加静态和动画的区分:

/**
* @param progress      进度
* @param showAnimation 是否展示动画
*/
public void setProgress(float progress, boolean showAnimation) {
    mShowAnimation = showAnimation;
    if (mValueAnimator != null && mValueAnimator.isRunning()) {
        mValueAnimator.cancel();
    }
    if (mShowAnimation) {
        mValueAnimator = ValueAnimator.ofFloat(progress);
        mValueAnimator.setDuration(mDuration);
        mValueAnimator.setInterpolator(new LinearInterpolator());
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mProgress = (float) animation.getAnimatedValue();
                if (mListener != null) {
                    mListener.onProgressChanged(mProgress);
                }
                invalidate();
            }
        });
        mValueAnimator.start();
    } else {
        mProgress = progress;
        invalidate();
    }
}

设置画笔类型,增加圆头画笔:

public void setCap(Paint.Cap cap) {
    mProgressPaint.setStrokeCap(cap);
    mBackgroundPaint.setStrokeCap(cap);
}

在这里插入图片描述

5. 增加切割圆类型

增加进度条类型mProgressType:

/**
 * 整圆进度条
 */
public static final int TYPE_CIRCLE = 0;
/**
 * 切割圆进度条
 */
public static final int TYPE_CLIP = 1;

切割圆从开始角度到结束角度之间,总进度为100。所以这种情况下:总进度=终止角度 - 起始角度

设置进度的方法修改:

public void setProgress(float progress, boolean showAnimation) {
    mShowAnimation = showAnimation;
    if (mProgressType == TYPE_CLIP) {
        progress = (int) ((mEndAngle - mStartAngle) * 100 / 360.0f);
        mTotalProgress = progress;
    } else {
        mTotalProgress = 100;
    }
    if (mValueAnimator != null && mValueAnimator.isRunning()) {
        mValueAnimator.cancel();
    }
    if (mShowAnimation) {
        mValueAnimator = ValueAnimator.ofFloat(progress);
        mValueAnimator.setDuration(mDuration);
        mValueAnimator.setInterpolator(new LinearInterpolator());
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mProgress = (float) animation.getAnimatedValue();
                if (mListener != null) {
                    mListener.onProgressChanged(mProgress * 100 / mTotalProgress);
                }
                invalidate();
            }
        });
        mValueAnimator.start();
    } else {
        mProgress = progress;
        invalidate();
    }
}

OnDraw修改,区分切割圆和整圆:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mRectf = new RectF(mProgressWidth / 2, mProgressWidth / 2, mViewWidth - mProgressWidth / 2, mViewWidth - mProgressWidth / 2);
    if (mProgressType == TYPE_CIRCLE) {
        canvas.drawCircle(mViewWidth / 2, mViewWidth / 2, mViewWidth / 2 - mProgressWidth / 2, mBackgroundPaint);
        canvas.drawArc(mRectf, mStartAngle, mProgress * 360 / 100, false, mProgressPaint);
    } else if (mProgressType == TYPE_CLIP) {
        canvas.drawArc(mRectf, mStartAngle, mEndAngle - mStartAngle, false, mBackgroundPaint);
        canvas.drawArc(mRectf, mStartAngle, mProgress * 360 / 100, false, mProgressPaint);
    }
}

完成。

github地址:https://github.com/opq1289/CircleProgressView