自定義View實現之健康指數監控器

馬上要離開現單位了,蛋蛋的憂傷。。。以此紀念吧!

需求:健康指數監控控件
分析:
1、三條不同顏色的圓弧
2、最外層圓弧顏色根據進度不同漸變
3、外層圓弧內部邊緣文字描述
4、中心部分文字:健康指數
5、中間層圓弧進度顯示
解析:
1、畫三條圓弧角度爲120°~300°

    /**
     * 準備工作。。
     * 
     * @param canvas
     */
    private void drawAllArc(Canvas canvas) {
        float flagWidth = 0.0f;
        flagWidth = (mWidth - strokeOuterWidth);
        initPaint(outerColor, strokeOuterWidth, strokeOuterWidth,
                strokeOuterWidth, flagWidth, flagWidth);

        mColors = new int[] {// 漸變色數組
        0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFF00,
                0xFFFF0000 };
        // 漸變着色器
        Shader s = new SweepGradient(0, 0, mColors, null);
        mPaint.setShader(s);
        // 外層 第一段
        canvas.drawArc(oval, startAngle, sweetAngle / 3, false, mPaint);
        mPaint.setColor(getContext().getResources().getColor(
                R.color.outerColor2));
        // 外層 第二段
        canvas.drawArc(oval, startAngle + sweetAngle / 3, sweetAngle / 3,
                false, mPaint);
        mPaint.setColor(getContext().getResources().getColor(
                R.color.outerColor3));
        // 外層 第三段
        canvas.drawArc(oval, startAngle + sweetAngle / 3 * 2, sweetAngle / 3,
                false, mPaint);

        flagWidth = flagWidth - strokeMidWidth;

        initPaint(innerColor,
                strokeMidWidth, mOffset + strokeMidWidth + strokeOuterWidth,
                mOffset + strokeMidWidth + strokeOuterWidth,
                flagWidth - mOffset,
                flagWidth - mOffset);
        // 畫中間層
        canvas.drawArc(oval, startAngle, sweetAngle, false, mPaint);

        flagWidth = flagWidth - strokeOuterWidth;

        initPaint(innerColor, strokeInnerWidth, mOffset*2 + strokeInnerWidth + strokeMidWidth + strokeOuterWidth,
                mOffset*2 + strokeInnerWidth + strokeMidWidth + strokeOuterWidth,
                flagWidth - mOffset*2, 
                flagWidth - mOffset*2);
        // 畫內層
        canvas.drawArc(oval, startAngle, sweetAngle, false, mPaint);
    }

其中initPaint()方法是對畫筆的一些初始化操作,如下:

    /**
     * 初始化/重置 畫筆
     * 
     * @param outerColor
     * @param strokeOuterWidth
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    private void initPaint(int outerColor, float strokeOuterWidth, float left,
            float top, float right, float bottom) {
        mPaint.reset();
        mPaint.setColor(outerColor);
        mPaint.setStrokeWidth(strokeOuterWidth);
        mPaint.setAntiAlias(true); // 消除鋸齒
        mPaint.setStyle(Paint.Style.STROKE); // 設置空心
        oval = new RectF(left, top, right, bottom);
    }

oval 是它所在的矩形,由此確定位置。
2、顏色漸變

mColors = new int[] {// 漸變色數組
        0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFF00,
                0xFFFF0000 };
        // 漸變着色器
        Shader s = new SweepGradient(0, 0, mColors, null);
        mPaint.setShader(s);

主要用到 SweepGradient 爲 mPaint 設置着色器,第三個參數爲顏色數組,以此來確定漸變顏色值。(該方法具體使用請參考官方Api)
3、外層圓弧內部邊緣文字描述

/**
     * 主要用到api path.addArc 和 canvas.drawTextOnPath
     * 具體參數可以上網查查,這個應該可以計算出一套算法的,我這裏是一點點試出來的,(好尷尬,不會算法)
     * 
     * @param canvas
     */
    private void drawShowTextOnPath(Canvas canvas) {
        RectF ovalF = new RectF(strokeOuterWidth, strokeOuterWidth, mWidth
                - strokeOuterWidth, mWidth - strokeOuterWidth);
        float vOffset = 45;
        mTextPath.reset();
        mTextPath.addArc(ovalF, startAngle, 5);
        canvas.drawTextOnPath("0", mTextPath, 0, vOffset, textPaint);
        // 每次要 reset
        mTextPath.reset();
        mTextPath.addArc(ovalF, startAngle, 150 - 75);
        canvas.drawTextOnPath("差", mTextPath, 0, vOffset, textPaint);

        mTextPath.reset();
        mTextPath.addArc(ovalF, startAngle, 150);
        canvas.drawTextOnPath("25", mTextPath, 0, vOffset, textPaint);

        mTextPath.reset();
        mTextPath.addArc(ovalF, startAngle, 150 + 75);
        canvas.drawTextOnPath("中", mTextPath, 0, vOffset, textPaint);

        mTextPath.reset();
        mTextPath.addArc(ovalF, startAngle, 300);
        canvas.drawTextOnPath("50", mTextPath, 0, vOffset, textPaint);

        mTextPath.reset();
        mTextPath.addArc(ovalF, sweetAngle, 25);
        canvas.drawTextOnPath("良", mTextPath, 0, vOffset, textPaint);

        mTextPath.reset();
        mTextPath.addArc(ovalF,  sweetAngle, 100);
        canvas.drawTextOnPath("75", mTextPath, 0, vOffset, textPaint);

        mTextPath.reset();
        mTextPath.addArc(ovalF, sweetAngle, 165);
        canvas.drawTextOnPath("優", mTextPath, 0, vOffset, textPaint);

        mTextPath.reset();
        mTextPath.addArc(ovalF, sweetAngle , 225);
        canvas.drawTextOnPath("100", mTextPath, 0, vOffset, textPaint);
    }

