Android 自定义带进度显示的半圆形进度条ArcTextProgressBar

自定义ArcTextProgressBar

这个控件最终需要实现如下效果:

1、进度条采用半圆弧形式呈现;

2、圆弧上有已完成的进度提示;

见下面效果图:

Android 自定义带进度显示的半圆形进度条ArcTextProgressBar_第1张图片


其他一些诸于设置进度条宽度、颜色,进度文字圆大小等属性具体见代码中的定义,好废话不说,上代码:

首先是自定义的属性,这样便于在xml文件中设置属性:


        
        
        
        
        
        
        
        
        
        
        
        

    


然后是ArcTextProgressBar.java文件,具体说明见注释,应该比较清楚:

public class ArcTextProgressBar extends View {
    private int diameter = 600;                                                 // 圆弧直径

    private int mStokeWidth = 40;                                               // 进度条宽度
    private int mTextBgCircleDiameter = 20;                                     // 进度条圆形背景上的致敬
    private String mTextStringPrefix = "已完成";                                 // 进度提示文字前缀
    private int mMinProgress = 0;
    private int mCurrentProgress = 25;                                          // 进度条当前进度
    private int mMaxProgress = 100;
    private String mTextStringSuffix = "%";                                     // 进度提示文字后缀

    private int mTotalArcColor = Color.GRAY;
    private int mCurrentArcColor = Color.CYAN;
    private int mCircleTextBgColor = Color.CYAN;
    private int mCircleTextColor = Color.WHITE;

    private int mCircleTextSize = dipToPx(8);

    private int mStartAngle = 180;                                              // 进度条开始绘制的弧度
    private int mSweepAngle = 90;                                               // 进度条滑过的弧度
    private int mTotalAngle = 180;                                              // 进度条总共的弧度

    private Point mPointTextBgCircleCenter = null;                              // 文字背景圆的圆心坐标
    private RectF mRectBg = null;                                               // 背景矩形
    private Paint mPaintAllArc = null;                                          //
    private Paint mPaintCurrentArc = null;
    private Paint mPaintCircleBg = null;
    private StaticLayout mStaticLayout = null;
    private TextPaint mPaintCircleText = null;

    public ArcTextProgressBar(Context context) {
        super(context, null);
        initView();
    }

