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);
}
}
複製程式碼
完成。