Android自定义圆形进度条

前言:

看到别人写的那些个酷炫的动画,心里痒痒的,于是,自己就开始了自定义View的探索之路。如果对自定义View还不是很熟,在看我这篇文章之前,我觉得你最好先看这篇文章:
自定义View,有这一篇就够了

正好公司有这样一个需求:
数据和圆的进度同时在动
与普通进度条区别是,它没有浮标。从图中我们可以知道,重要的有3部分:
1.画进度圆圈。
2.画数字。
3.画汉字。
画出这3部分。按照自定义View的步骤一步一步来吧。
一、创建类OvalProgressView.java继承View类,定义、初始化属性

根据上图可以在res/values/attrs.xml文件中定义以下属性方便用户扩展:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="OvalProgressView">
        <attr name="ovalColor" format="color" />
        <attr name="fixTextColor" format="color" />
        <attr name="progressextColor" format="color" />
        <attr name="ovalWidth" format="dimension" />
        <attr name="progressText" format="string" />
        <attr name="progressTextSize" format="dimension" />
        <attr name="fixText" format="string" />
        <attr name="fixTextSize" format="dimension" />
    </declare-styleable>

</resources>
圆圈的颜色,宽度,最终进度,进度描述内容以及他们的字体大小,颜色。当然在.java文件中也要定义相同的属性了。
private static final int DEFAULT_RADIUS = 150;
    /**
     * 圆圈颜色
     *
     * @param context
     */
    private int mOvalColor;
    /**
     * 进度颜色
     *
     * @param context
     */
    private int mProgressColor;
    /**
     * 进度描述内容颜色
     *
     * @param context
     */
    private int mfixTextColor;

    /**
     * 圆圈宽度
     *
     * @param context
     */
    private int mOvalWidth;


    /**
     * 圆圈最终进度
     *
     * @param context
     */
    private float mOvalProgress;
    /**
     * 圆圈中间变动进度文字大小
     *
     * @param context
     */
    private int mProgressTextSize = sp2px(30);
    /**
     * 圆圈中间固定文字尺寸大小
     *
     * @param context
     */
    private int mFixTextSize = sp2px(20);
    /**
     * 圆圈中间变动进度
     *
     * @param context
     */
    private String mProgressText = "0.0";
    /**
     * 圆圈中间固定文字
     *
     * @param context
     */
    private String mFixText = "盈利指数";
    /**
     * 圆圈当前进度
     *
     * @param context
     */
    private float mProgress;

    /**
     * 画笔
     */
    private Paint mPaint;

    /**
     * 是否已到达最终进度
     *
     * @param context
     */
    private boolean isStop = false;
    /**
     * 格式化小数点
     */
    private DecimalFormat dff;
    /**
     * 圆心
     */
    private int centreX,centreY;
    /**
     半径
     *
     */
    private int radius = DEFAULT_RADIUS;

在布局文件中将属性值设置好,接下来就要在构造方法中获取属性了:

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

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

    public OvalProgrossView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.OvalProgressView, defStyleAttr, 0);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.OvalProgressView_ovalColor:
                    mOvalColor = a.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.OvalProgressView_fixTextColor:
                    mfixTextColor = a.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.OvalProgressView_progressTextColor:
                    mProgressColor = a.getColor(attr, Color.RED);
                    break;
                case R.styleable.OvalProgressView_progressTextSize:
                    mProgressTextSize = a.getDimensionPixelSize(attr, sp2px(30));
                    break;
                case R.styleable.OvalProgressView_fixTextSize:
                    mFixTextSize = a.getDimensionPixelSize(attr, sp2px(20));
                    break;
                case R.styleable.OvalProgressView_ovalWidth:
                    mOvalWidth = a.getDimensionPixelSize(attr, sp2px(5));
                    break;
                case R.styleable.OvalProgressView_progressText:
                    mProgressText = a.getString(attr);
                    mOvalProgress = Float.valueOf(mProgressText);
                    break;
                case R.styleable.OvalProgressView_fixText:
                    mFixText = a.getString(attr);
                    break;
            }
        }
        a.recycle();
        mPaint = new Paint();
        dff = new DecimalFormat("0.0");

    }

