1. 程式人生 > >Android自定義View-圓形進度條

Android自定義View-圓形進度條

好幾天不寫部落格了,這段時間一直沒時間,感覺一直在忙,但是進度不大。
好了,言歸正傳,最近專案裡要用到這麼一個自定義view,是一個圓形的進度圓環,現在學習下怎麼來自定義它。
image.png

原始碼下載地址

自定義之前先分析一下,這個自定義View主要有以下幾個部分組成:
- 最外層的圓環
- 圓環上的小圓點,會隨著進度移動
- 圓弧,會隨著小圓點移動,圓環上有個漸變度
- 文字
- 新增動畫

1. 第一步還是先考慮一下進度圓環的屬性資訊。

我這裡可以設定的有,背景顏色,最外層圓環的寬度,最外層圓環的顏色,進度圓環的顏色(只不過這裡設定了一個漸進度),圓環上小圓點的顏色,大小,還有一些文字的設定,距離的設定等。

<!--CircleProgress,血壓測量進度動畫-->
    <declare-styleable name="CircleProgress">
        <!--背景-->
        <attr name="backGroundColor" format="color"></attr>
        <!--圓環的顏色-->
        <attr name="ringColor" format="color"></attr>
        <!--圓環的寬度-->
<attr name="ringSize" format="dimension"></attr> <!--進度圓環的顏色--> <attr name="ringprogressColor" format="color"></attr> <!--圓環上的小圓點顏色--> <attr name="dotColor" format="color"></attr> <!--圓環上的小圓點大小--> <attr
name="dotSize" format="dimension">
</attr> <!--進度字型顏色--> <attr name="textProgressColor" format="color"></attr> <!--進度字型大小--> <attr name="textProgressSize" format="dimension"></attr> <!--百分號顏色--> <attr name="textPercentColor" format="color"></attr> <!--固定字型設定--> <attr name="showProgressText" format="string"></attr> <!--固定字型顏色--> <attr name="texColor" format="color"></attr> <!--固定字型大小--> <attr name="texSize" format="dimension"></attr> <!--文字之間的距離--> <attr name="texMarginSize" format="dimension"></attr> <!--固定字型大小--> <attr name="setNumber" format="integer"></attr> </declare-styleable>

2. 當然,我們自定義view的名字必須叫CircleProgress,和上面attrs.xml裡的名字相同,你可以試試如果不同會出現什麼後果。

獲得我們在attrs.xml中定義的屬性
  public CircleProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //獲得atts.xml定義的屬性值,儲存在TypedArray中
        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleProgress, defStyleAttr, 0);
        int n = ta.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = ta.getIndex(i);
            switch (attr) {


                case R.styleable.CircleProgress_ringColors: //圓環顏色
                    ring_color = ta.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CircleProgress_ringSize: //圓環寬度
                    ringSize = ta.getDimension(R.styleable.CircleProgress_ringSize, 13);
                    break;
                case R.styleable.CircleProgress_ringprogressColor: //圓環進度顏色
                    ring_progress_color = ta.getColor(attr, Color.WHITE);
                    break;

                case R.styleable.CircleProgress_dotColor:  //小圓點
                    dot_color = ta.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.CircleProgress_dotSize:  //小圓點大小
                    dotSize = ta.getDimension(R.styleable.CircleProgress_dotSize, 32);
                    break;

                case R.styleable.CircleProgress_textProgressColor: //字型進度的顏色
                    text_progress_color = ta.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CircleProgress_textProgressSize:  //字型進度大小
                    textProgressSize = ta.getDimension(R.styleable.CircleProgress_textProgressSize, 32);
                    break;

                case R.styleable.CircleProgress_textPercentColor:  //百分號顏色
                    percent_color = ta.getColor(attr, Color.BLACK);
                    break;

                case R.styleable.CircleProgress_showProgressText:  //固定字型顯示
                    showText = ta.getString(R.styleable.CircleProgress_showProgressText);
                    break;
                case R.styleable.CircleProgress_texColor: //字型的顏色
                    text_color = ta.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CircleProgress_texSize:  //字型大小
                    texSize = ta.getDimension(R.styleable.CircleProgress_texSize, 17);
                    break;

                case R.styleable.CircleProgress_texMarginSize:  //字之間的款阿杜
                    texMarginSize = ta.getDimension(R.styleable.CircleProgress_texMarginSize, 9);
                    break;
            }
        }
        ta.recycle();
        init();
    }
