效果圖: 自定義CircleProgressView:

public class CircleProgressView extends ProgressBar {

    private int mReachBarSize = DisplayUtil.dip2px(getContext(), 2); // 未完成進度條大小
    private int mNormalBarSize = DisplayUtil.dip2px(getContext(), 2); // 未完成進度條大小
    private int mReachBarColor = Color.parseColor("#108ee9"); // 已完成進度顏色
    private int mNormalBarColor = Color.parseColor("#FFD3D6DA"); // 未完成進度顏色
    private int mTextSize = DisplayUtil.sp2px(getContext(), 14); // 進度值字型大小
    private int mTextColor = Color.parseColor("#108ee9"); // 進度的值字型顏色
    private float mTextSkewX; // 進度值字型傾斜角度
    private String mTextSuffix = "%"; // 進度值字首
    private String mTextPrefix = ""; // 進度值字尾
    private boolean mTextVisible = true; // 是否顯示進度值
    private boolean mReachCapRound; // 畫筆是否使用圓角邊界,normalStyle下生效
    private int mRadius = DisplayUtil.dip2px(getContext(), 20); // 半徑
    private int mStartArc; // 起始角度
    private int mInnerBackgroundColor; // 內部背景填充顏色
    private int mProgressStyle = ProgressStyle.NORMAL; // 進度風格
    private int mInnerPadding = DisplayUtil.dip2px(getContext(), 1); // 內部圓與外部圓間距
    private int mOuterColor; // 外部圓環顏色
    private boolean needDrawInnerBackground; // 是否需要繪製內部背景
    private RectF rectF; // 外部圓環繪製區域
    private RectF rectInner; // 內部圓環繪製區域
    private int mOuterSize = DisplayUtil.dip2px(getContext(), 1); // 外層圓環寬度
    private Paint mTextPaint; // 繪製進度值字型畫筆
    private Paint mNormalPaint; // 繪製未完成進度畫筆
    private Paint mReachPaint; // 繪製已完成進度畫筆
    private Paint mInnerBackgroundPaint; // 內部背景畫筆
    private Paint mOutPaint; // 外部圓環畫筆

    private int mRealWidth;
    private int mRealHeight;

    @IntDef({ProgressStyle.NORMAL, ProgressStyle.FILL_IN, ProgressStyle.FILL_IN_ARC})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ProgressStyle {
        int NORMAL = 0;
        int FILL_IN = 1;
        int FILL_IN_ARC = 2;
    }

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

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