在这个时候呢,可以做一些初始化操作。画笔,格式化小数点。千万不要忘了,还有属性值的回收(a.recycle())。
二、重写onMeasure()方法;

    @Override
    protected  void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wideSize = MeasureSpec.getSize(widthMeasureSpec);
        int wideMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int width, height;
        if (wideMode == MeasureSpec.EXACTLY) { //精确值 或matchParent
            width = wideSize;
        } else {
            width = radius * 2 + getPaddingLeft() + getPaddingRight();
            if (wideMode == MeasureSpec.AT_MOST) {
                width = Math.min(width, wideSize);
            }
        }

        if (heightMode == MeasureSpec.EXACTLY) { //精确值 或matchParent
            height = heightSize;
        } else {
            height = radius * 2 + getPaddingLeft() + getPaddingRight();
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(height, heightSize);
            }
        }
        setMeasuredDimension(width, height);
        centreX = width / 2; // 获取圆心的x座标
        centreY = height / 2; // 获取圆心的y座标
        radius = (int) (Math.min(width - getPaddingLeft() - getPaddingRight(),
                height - getPaddingTop() - getPaddingBottom()) * 1.0f / 2) - mOvalWidth;
    }
我推荐的这一篇文章已经具体介绍了3种模式,我就不具体解说了。

三、画圆圈和文字

    private void paintText(Canvas canvas) {
        mPaint.setTextSize(mProgressTextSize);
        mPaint.setColor(mProgressColor);
        mPaint.setStyle(Paint.Style.FILL); // 设置实心
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(mProgressText, centreX, centreY, mPaint);

        Rect rect = new Rect();
        mPaint.getTextBounds(mFixText, 0, mFixText.length(), rect);
        mPaint.setTextSize(mFixTextSize);
        mPaint.setColor(mfixTextColor);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(mFixText, centreX, centreY + rect.height(), mPaint);

    }

    public void paintOval(Canvas canvas) {
        mPaint.setStrokeWidth(mOvalWidth); // 设置圆环的宽度
        mPaint.setAntiAlias(true); // 消除锯齿
        mPaint.setStyle(Paint.Style.STROKE); // 设置空心
        mPaint.setColor(mOvalColor); // 设置圆环的颜色
        RectF oval = new RectF(centreX - radius, centreY - radius, centreX + radius, centreY + radius);
        canvas.drawArc(oval, 270, (float) (-mProgress * 3.6), false, mPaint); // 根据进度画圆弧
    }

四、重写onDraw()方法;

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mOvalProgress == 0) {
            isStop = true;
            mProgressText = dff.format(mProgress);
        } else if (mOvalProgress < 0) {
            mProgressText = dff.format(-mProgress);
            mProgress = mProgress + 1.1f;
            if (mProgress >= -mOvalProgress  || getVisibility() == View.INVISIBLE || mProgress >= 100) {
                isStop = true;
                mProgressText = dff.format(mOvalProgress);//当最后一次画的时候,显示最终指定的值.
                mProgress = mOvalProgress;
            }
        } else {
            mProgressText = dff.format(mProgress);
            mProgress = mProgress + 1.1f;
            if (mProgress >= mOvalProgress || getVisibility() == View.INVISIBLE || mProgress >= 100) {
                isStop = true;
                mProgressText = dff.format(mOvalProgress);//当最后一次画的时候,显示最终指定的值.
                mProgress = mOvalProgress;
            }
        }

        paintOval(canvas);
        paintText(canvas);
        if (!isStop) {
            postInvalidate();
        }
    }

在画圆圈和文字之前要设置好对应的属性值。由于我们的需求是,如果进度为负数,圆圈颜色就为绿色。所以在画的时候要判断下最终进度是整数还是负数。

五、根据需求具体情况,添加一些getter和setter方法以及一些工具方法。

   public  void  setmOvalProgress(float mOvalProgress) {
        this.mOvalProgress = mOvalProgress;
        isStop = false;
        postInvalidate();
        mProgress = 0  ;
        if (mOvalProgress >= 0) {
            mOvalColor = WPBApp.getInstance().getResources().getColor(R.color.red);
        } else {
            mOvalColor = WPBApp.getInstance().getResources().getColor(R.color.green);
        }
    }
/**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     *
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());

    }
调用 setmOvalProgress()方法,就会像上图那样有动画效果了。一个自定义View就诞生了。

————————————我是分割线—————————————

完成这个View,不可能像我上面说的那样,非常顺利。也遇到了一些坑。
第一、我最开始想到的是继承View,后来一想继承progressBar会不会简化一些,这样我就可以很方便的去设置进度了。然而,事情并没有我想像的那样。整个listView列表的数据会错乱。而且它的进度是int类型,我的带有小数。我还需要做转换,于是还是继承View吧。
第二,开始画View的时候,我不知道ondraw()方法可以调用多次,居然使用了线程。所以在onDraw()方法中new了好多线程,导致listView列表卡顿。
第三、开始完全不理解onMeasuer().我连这个方法都没重写。
学习是一个积累的过程。在填坑的过程中进步!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章