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().我連這個方法都沒重寫。
學習是一個積累的過程。在填坑的過程中進步!

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