    public CircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        obtainAttributes(attrs);
        initPaint();
    }

    private void initPaint() {
        mTextPaint = new Paint();
        mTextPaint.setColor(mTextColor);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setTextSkewX(mTextSkewX);
        mTextPaint.setAntiAlias(true);

        mNormalPaint = new Paint();
        mNormalPaint.setColor(mNormalBarColor);
        mNormalPaint.setStyle(mProgressStyle == ProgressStyle.FILL_IN_ARC ? Paint.Style.FILL : Paint.Style.STROKE);
        mNormalPaint.setAntiAlias(true);
        mNormalPaint.setStrokeWidth(mNormalBarSize);

        mReachPaint = new Paint();
        mReachPaint.setColor(mReachBarColor);
        mReachPaint.setStyle(mProgressStyle == ProgressStyle.FILL_IN_ARC ? Paint.Style.FILL : Paint.Style.STROKE);
        mReachPaint.setAntiAlias(true);
        mReachPaint.setStrokeCap(mReachCapRound ? Paint.Cap.ROUND : Paint.Cap.BUTT);
        mReachPaint.setStrokeWidth(mReachBarSize);

        if (needDrawInnerBackground) {
            mInnerBackgroundPaint = new Paint();
            mInnerBackgroundPaint.setStyle(Paint.Style.FILL);
            mInnerBackgroundPaint.setAntiAlias(true);
            mInnerBackgroundPaint.setColor(mInnerBackgroundColor);
        }
        if (mProgressStyle == ProgressStyle.FILL_IN_ARC) {
            mOutPaint = new Paint();
            mOutPaint.setStyle(Paint.Style.STROKE);
            mOutPaint.setColor(mOuterColor);
            mOutPaint.setStrokeWidth(mOuterSize);
            mOutPaint.setAntiAlias(true);
        }
    }

    private void obtainAttributes(AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
        mProgressStyle = ta.getInt(R.styleable.CircleProgressView_cpv_progressStyle, ProgressStyle.NORMAL);
        // 獲取三種風格通用的屬性
        mNormalBarSize = (int) ta.getDimension(R.styleable.CircleProgressView_cpv_progressNormalSize, mNormalBarSize);
        mNormalBarColor = ta.getColor(R.styleable.CircleProgressView_cpv_progressNormalColor, mNormalBarColor);

        mReachBarSize = (int) ta.getDimension(R.styleable.CircleProgressView_cpv_progressReachSize, mReachBarSize);
        mReachBarColor = ta.getColor(R.styleable.CircleProgressView_cpv_progressReachColor, mReachBarColor);

        mTextSize = (int) ta.getDimension(R.styleable.CircleProgressView_cpv_progressTextSize, mTextSize);
        mTextColor = ta.getColor(R.styleable.CircleProgressView_cpv_progressTextColor, mTextColor);
        mTextSkewX = ta.getDimension(R.styleable.CircleProgressView_cpv_progressTextSkewX, 0);
        if (ta.hasValue(R.styleable.CircleProgressView_cpv_progressTextSuffix)) {
            mTextSuffix = ta.getString(R.styleable.CircleProgressView_cpv_progressTextSuffix);
        }
        if (ta.hasValue(R.styleable.CircleProgressView_cpv_progressTextPrefix)) {
            mTextPrefix = ta.getString(R.styleable.CircleProgressView_cpv_progressTextPrefix);
        }
        mTextVisible = ta.getBoolean(R.styleable.CircleProgressView_cpv_progressTextVisible, mTextVisible);

        mRadius = (int) ta.getDimension(R.styleable.CircleProgressView_cpv_radius, mRadius);
        rectF = new RectF(-mRadius, -mRadius, mRadius, mRadius);

        switch (mProgressStyle) {
            case ProgressStyle.FILL_IN:
                mReachBarSize = 0;
                mNormalBarSize = 0;
                mOuterSize = 0;
                break;
            case ProgressStyle.FILL_IN_ARC:
                mStartArc = ta.getInt(R.styleable.CircleProgressView_cpv_progressStartArc, 0) + 270;
                mInnerPadding = (int) ta.getDimension(R.styleable.CircleProgressView_cpv_innerPadding, mInnerPadding);
                mOuterColor = ta.getColor(R.styleable.CircleProgressView_cpv_outerColor, mReachBarColor);
                mOuterSize = (int) ta.getDimension(R.styleable.CircleProgressView_cpv_outerSize, mOuterSize);
                mReachBarSize = 0;// 將畫筆大小重置為0
                mNormalBarSize = 0;
                if (!ta.hasValue(R.styleable.CircleProgressView_cpv_progressNormalColor)) {
                    mNormalBarColor = Color.TRANSPARENT;
                }
                int mInnerRadius = mRadius - mOuterSize / 2 - mInnerPadding;
                rectInner = new RectF(-mInnerRadius, -mInnerRadius, mInnerRadius, mInnerRadius);

                break;
            case ProgressStyle.NORMAL:
                mReachCapRound = ta.getBoolean(R.styleable.CircleProgressView_cpv_reachCapRound, true);
                mStartArc = ta.getInt(R.styleable.CircleProgressView_cpv_progressStartArc, 0) + 270;
                if (ta.hasValue(R.styleable.CircleProgressView_cpv_innerBackgroundColor)) {
                    mInnerBackgroundColor = ta.getColor(R.styleable.CircleProgressView_cpv_innerBackgroundColor, Color.argb(0, 0, 0, 0));
                    needDrawInnerBackground = true;
                }
                break;
        }
        ta.recycle();
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int maxBarPaintWidth = Math.max(mReachBarSize, mNormalBarSize);
        int maxPaintWidth = Math.max(maxBarPaintWidth, mOuterSize);
        int height = 0;
        int width = 0;
        switch (mProgressStyle) {
            case ProgressStyle.FILL_IN:
                height = getPaddingTop() + getPaddingBottom()  // 邊距
                        + Math.abs(mRadius * 2);  // 直徑
                width = getPaddingLeft() + getPaddingRight()  // 邊距
                        + Math.abs(mRadius * 2);  // 直徑
                break;
            case ProgressStyle.FILL_IN_ARC:
                height = getPaddingTop() + getPaddingBottom()  // 邊距
                        + Math.abs(mRadius * 2)  // 直徑
                        + maxPaintWidth;// 邊框
                width = getPaddingLeft() + getPaddingRight()  // 邊距
                        + Math.abs(mRadius * 2)  // 直徑
                        + maxPaintWidth;// 邊框
                break;
            case ProgressStyle.NORMAL:
                height = getPaddingTop() + getPaddingBottom()  // 邊距
                        + Math.abs(mRadius * 2)  // 直徑
                        + maxBarPaintWidth;// 邊框
                width = getPaddingLeft() + getPaddingRight()  // 邊距
                        + Math.abs(mRadius * 2)  // 直徑
                        + maxBarPaintWidth;// 邊框
                break;
        }

        mRealWidth = resolveSize(width, widthMeasureSpec);
        mRealHeight = resolveSize(height, heightMeasureSpec);

        setMeasuredDimension(mRealWidth, mRealHeight);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        switch (mProgressStyle) {
            case ProgressStyle.NORMAL:
                drawNormalCircle(canvas);
                break;
            case ProgressStyle.FILL_IN:
                drawFillInCircle(canvas);
                break;
            case ProgressStyle.FILL_IN_ARC:
                drawFillInArcCircle(canvas);
                break;
        }
    }

    /**
     * 繪製PROGRESS_STYLE_FILL_IN_ARC圓形
     */
    private void drawFillInArcCircle(Canvas canvas) {
        canvas.save();
        canvas.translate(mRealWidth / 2, mRealHeight / 2);
        // 繪製外層圓環
        canvas.drawArc(rectF, 0, 360, false, mOutPaint);
        // 繪製內層進度實心圓弧
        // 內層圓弧半徑
        float reachArc = getProgress() * 1.0f / getMax() * 360;
        canvas.drawArc(rectInner, mStartArc, reachArc, true, mReachPaint);

        // 繪製未到達進度
        if (reachArc != 360) {
            canvas.drawArc(rectInner, reachArc + mStartArc, 360 - reachArc, true, mNormalPaint);
        }

        canvas.restore();
    }

    /**
     * 繪製PROGRESS_STYLE_FILL_IN圓形
     */
    private void drawFillInCircle(Canvas canvas) {
        canvas.save();
        canvas.translate(mRealWidth / 2, mRealHeight / 2);
        float progressY = getProgress() * 1.0f / getMax() * (mRadius * 2);
        float angle = (float) (Math.acos((mRadius - progressY) / mRadius) * 180 / Math.PI);
        float startAngle = 90 + angle;
        float sweepAngle = 360 - angle * 2;
        // 繪製未到達區域
        rectF = new RectF(-mRadius, -mRadius, mRadius, mRadius);
        mNormalPaint.setStyle(Paint.Style.FILL);
        canvas.drawArc(rectF, startAngle, sweepAngle, false, mNormalPaint);
        // 翻轉180度繪製已到達區域
        canvas.rotate(180);
        mReachPaint.setStyle(Paint.Style.FILL);
        canvas.drawArc(rectF, 270 - angle, angle * 2, false, mReachPaint);
        // 文字顯示在最上層最後繪製
        canvas.rotate(180);
        // 繪製文字
        if (mTextVisible) {
            String text = mTextPrefix + getProgress() + mTextSuffix;
            float textWidth = mTextPaint.measureText(text);
            float textHeight = (mTextPaint.descent() + mTextPaint.ascent());
            canvas.drawText(text, -textWidth / 2, -textHeight / 2, mTextPaint);
        }
    }

    /**
     * 繪製PROGRESS_STYLE_NORMAL圓形
     */
    private void drawNormalCircle(Canvas canvas) {
        canvas.save();
        canvas.translate(mRealWidth / 2, mRealHeight / 2);
        // 繪製內部圓形背景色
        if (needDrawInnerBackground) {
            canvas.drawCircle(0, 0, mRadius - Math.min(mReachBarSize, mNormalBarSize) / 2,
                    mInnerBackgroundPaint);
        }
        // 繪製文字
        if (mTextVisible) {
            String text = mTextPrefix + getProgress() + mTextSuffix;
            float textWidth = mTextPaint.measureText(text);
            float textHeight = (mTextPaint.descent() + mTextPaint.ascent());
            canvas.drawText(text, -textWidth / 2, -textHeight / 2, mTextPaint);
        }
        // 計算進度值
        float reachArc = getPro