重寫onMeasure()方法,讓控制元件支援wrap_content屬性
 /**
     * 重寫onMeasure()方法,支援wrap_content屬性
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);  //寬度的測量模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);  //寬度的測量值
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);  //高度的測量模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec); //高度的測量值
        //如果佈局裡面設定的是固定值,這裡取佈局裡面的固定值;如果設定的是match_parent,則取父佈局的大小
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            //如果佈局裡面沒有設定固定值,這裡取佈局的寬度的1/2
            width = widthSize * 1 / 2;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            //如果佈局裡面沒有設定固定值,這裡取佈局的高度的3/4
            height = heightSize * 3 / 4;
        }

        setMeasuredDimension(width, height);

    }
畫最外層的圓
 // 畫最外層的大圓環
        int centre = getWidth() / 2; //獲取圓心的x座標
        int radius = (int) (centre - 2 * ringSize); //圓環的半徑
        mPaint.setColor(ring_color); //設定圓環的顏色
        mPaint.setStyle(Paint.Style.STROKE); //設定空心
        mPaint.setStrokeWidth(ringSize); //設定圓環的寬度
        mPaint.setAntiAlias(true);  //消除鋸齒
        canvas.drawCircle(centre, centre, radius, mPaint); //畫出圓環
畫文字
 //畫進度文字
        //設定進度值的大小顏色,字型樣式
        textPaint.setStrokeWidth(0);
        textPaint.setColor(text_progress_color);
        textPaint.setTextSize(textProgressSize);
        textPaint.setTypeface(Typeface.MONOSPACE);

        //中間的進度百分比,先轉換成float在進行除法運算,不然都為0
        int percent = (int) (((float) tempProgress / (float) maxProgress) * 100);
        String percent_draw;
        if (percent == 0) {
            percent_draw = "00";
        } else {
            percent_draw = percent + "";
        }
        float textHeight = textProgressSize; //進度字型的高度
        float textWidth = textPaint.measureText(percent_draw);

        textPaint.setTextSize(textHeight);
        //   canvas.drawText(percent_draw, centre - 5 * textWidth, centre, textPaint);
        canvas.drawText(percent_draw, centre - textWidth / 2, centre + textProgressSize / 6, textPaint);

        //畫百分比號
        textPaint.setStrokeWidth(3);
        String text_percent = "%";
        textPaint.setTextSize(textHeight / 3);
        // canvas.drawText(text_percent, centre + 5 * textWidth , centre + textWidth / 2, textPaint);
        canvas.drawText("%", centre + textWidth / 2, centre + textProgressSize / 8, textPaint);

        //畫展示文字
        textPaint.setColor(text_color);
        textPaint.setTextSize(texSize);
        canvas.drawText(showText, centre - textProgressSize / 2 - 10, centre + textProgressSize / 3 + texMarginSize, textPaint);
畫圓弧

在畫圓弧的過程中,我們建立了一個SweepGradient渲染器,來畫我們的漸進色圓環。直接通過 ringProgressPaint.setShader(mSweepGradient)設定給Paint即可

        //畫圓弧
        ringProgressPaint.setStyle(Paint.Style.STROKE);
        ringProgressPaint.setStrokeWidth(ringSize);

        //  ringProgressPaint.setColor(ring_progress_color);
        RectF oval = new RectF(centre - radius, centre - radius, centre
                + radius, centre + radius);  //用於定義的圓弧的形狀和大小的界限

        //建立一個渲染器
        SweepGradient mSweepGradient=new SweepGradient(canvas.getWidth()/2
                ,canvas.getHeight()/2,new int[]{Color.rgb(130,213,131),Color.rgb(150,251,196),Color.rgb(130,213,131)},null);
        Matrix matrix=new Matrix();
        matrix.setRotate(-90f,canvas.getWidth()/2,canvas.getHeight()/2);
        mSweepGradient.setLocalMatrix(matrix);
        ringProgressPaint.setShader(mSweepGradient);
        canvas.drawArc(oval, 90, 360 * tempProgress / maxProgress, false, ringProgressPaint);
畫小圓點

其中有個計算來計算小圓點的座標,你可以修改這個計算部分來設定小圓點的位置,我把它放在了最下面的位置。

//畫圓點
        // 畫進度點   30°角度 的弧度 = 2 * PI / 360 * 30
        int rangle = 0;
        if (tempProgress == 0) {
            rangle = 360 / maxProgress;
        } else {
            rangle = 360 * (int) tempProgress / maxProgress;
        }

        double a = 0.0;//角度
        int pointX = 0;
        int pointY = 0;


        if (rangle > 0 && rangle <= 90) {
            a = 2 * Math.PI / 360 * (270 - rangle);
            pointX = centre + (int) (radius * Math.cos(a));
            pointY = centre - (int) (radius * Math.sin(a));
        } else if (rangle > 90 && rangle <= 180) {
            a = 2 * Math.PI / 360 * (rangle + 90);
            pointX = centre + (int) (radius * Math.cos(a));
            pointY = centre + (int) (radius * Math.sin(a));
        } else if (rangle > 180 && rangle <= 270) {
            a = 2 * Math.PI / 360 * (rangle);
            pointX = centre - (int) (radius * Math.sin(a));
            pointY = centre + (int) (radius * Math.cos(a));
        } else if (rangle > 270 && rangle <= 360) {
            a = 2 * Math.PI / 360 * (rangle - 90);
            pointX = centre - (int) (radius * Math.cos(a));
            pointY = centre - (int) (radius * Math.sin(a));
        }

        pointPaint.setColor(dot_color);
        pointPaint.setStyle(Paint.Style.FILL);
        pointPaint.setAntiAlias(true);  //消除鋸齒
        pointPaint.setShadowLayer(10, 0, 0, Color.GRAY);
        //  Log.d("TAG", "pointX = " + pointX + "||pointY = " + pointY);
        canvas.drawCircle(pointX, pointY, dotSize, pointPaint);
其他的方法

主要是設定圓環的進度和動畫的一些方法

 public synchronized void setMax(int maxProgress) {
        if (maxProgress < 0) {
            throw new IllegalArgumentException("maxProgress not less than 0");
        }
        this.maxProgress = maxProgress;
    }

    /**
     * 開啟動畫
     *
     * @param curProgress
     */
    public void setProgressWithAnimation(int curProgress) {
        this.curProgress = curProgress;
        animator = ValueAnimator.ofInt(0, curProgress);
        animator.setDuration(30000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                tempProgress = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();

    }

    /**
     * 完成測量
     *
     * @param curProgress
     */
    public void completeMeasure(int curProgress) {
        animator.cancel();
        this.curProgress = curProgress;
        tempProgress = curProgress;
        invalidate();
    }

    /**
     * 停止Progress
     */
    public void stopProgress(){
        animator.cancel();
        invalidate();
    }

這裡自定義的CircleProgress就定義完了,接下來可以在xml檔案中引用,然後通過下面兩行程式碼開啟

  mCircleProgress.setMax(100);
  mCircleProgress.setProgressWithAnimation(100);

當然你也可以根據自己的需求新增其他的方法,再次就不一一添加了。
直接執行看效果圖吧

Animation1.gif