主要用到api path.addArc 和 canvas.drawTextOnPath,具體使用方法參考官方Api
4、中心部分文字:健康指數

/**
     * 畫文字--健康指數質量
     * 
     * @param canvas
     */
    private void setMidText(Canvas canvas) {
        mTextStrPaint.setTextSize(22);
        mTextStrPaint.getTextBounds(strText, 0, strText.length(), boundsText);
        Log.e("VIEW_LOG_TAG", boundsText.right + "," + boundsText.bottom + ","
                + boundsText.top + "," + boundsText.left);
        canvas.drawText(strText, mWidth/2 - (float) (boundsText.right / 2)
                + (float) (boundsText.left / 2), mWidth/2 + mTextOffset, mTextStrPaint);
    }

    /**
     * 畫文字--健康指數數量
     * 
     * @param canvas
     */
    private void setMidTextNum(Canvas canvas) {
        mTextNumPaint.setTextSize(45);
        mTextNumPaint.getTextBounds(strTextNum, 0, strTextNum.length(), bounds);
        System.out.println(bounds.right + "," + bounds.bottom);
        Log.e("VIEW_LOG_TAG", bounds.right + "," + bounds.bottom + ","
                + bounds.top + "," + bounds.left);
        canvas.drawText(strTextNum, mWidth/2 - (float) (bounds.right / 2)
                + (float) (bounds.left / 2), mWidth/2, mTextNumPaint);
    }

其中難點就是要計算文字顯示位置,首先確定文字所在矩形

mTextStrPaint.getTextBounds(strText, 0, strText.length(), boundsText);

然後調用 drawText

android.graphics.Canvas.drawText(String text, float x, float y, Paint paint)

其中 x , y 是確定它所繪製的位置

x = mWidth/2 - (float) (boundsText.right / 2)
                + (float) (boundsText.left / 2);
y = mWidth/2 + mTextOffset;

由總寬度的一半減去文字的一半就可以確定文字開始繪製的位置了。
5、中間層圓弧進度顯示

    /**
     * 根據當前進度畫線
     * 
     * @param canvas
     */
    private void setAngleProgress(Canvas canvas) {

        initPaint(midColor, strokeMidWidth, 
                mOffset  + strokeMidWidth + strokeOuterWidth,
                mOffset + strokeMidWidth + strokeOuterWidth,
                mWidth - strokeOuterWidth - strokeMidWidth - mOffset,
                mWidth - strokeOuterWidth - strokeMidWidth - mOffset);
        float progress = this.mProgress * sweetAngle / 100.0f;
        // 畫中間層
        canvas.drawArc(oval, startAngle, progress, false, mPaint);
    }

其原理就是在中間弧線上根據進度繪製一條弧線,因爲總弧度爲300°,所以其轉換爲當前進度(0-100)* 300 / 100 , 這樣就可以計算出需要繪製的進度了。
至此整個過程已經結束了,其中不足之處就是繪製外層圓弧文字的時候計算問題,沒有找到規律,對Api使用不是太熟悉,如果有哪位朋友能改善希望告知,不勝感激!
對了,還有適配的問題,在這裏我是重寫 onMeasure

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /**
         * 設置寬度
         */
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
        {
            mWidth = specSize;
        }
        /***
         * 設置高度
         */

        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
        {
            mHeight = specSize;
        }

        mWidth = mHeight = Math.max(mWidth, mHeight);
        if(mWidth > mScreenWidth || mWidth == 0){
            mWidth = mScreenWidth;
        }
        Log.e("xxx", "EXACTLY" + mWidth + "  " + mHeight);
        setMeasuredDimension(mWidth,mWidth); // 強制長寬相等

    }

如果 xml 裏沒有指定寬高或者指定的高度大於屏幕寬度的話則
mWidth = mScreenWidth;
然後 setMeasuredDimension(mWidth,mWidth); // 強制長寬相等

代碼下載地址demo

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