    public ArcTextProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
        initView();
        initConfig(context, attrs);
    }

    public ArcTextProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
        initConfig(context, attrs);
    }

    private void initConfig(Context context, AttributeSet attrs) {

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ArcTextProgressBar);
        mStokeWidth = typedArray.getInteger(R.styleable.ArcTextProgressBar_arc_text_progress_stroke_width, 40);
        mTextBgCircleDiameter = typedArray.getInteger(R.styleable.ArcTextProgressBar_arc_text_progress_text_bg_circle_diameter, 20);
        mTextStringPrefix = typedArray.getString(R.styleable.ArcTextProgressBar_arc_text_progress_text_string_prefix);
        mTextStringSuffix = typedArray.getString(R.styleable.ArcTextProgressBar_arc_text_progress_text_string_suffix);
        mMinProgress = typedArray.getInteger(R.styleable.ArcTextProgressBar_arc_text_progress_min_progress, 0);
        mMaxProgress = typedArray.getInteger(R.styleable.ArcTextProgressBar_arc_text_progress_max_progress, 100);
        mCurrentProgress = typedArray.getInteger(R.styleable.ArcTextProgressBar_arc_text_progress_current_progress, 25);
        mTotalArcColor = typedArray.getColor(R.styleable.ArcTextProgressBar_arc_text_progress_total_arc_color, Color.GRAY);
        mCurrentArcColor = typedArray.getColor(R.styleable.ArcTextProgressBar_arc_text_progress_current_arc_color, Color.CYAN);
        mCircleTextBgColor = typedArray.getColor(R.styleable.ArcTextProgressBar_arc_text_progress_circle_text_bg_color, Color.CYAN);
        mCircleTextColor = typedArray.getColor(R.styleable.ArcTextProgressBar_arc_text_progress_circle_text_color, Color.WHITE);
        mCircleTextSize = typedArray.getInteger(R.styleable.ArcTextProgressBar_arc_text_progress_circle_text_size, dipToPx(8));

        // 进行初始化设置
        if (null == mTextStringPrefix) {
            mTextStringPrefix = "已完成";
        }
        if (null == mTextStringSuffix) {
            mTextStringSuffix = "%";
        }
        setStokeWidth(mStokeWidth);
        setTextBgCircleDiameter(mTextBgCircleDiameter);
        setTextStringPrefix(mTextStringPrefix);
        setTextStringSuffix(mTextStringSuffix);
        setMinProgress(mMinProgress);
        setMaxProgress(mMaxProgress);
        setCurrentProgress(mCurrentProgress);
        setTotalArcColor(mTotalArcColor);
        setCurrentArcColor(mCurrentArcColor);
        setCircleTextBgColor(mCircleTextBgColor);
        setCircleTextColor(mCircleTextColor);
        typedArray.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = Math.max(mTextBgCircleDiameter, mStaticLayout.getWidth()) + 10 + diameter;
        int height = Math.max(mTextBgCircleDiameter, mStaticLayout.getWidth()) + 10 + diameter / 2;
        setMeasuredDimension(width, height);
    }


    private void initView() {
        diameter = getScreenWidth() * 4 / 5 - getPaddingLeft() - getPaddingRight();
        // 绘制文字
        mPaintCircleText = new TextPaint();
        mPaintCircleText.setColor(mCircleTextColor);
        mPaintCircleText.setAntiAlias(true);
        mPaintCircleText.setTextSize(mCircleTextSize);
        float startChangeLinePos = mPaintCircleText.measureText(mTextStringPrefix);
        mStaticLayout = new StaticLayout(mTextStringPrefix + mCurrentProgress + mTextStringSuffix,
                mPaintCircleText, (int) startChangeLinePos, Layout.Alignment.ALIGN_CENTER, 1.0f, 1.0f, true);

        int maxStroke = mStokeWidth + mTextBgCircleDiameter + mStaticLayout.getWidth();

        // 计算当前滑过的弧度
        mSweepAngle = (int) (mCurrentProgress * 1.0 / 100 * mTotalAngle);
        mRectBg = new RectF();
        mRectBg.left = Math.max(mTextBgCircleDiameter, mStaticLayout.getWidth()) / 2 + 10;
        mRectBg.top = Math.max(mTextBgCircleDiameter, mStaticLayout.getWidth()) / 2 + 10;
        mRectBg.right = Math.max(mTextBgCircleDiameter, mStaticLayout.getWidth()) / 2 + 10 + diameter;
        mRectBg.bottom = Math.max(mTextBgCircleDiameter, mStaticLayout.getWidth()) / 2 + 10 + diameter;

        // 绘制全部进度圆弧
        mPaintAllArc = new Paint();
        mPaintAllArc.setAntiAlias(true);
        mPaintAllArc.setStyle(Paint.Style.STROKE);
        mPaintAllArc.setStrokeWidth(mStokeWidth);
        mPaintAllArc.setColor(mTotalArcColor);
        mPaintAllArc.setStrokeCap(Paint.Cap.ROUND);

        // 当前进度的弧形
        mPaintCurrentArc = new Paint();
        mPaintCurrentArc.setAntiAlias(true);
        mPaintCurrentArc.setStyle(Paint.Style.STROKE);
        mPaintCurrentArc.setStrokeCap(Paint.Cap.ROUND);
        mPaintCurrentArc.setStrokeWidth(mStokeWidth);
        mPaintCurrentArc.setColor(mCurrentArcColor);

        // 绘制当前文字圆形背景
        mPaintCircleBg = new Paint();
        mPaintCircleBg.setAntiAlias(true);
        mPaintCircleBg.setStyle(Paint.Style.FILL);
        mPaintCircleBg.setColor(mCircleTextBgColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 控件背景
//        canvas.drawRect(mRectBg, mPaintRectBg);
        // 整个弧
        canvas.drawArc(mRectBg, mStartAngle, mTotalAngle, false, mPaintAllArc);
        // 当前进度
        canvas.drawArc(mRectBg, mStartAngle, mSweepAngle, false, mPaintCurrentArc);
        // 得到文字背景圆的圆心
        mPointTextBgCircleCenter = calculateTextBgCircleCenter();

        if (mCurrentProgress == 0 || mCurrentProgress == 100) {
            canvas.drawCircle(mPointTextBgCircleCenter.x, mPointTextBgCircleCenter.y - mStaticLayout.getWidth() / 2,
                    ((Math.max(mStokeWidth, mTextBgCircleDiameter) + mStaticLayout.getWidth()) / 2), mPaintCircleBg);
            //开始绘制文字的位置
            canvas.translate(mPointTextBgCircleCenter.x - mStaticLayout.getWidth() / 2,
                    mPointTextBgCircleCenter.y - mStaticLayout.getHeight());
        } else {
            canvas.drawCircle(mPointTextBgCircleCenter.x, mPointTextBgCircleCenter.y,
                    ((Math.max(mStokeWidth, mTextBgCircleDiameter) + mStaticLayout.getWidth()) / 2), mPaintCircleBg);
            //开始绘制文字的位置
            canvas.translate(mPointTextBgCircleCenter.x - mStaticLayout.getWidth() / 2,
                    mPointTextBgCircleCenter.y - mStaticLayout.getHeight() / 2);
        }

        mStaticLayout.draw(canvas);
    }

    /**
     * 关键点:计算文字背景圆的圆心
     *
     * @return the textBgCircleCenter
     */
    private Point calculateTextBgCircleCenter() {
        // 得到圆环中心圆对应的半径:  外半径 - 内半径 / 2
        float arcRadius = mRectBg.width() / 2;
        //圆心
        Point point = new Point();
        point.x = (int) (mRectBg.width() / 2 + mRectBg.left - arcRadius * Math.cos(mSweepAngle * 3.14 / 180));
        point.y = (int) (mRectBg.height() / 2 + mRectBg.top - arcRadius * Math.sin(mSweepAngle * 3.14 / 180));

        return point;
    }

    /**
     * dip 转换成px
     *
     * @param dip
     * @return
     */
    private int dipToPx(float dip) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));
    }

    /**
     * 得到屏幕宽度
     *
     * @return
     */
    private int getScreenWidth() {
        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }

    /**
     * 设置progressBar宽度
     *
     * @param stokeWidth 宽度
     */
    public void setStokeWidth(int stokeWidth) {
        this.mStokeWidth = stokeWidth;
        mPaintAllArc.setStrokeWidth(mStokeWidth);
        mPaintCurrentArc.setStrokeWidth(mStokeWidth);
        invalidate();
    }

    /**
     * 设置progressBar进度提示圆形直径
     *
     * @param textBgCircleDiameter 直径
     **/
    public void setTextBgCircleDiameter(int textBgCircleDiameter) {
        this.mTextBgCircleDiameter = textBgCircleDiameter;
        // todo 根据直径大小来设置里面文字的大小
        invalidate();
    }

    public void setTextStringPrefix(String textStringPrefix) {
        this.mTextStringPrefix = textStringPrefix;
        invalidate();
    }

    public void setMinProgress(int minProgress) {
        this.mMinProgress = minProgress;
    }

    public void setCurrentProgress(int currentProgress) {
        if (currentProgress < mMinProgress) {
            currentProgress = mCurrentProgress;
        } else if (currentProgress > mMaxProgress) {
            currentProgress = mMaxProgress;
        }
        this.mCurrentProgress = currentProgress;
        mSweepAngle = (int) (mCurrentProgress * 1.0 / 100 * mTotalAngle);
        float startChangeLinePos = mPaintCircleText.measureText(mTextStringPrefix);
        mStaticLayout = new StaticLayout(mTextStringPrefix + mCurrentProgress + mTextStringSuffix,
                mPaintCircleText, (int) startChangeLinePos, Layout.Alignment.ALIGN_CENTER, 1.0f, 1.0f, true);
        // todo 设置动画
        invalidate();
    }

    public void setMaxProgress(int maxProgress) {
        this.mMaxProgress = maxProgress;
    }

    public void setTextStringSuffix(String textStringSuffix) {
        this.mTextStringSuffix = textStringSuffix;
        invalidate();
    }

    public void setTotalArcColor(int mTotalArcColor) {
        this.mTotalArcColor = mTotalArcColor;
        mPaintAllArc.setColor(mTotalArcColor);
        invalidate();
    }

    public void setCurrentArcColor(int currentArcColor) {
        this.mCurrentArcColor = currentArcColor;
        mPaintCurrentArc.setColor(mCurrentArcColor);
        invalidate();
    }

    public void setCircleTextBgColor(int circleTextBgColor) {
        this.mCircleTextBgColor = circleTextBgColor;
        mPaintCircleBg.setColor(mCircleTextBgColor);
        invalidate();
    }

    public void setCircleTextColor(int circleTextColor) {
        this.mCircleTextColor = circleTextColor;
        mPaintCircleText.setColor(mCircleTextColor);
        invalidate();
    }

    public void setCircleTextSize(int circleTextSize) {
        this.mCircleTextSize = dipToPx(circleTextSize);
        mPaintCircleText.setTextSize(mCircleTextSize);
        invalidate();
    }
}

需要重点注意的问题:

1、进度圆圆心的确定;

2、onMeasure整个空间宽、高的计算;


需要完善的地方:

1、进度圆中的文字大小应该能自适应圆本身的大小;

2、添加进度圆动画效果;

将在之后进行更新。


你可能感兴趣的:(Android学习开发)