Android自定義View初探(一)——餅圖

接觸Android已有兩年有餘,自從工作後都是爲了工作而學習,沒有時間去想、去做一些事情,久而久之,發現自己除了複製粘貼別人的代碼和敲打一些簡單的代碼之外,無所長進。

當然,這裏開始嘗試做一些事情的時候,並不一定就是“長進”了,只是爲了一點點突破,對自己思維的突破,對自己從無到有的突破。因爲我意識到,有些事情,你不去嘗試,你永遠都無法進步。

對於從事Android開發工作的人來說,View這個東西既熟悉又陌生。熟悉的是他的基本功能,陌生的是他的原理。曾經面試過幾個公司,面試官的問題中都提到了View相關的問題,不過大致都是“你只需要簡單介紹View的基本繪製流程,不需要非常瞭解”,說到這個,我相信絕大部分Android開發者的大腦中都會迅速的呈現:onLayout、onMeasure,onDraw——如果這些你不知道,那你就需要去補補功課了。

當然,今天我們這裏也只是入門操作,所以可以先不管那些複雜的自定義View的實現,我們今天只嘗試在onDraw方法裏做一些簡單的自定義操作。

可能github或者別的地方早已有類似的開源控件,但是我沒找到,所以只好自己做了。我不想只貼代碼,我想讓所有看過這篇文章的人都瞭解我的想法,或許很多地方是錯的,這樣不就爲你們提供了反面教材嗎?O(∩_∩)O

先看兩張圖片(只需要看圖片上半部分即可):
運動時長的佔比圖--有數據
運動時長的佔比圖--無數據

看到這兩張圖片後,Android高手已經知道實現的原理了,不過這裏我還是要嘮叨一下,萬一有人不知道呢?O(∩_∩)O

整個View分爲兩個大部分:左側的圓和右側的矩形(相當於圖列),矩形的底色和圓的底色保持一致;左側其實是六個扇形和一個圓外加三個text組成,右側是六個圓角矩形加九個text(百分比、時長、運動類型*3)組成。

繪製的流程可以隨意控制,我是先畫的左邊部分,這部分的代碼如下(代碼裏有註釋,其實難點在於計算圖形和位子的位置):

    /**
     * 畫運動餅圖
     * 
     * @param canvas
     */
    private void drawSport_left(Canvas canvas)
    {
        //左邊部分的中心點座標(px)
        final float centerX = width / 3;
        final float centerY = height / 2;
        float baseDgree = 0;
        float total = 0;
        if (basePieAmounts != null && basePieAmounts.length > 0 && basePieColors != null
                && basePieColors.length > 0)
        {
            for (int i = 0; i < basePieAmounts.length; i++)
            {
                total += basePieAmounts[i];
            }
            // 畫基礎扇形
            for (int i = 0; i < basePieAmounts.length; i++)
            {
                float sweepAngle = (-DEFAULT_CIRCLE_ANGLE / total) * basePieAmounts[i];
                paint.setColor(SPORT_BASE_SWEEP_COLOR);
                paint.setAlpha(OPAQUE);
                paint.setStyle(Style.FILL);
                canvas.drawArc(pieRectf, baseDgree, sweepAngle + DEFAULT_OFFSET_ANGLE, true, paint);
                baseDgree -= -sweepAngle;
            }
        }
        // 畫佔比扇形
        if (childernAmounts != null && childernAmounts.length > 0)
        {
            baseDgree = 0;
            for (int i = 0; i < childernAmounts.length; i++)
            {
                float sweepAngle = (-DEFAULT_CIRCLE_ANGLE / total) * basePieAmounts[i];
                // 畫佔比扇形
                paint.setColor(childPieColors[i]);
                paint.setStyle(Style.FILL);
                paint.setXfermode(new PorterDuffXfermode(Mode.SRC_OVER));
                paint.setAlpha(OPAQUE);
                canvas.drawArc(pieRectf, baseDgree, childernAmounts[i]
                        / (basePieAmounts[i] / (sweepAngle + DEFAULT_OFFSET_ANGLE)), true, paint);//這個DEFAULT_OFFSET_ANGLE就是爲了實現扇形之間的間隙的

                baseDgree -= -sweepAngle;
            }
        }
        // 畫能量消耗小圓
        paint.setColor(Color.WHITE);
        canvas.drawCircle(centerX, centerY, DEFAULT_CIRCLE_RADIUS / 1.6f, paint);
        // 畫小圓能量消耗文本
        paint.setColor(Color.BLACK);
        paint.setTextSize(sportPowerCenterTextSize * density);
        paint.setTypeface(Typeface.create("System", Typeface.BOLD));
        paint.getTextBounds(sportPowerText, 0, sportPowerText.length(), rect);
        int tempH = rect.height();
        canvas.drawText(sportPowerText, centerX - rect.width() / 2, centerY + tempH / 2,
                paint);
        // 畫小圓頂部文本
        resetPaint();
        paint.setColor(Color.GRAY);
        paint.setTextSize(sportPowerRoundTextSize * density);
        paint.getTextBounds(SPORT_POWER_TOP_TEXT, 0, SPORT_POWER_TOP_TEXT.length(), rect);
        canvas.drawText(SPORT_POWER_TOP_TEXT, centerX - rect.width() / 2,
                centerY - tempH - rect.height() / 2, paint);
        // 畫小圓底部文本(可不重新設置paint)
        paint.setColor(Color.GRAY);
        paint.setTextSize(sportPowerRoundTextSize * density);
        paint.getTextBounds(SPORT_POWER_BOTTOM_TEXT, 0, SPORT_POWER_BOTTOM_TEXT.length(), rect);
        canvas.drawText(SPORT_POWER_BOTTOM_TEXT, centerX - rect.width() / 2,
                centerY + 2 * rect.height() + tempH / 2, paint);
    }

裏面有些參數看不懂的不用糾結,後面我會貼上完整的代碼,這裏只是展現左側部分的繪製:先畫三個基礎的時間長度扇形,並不是等分的,比如從0度逆時針看,每個扇形代表的時長依次是15min、10min、5min,那麼對應的扇形所佔角度爲360/30*15、360/30*10、360/30*5,知道這個原理後,對應時長的已完成時長(佔比扇形的角度)也就知道了。

再看看右邊部分,代碼如下:

    /**
     * 畫樣例
     * 
     * @param canvas
     */
    private void drawSportLabels(Canvas canvas)
    {
        // 畫底部樣例
        if (bottomSamples != null && bottomSamples.length > 0 && basePieColors != null
                && basePieColors.length > 0)
        {
            resetPaint();
            float offect = 0;
            float textH = 0;
            final float offsetX = width / 3 + DEFAULT_CIRCLE_RADIUS;//與左側圓的距離
            final float offsetY = height / 2 - 7.25f * DEFAULT_SPACING_HEIGHT * density;//7.25的由來:2.25的矩形高度+5
            for (int i = 0; i < bottomSamples.length; i++)
            {
                String text = bottomSamples[i];
                String sportTime = sportTimes[i];
                paint.setColor(SPORT_BASE_SWEEP_COLOR);
                paint.setAlpha(OPAQUE);
                paint.setTextSize(sampleTextSize * density);
                paint.getTextBounds(text, 0, text.length(), rect);
                textH = rect.height();
                // Log.e(TAG, "textLen : " + rect.width() + " textHeigh : " + rect.height());
                // 畫圖列方塊
                RectF tempRectF = new RectF(offsetX + DEFAULT_SPACING_WIDTH * density, offsetY
                        + (DEFAULT_SPACING_HEIGHT + offect) * density, offsetX + 2.5f
                        * DEFAULT_SPACING_WIDTH * density, offsetY
                        + (2.5f * DEFAULT_SPACING_HEIGHT + offect) * density);
                canvas.drawRoundRect(tempRectF, 5.0f, 5.0f, paint);

                // 畫圖列文本(運動類型)
                paint.setColor(childPieColors[i]);
                canvas.drawText(text, offsetX + DEFAULT_SPACING_WIDTH * density, offsetY
                        + (2.75f * DEFAULT_SPACING_HEIGHT + offect) * density+textH, paint);

                // 畫運動時長
                paint.setTextSize(sampleTextSize * density * 2);
                paint.setTypeface(Typeface.create("System", Typeface.BOLD));
                paint.getTextBounds(sportTime, 0, sportTime.length(), rect);
                canvas.drawText(sportTime, offsetX + (2.6f * DEFAULT_SPACING_WIDTH) * density, offsetY
                        + (1.5f * DEFAULT_SPACING_HEIGHT + offect) * density+rect.height()/2,
                        paint);
                // 畫運動時長佔比矩形
                float persent = childernAmounts[i] / basePieAmounts[i];
                float child = 1.5f * DEFAULT_SPACING_WIDTH * persent;
                String per = decimalFormat.format(persent * 100) + "%";
                paint.setColor(childPieColors[i]);
                tempRectF = new RectF(offsetX + DEFAULT_SPACING_WIDTH * density, offsetY
                        + (DEFAULT_SPACING_HEIGHT + offect) * density, offsetX
                        + (DEFAULT_SPACING_WIDTH + child) * density, offsetY
                        + (2.5f * DEFAULT_SPACING_HEIGHT + offect) * density);
                canvas.drawRoundRect(tempRectF, 5.0f, 5.0f, paint);
                //畫運動時長佔比百分數
                paint.setColor(Color.WHITE);
                paint.setTypeface(Typeface.create("System", Typeface.NORMAL));
                paint.setTextSize(sampleTextSize * density);
                paint.getTextBounds(per, 0, per.length(), rect);
                canvas.drawText(
                        per,
                        //1.75是1.5的矩形寬度+0.25的偏移,文字和矩形分開
                        offsetX + 1.75f * DEFAULT_SPACING_WIDTH * density - rect.width() / 2,
                        //2.25是要減去1.25的矩形高度,再減去1矩形的高度讓文字底部與矩形底部平行
                        offsetY + (2.25f * DEFAULT_SPACING_HEIGHT + offect) * density
                                - rect.height() / 2, paint);
                //每畫一次向下移動5個距離
                offect += 5 * DEFAULT_SPACING_HEIGHT;
            }
        }
    }

這裏的原理也很簡單:計算出一個矩形的位置(並不一定要像我一樣從上往下畫),再根據這個位置畫出與其相鄰的圖形或文字,就是那些座標要注意適配屏幕,注意density的使用。

畫圖形和文字的核心代碼就是這兩個方法裏的內容,當然,這兩個方法都是在onDraw裏執行的。我知道一些開發者只看這兩部分代碼,也不理解我文章的意圖,畢竟我也是第一次嘗試做這樣的事情,難免有想不到的地方,下面我就貼出整個自定義View的代碼,與大家一起共同探討和交流:

/**
 * PieView
 * @author MR.yan <br/>
 *         create at 2015年3月10日 下午4:19:47
 */
public class PieView extends View
{
    static final String TAG = PieView.class.getSimpleName();
    /**
     * 數據顯示類型-能量
     */
    public static final int CHART_DRAWING_DRAW_TYPE_POWER = 1;
    /**
     * 數據顯示類型-睡眠
     */
    public static final int CHART_DRAWING_DRAW_TYPE_SLEEP = CHART_DRAWING_DRAW_TYPE_POWER + 1;

    /**
     * 數據顯示類型-健康
     */
    public static final int CHART_DRAWING_DRAW_TYPE_HEALTH = CHART_DRAWING_DRAW_TYPE_POWER + 2;

    /**
     * 數據顯示類型-運動
     */
    public static final int CHART_DRAWING_DRAW_TYPE_SPORT = CHART_DRAWING_DRAW_TYPE_POWER + 3;

    private static final int SPORT_BASE_SWEEP_COLOR = Color.parseColor("#d3d3d3");
    /**
     * 能量消耗頂部固定文本
     */
    private static final String SPORT_POWER_TOP_TEXT = "能量消耗";
    /**
     * 能量消耗底部固定文本
     */
    private static final String SPORT_POWER_BOTTOM_TEXT = "大卡";
    /**
     * 圓周角度
     */
    public static final float DEFAULT_CIRCLE_ANGLE = 360.0f;
    /**
     * 默認扇形角度差值
     */
    public static final float DEFAULT_OFFSET_ANGLE = 1.5f;
    /**
     * 透明度
     */
    private static final int OPAQUE = 0xFF;
    /**
     * 餅圖半徑
     */
    private float DEFAULT_CIRCLE_RADIUS = 300;
    /**
     * 默認水平間距
     */
    private float DEFAULT_SPACING_WIDTH = 20;
    /**
     * 默認垂直間距
     */
    private float DEFAULT_SPACING_HEIGHT = 10;
    /**
     * 總數量
     */
    private float totalAmounts;
    /**
     * 屏幕密度
     */
    private float density;
    /**
     * 畫筆
     */
    private Paint paint;
    /**
     * 是否初始化過
     */
    private boolean isInit;
    /**
     * 測量文本寬高的矩形
     */
    private Rect rect;
    /**
     * 圖表視圖的寬度(px)
     */
    private float width;
    /**
     * 圖表視圖的高度(px)
     */
    private float height;
    /**
     * 底部圖表示例文本數組
     */
    private String bottomSamples[] = new String[] { "輕度運動", "中度運動", "劇烈運動" };
    // 運動時長數組
    private String sportTimes[] = new String[] { "0分鐘", "0分鐘", "0分鐘" };
    /**
     * 底部圖表示例文本的大小 sp
     */
    private float sampleTextSize = 10;
    /**
     * 運動能量消耗值字體大小
     */
    private float sportPowerCenterTextSize = 30;
    /**
     * 運動能量消耗其他字體大小
     */
    private float sportPowerRoundTextSize = 18;

    /**
     * 是否在扇形上顯示文本
     */
    private boolean showPieText;

 <color name="sport_lsport_base_color">#167aa4</color> <!-- 輕度運動底色 -->
    <color name="sport_lsport_over_color">#00b4ff</color> <!-- 輕度運動覆蓋色 -->
    <color name="sport_msport_base_color">#1c924b</color> <!-- 中度運動底色 -->
    <color name="sport_msport_over_color">#0fce5b</color> <!-- 中度運動覆蓋色 -->
    <color name="sport_hsport_base_color">#b82649</color> <!-- 高度運動底色 -->
    <color name="sport_hsport_over_color">#ff2f06</color> <!-- 高度運動覆蓋色 -->
    /**
     * 起始餅狀圖的顏色數組(主要是運動起始扇形)
     */
    private int basePieColors[] = new int[] {
            getResources().getColor(R.color.sport_lsport_base_color),
            getResources().getColor(R.color.sport_msport_base_color),
            getResources().getColor(R.color.sport_hsport_base_color) };

    /**
     * 佔比餅狀圖的顏色數組(主要是運動佔比扇形)
     */
    private int childPieColors[] = new int[] {
            getResources().getColor(R.color.sport_lsport_over_color),
            getResources().getColor(R.color.sport_msport_over_color),
            getResources().getColor(R.color.sport_hsport_over_color) };

    /**
     * 起始餅狀圖的值數組(對應扇形)
     */
    private float basePieAmounts[] = new float[] { 1, 1, 1 };
    /**
     * 佔比扇形的值數組
     */
    private float[] childernAmounts = new float[] { 0, 0, 0 };
    /**
     * 當前的圖表類型
     */
    private int mCurrentDrawType;

    /**
     * 默認圖表背景顏色
     */
    private int defaultBgColor = color.whitesmoke;
    /**
     * 佔比扇形的顏色
     */
    private int smallPieColor = Color.YELLOW;
    /**
     * 外圓外切矩形
     */
    private RectF pieRectf;
    /**
     * 格式化小數
     */
    private DecimalFormat decimalFormat;
    /**
     * 內圓內的文本
     */
    private String scoreText = "0分";
    /**
     * 得分的字體
     */
    private float scoreTextSize = 10;
    /**
     * 圖表標題
     */
    private String pieTitle = "";
    /**
     * 運動圖表能量消耗文本
     */
    private String sportPowerText = "0";

    public String[] getSportTimes()
    {
        return sportTimes;
    }

    public void setSportTimes(String[] sportTimes)
    {
        this.sportTimes = sportTimes;
    }

    public float getSportPowerCenterTextSize()
    {
        return sportPowerCenterTextSize;
    }

    public void setSportPowerCenterTextSize(float sportPowerCenterTextSize)
    {
        this.sportPowerCenterTextSize = sportPowerCenterTextSize;
    }

    public float getSportPowerRoundTextSize()
    {
        return sportPowerRoundTextSize;
    }

    public void setSportPowerRoundTextSize(float sportPowerRoundTextSize)
    {
        this.sportPowerRoundTextSize = sportPowerRoundTextSize;
    }

    public String getSportPowerText()
    {
        return sportPowerText;
    }

    public void setSportPowerText(String sportPowerText)
    {
        this.sportPowerText = sportPowerText;
    }

    public String getPieTitle()
    {
        return pieTitle;
    }

    public void setPieTitle(String pieTitle)
    {
        this.pieTitle = pieTitle;
    }

    public float getTotalAmounts()
    {
        return totalAmounts;
    }

    public void setTotalAmounts(float totalAmounts)
    {
        this.totalAmounts = totalAmounts;
    }

    public float getScoreTextSize()
    {
        return scoreTextSize;
    }

    public void setScoreTextSize(float scoreTextSize)
    {
        this.scoreTextSize = scoreTextSize;
    }

    public String getScoreText()
    {
        return scoreText;
    }

    public void setScoreText(String scoreText)
    {
        this.scoreText = scoreText;
    }

    /**
     * 獲取佔比扇形顏色
     * 
     * @return
     */
    public int getSmallPieColor()
    {
        return smallPieColor;
    }

    /**
     * 設置佔比扇形顏色
     * 
     * @param smallPieColor
     */
    public void setSmallPieColor(int smallPieColor)
    {
        this.smallPieColor = smallPieColor;
    }

    /**
     * 獲取當前的圖表類型
     * 
     * @return
     */
    public int getmCurrentDrawType()
    {
        return mCurrentDrawType;
    }

    /**
     * 設置當前圖表類型
     * 
     * @param mCurrentDrawType
     */
    public void setmCurrentDrawType(int mCurrentDrawType)
    {
        this.mCurrentDrawType = mCurrentDrawType;
    }

    /**
     * 獲取圖表默認背景顏色
     * 
     * @return
     */
    public int getDefaultBgColor()
    {
        return defaultBgColor;
    }

    /**
     * 設置圖表默認背景顏色
     * 
     * @param defaultBgColor
     */
    public void setDefaultBgColor(int defaultBgColor)
    {
        this.defaultBgColor = defaultBgColor;
    }

    /**
     * 獲取起始餅狀顏色數組
     * 
     * @return
     */
    public int[] getBasePieColors()
    {
        return basePieColors;
    }

    /**
     * 設置起始餅狀顏色數組
     * 
     * @param basePieColors
     */
    public void setBasePieColors(int[] basePieColors)
    {
        this.basePieColors = basePieColors;
    }

    /**
     * 獲取基礎扇形的對應值
     * 
     * @return
     */
    public float[] getBasePieAmounts()
    {
        return basePieAmounts;
    }

    /**
     * 設置基礎扇形的值
     * 
     * @param basePieAmounts
     */
    public void setBasePieAmounts(float[] basePieAmounts)
    {
        this.basePieAmounts = basePieAmounts;
    }

    /**
     * 獲取底部示例文本數組
     * 
     * @return
     */
    public String[] getBottomSamples()
    {
        return bottomSamples;
    }

    /**
     * 設置底部示例文本數組
     * 
     * @param bottomSamples
     */
    public void setBottomSamples(String[] bottomSamples)
    {
        this.bottomSamples = bottomSamples;
    }

    /**
     * 獲取底部文本示例文字大小
     * 
     * @return
     */
    public float getSampleTextSize()
    {
        return sampleTextSize;
    }

    /**
     * 設置底部文本示例文字大小
     * 
     * @param sampleTextSize
     */
    public void setSampleTextSize(float sampleTextSize)
    {
        this.sampleTextSize = sampleTextSize;
    }

    /**
     * 是否在餅狀圖上顯示文本
     * 
     * @return
     */
    public boolean isShowPieText()
    {
        return showPieText;
    }

    /**
     * 設置是否在餅狀圖上顯示文本
     * 
     * @param showBarText
     */
    public void setShowPieText(boolean showBarText)
    {
        this.showPieText = showBarText;
    }

    /**
     * 增加佔比扇形圖(包含的值的順序必須和構造時傳入的初始值的順序一樣)
     * 
     * @param amounts 新增的量集合
     * @return
     */
    public synchronized int onDrawPieChar(float amounts[])
    {
        if (amounts == null || amounts.length < 1)
            return -1;
        if (basePieAmounts == null || basePieAmounts.length < 1 || basePieColors == null
                || basePieColors.length < 0)
            return -2;
        for (int i = 0; i < amounts.length; i++)
        {
            if (amounts[i] < 0)
                amounts[i] = 0;
            if (mCurrentDrawType == CHART_DRAWING_DRAW_TYPE_SPORT)
                if (amounts[i] > basePieAmounts[i])
                    amounts[i] = basePieAmounts[i];
        }
        childernAmounts = Arrays.copyOf(amounts, amounts.length);

        postInvalidate();

        return 0;
    }

    public PieView(Context c)
    {
        this(c, null);
    }

    public PieView(Context c, AttributeSet attributeSet)
    {
        this(c, attributeSet, 0);
    }

    public PieView(Context c, AttributeSet attributeSet, int defaultStyle)
    {
        super(c, attributeSet, defaultStyle);
        init(c);
    }

    /**
     * 初始化
     * 
     * @param c
     */
    private void init(Context c)
    {
        density = c.getResources().getDisplayMetrics().density;
        paint = new Paint();
        pieRectf = new RectF();
        rect = new Rect();
        decimalFormat = new DecimalFormat();
        decimalFormat.setMaximumFractionDigits(0);
    }

    private void resetPaint()
    {
        paint.reset();
        paint.setAntiAlias(true);
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        drawInit(canvas);
        DEFAULT_SPACING_WIDTH = 30;
        DEFAULT_SPACING_HEIGHT = 12;
        drawSport_left(canvas);
        drawSportLabels(canvas);            
    }

    /**
     * 畫初始部分
     * 
     * @param canvas
     */
    private void drawInit(Canvas canvas)
    {
        resetPaint();
        if (!isInit)
        {
            width = getWidth();
            height = getHeight();
            float temp = width > height ? height : width;
            if (DEFAULT_CIRCLE_RADIUS > temp / 2 - 15 * density)
                DEFAULT_CIRCLE_RADIUS = temp / 2 - 15 * density;
            switch (mCurrentDrawType)
            {
                case CHART_DRAWING_DRAW_TYPE_HEALTH:
                    pieRectf = new RectF(width / 2 - DEFAULT_CIRCLE_RADIUS, height / 2
                            - DEFAULT_CIRCLE_RADIUS, width / 2 + DEFAULT_CIRCLE_RADIUS, height / 2
                            + DEFAULT_CIRCLE_RADIUS);// 設置個新的長方形,掃描測量
                    break;

                case CHART_DRAWING_DRAW_TYPE_SPORT:
                    pieRectf = new RectF(width / 3 - DEFAULT_CIRCLE_RADIUS, height / 2
                            - DEFAULT_CIRCLE_RADIUS, width / 3 + DEFAULT_CIRCLE_RADIUS, height / 2
                            + DEFAULT_CIRCLE_RADIUS);// 設置個新的長方形,掃描測量
                    break;
            }

            // Log.e(TAG, "widtdh : " + width + " height : " + height);
            isInit = true;
        }
        // 畫標題
        paint.setColor(Color.GRAY);
        paint.setTypeface(Typeface.create("System", Typeface.BOLD));
        paint.setTextSize(sampleTextSize * density);
        paint.getTextBounds(pieTitle, 0, pieTitle.length(), rect);
        canvas.drawText(pieTitle, DEFAULT_SPACING_WIDTH * density, DEFAULT_SPACING_HEIGHT * density
                + rect.height(), paint);

        resetPaint();
        // // 圖表底色
        // paint.setColor(defaultBgColor);
        // paint.setAlpha(OPAQUE);
        // canvas.drawRect(0, 0, width, height, paint);
    }
    /**
     * 畫樣例
     * 
     * @param canvas
     */
    private void drawSportLabels(Canvas canvas)
    {
        // 畫底部樣例
        if (bottomSamples != null && bottomSamples.length > 0 && basePieColors != null
                && basePieColors.length > 0)
        {
            resetPaint();
            float offect = 0;
            float textH = 0;
            final float offsetX = width / 3 + DEFAULT_CIRCLE_RADIUS;
            final float offsetY = height / 2 - 7.25f * DEFAULT_SPACING_HEIGHT * density;
            for (int i = 0; i < bottomSamples.length; i++)
            {
                String text = bottomSamples[i];
                String sportTime = sportTimes[i];
                paint.setColor(SPORT_BASE_SWEEP_COLOR);
                paint.setAlpha(OPAQUE);
                paint.setTextSize(sampleTextSize * density);
                paint.getTextBounds(text, 0, text.length(), rect);
                textH = rect.height();
                // Log.e(TAG, "textLen : " + rect.width() + " textHeigh : " + rect.height());
                // 畫圖列方塊
                RectF tempRectF = new RectF(offsetX + DEFAULT_SPACING_WIDTH * density, offsetY
                        + (DEFAULT_SPACING_HEIGHT + offect) * density, offsetX + 2.5f
                        * DEFAULT_SPACING_WIDTH * density, offsetY
                        + (2.5f * DEFAULT_SPACING_HEIGHT + offect) * density);
                canvas.drawRoundRect(tempRectF, 5.0f, 5.0f, paint);

                // 畫圖列文本
                paint.setColor(childPieColors[i]);
                canvas.drawText(text, offsetX + DEFAULT_SPACING_WIDTH * density, offsetY
                        + (2.75f * DEFAULT_SPACING_HEIGHT + offect) * density+textH, paint);

                // 畫運動時長
                paint.setTextSize(sampleTextSize * density * 2);
                paint.setTypeface(Typeface.create("System", Typeface.BOLD));
                paint.getTextBounds(sportTime, 0, sportTime.length(), rect);
                canvas.drawText(sportTime, offsetX + (2.6f * DEFAULT_SPACING_WIDTH) * density, offsetY
                        + (1.5f * DEFAULT_SPACING_HEIGHT + offect) * density+rect.height()/2,
                        paint);
                // 畫圖列百分比
                float persent = childernAmounts[i] / basePieAmounts[i];
                float child = 1.5f * DEFAULT_SPACING_WIDTH * persent;
                String per = decimalFormat.format(persent * 100) + "%";
                paint.setColor(childPieColors[i]);
                tempRectF = new RectF(offsetX + DEFAULT_SPACING_WIDTH * density, offsetY
                        + (DEFAULT_SPACING_HEIGHT + offect) * density, offsetX
                        + (DEFAULT_SPACING_WIDTH + child) * density, offsetY
                        + (2.5f * DEFAULT_SPACING_HEIGHT + offect) * density);
                canvas.drawRoundRect(tempRectF, 5.0f, 5.0f, paint);
                //畫運動時長佔比百分數
                paint.setColor(Color.WHITE);
                paint.setTypeface(Typeface.create("System", Typeface.NORMAL));
                paint.setTextSize(sampleTextSize * density);
                paint.getTextBounds(per, 0, per.length(), rect);
                canvas.drawText(
                        per,
                        offsetX + 1.75f * DEFAULT_SPACING_WIDTH * density - rect.width() / 2,
                        offsetY + (2.25f * DEFAULT_SPACING_HEIGHT + offect) * density
                                - rect.height() / 2, paint);
                //每畫一次向下移動5個距離
                offect += 5 * DEFAULT_SPACING_HEIGHT;
            }
        }
    }

    /**
     * 畫運動餅圖
     * 
     * @param canvas
     */
    private void drawSport_left(Canvas canvas)
    {
        //左邊部分的中心點座標(px)
        final float centerX = width / 3;
        final float centerY = height / 2;
        float baseDgree = 0;
        float total = 0;
        if (basePieAmounts != null && basePieAmounts.length > 0 && basePieColors != null
                && basePieColors.length > 0)
        {
            for (int i = 0; i < basePieAmounts.length; i++)
            {
                total += basePieAmounts[i];
            }
            // 畫基礎扇形
            for (int i = 0; i < basePieAmounts.length; i++)
            {
                float sweepAngle = (-DEFAULT_CIRCLE_ANGLE / total) * basePieAmounts[i];
                paint.setColor(SPORT_BASE_SWEEP_COLOR);
                paint.setAlpha(OPAQUE);
                paint.setStyle(Style.FILL);
                canvas.drawArc(pieRectf, baseDgree, sweepAngle + DEFAULT_OFFSET_ANGLE, true, paint);
                baseDgree -= -sweepAngle;
            }
        }
        // 畫佔比扇形
        if (childernAmounts != null && childernAmounts.length > 0)
        {
            baseDgree = 0;
            for (int i = 0; i < childernAmounts.length; i++)
            {
                float sweepAngle = (-DEFAULT_CIRCLE_ANGLE / total) * basePieAmounts[i];
                // 畫佔比扇形
                paint.setColor(childPieColors[i]);
                paint.setStyle(Style.FILL);
                paint.setXfermode(new PorterDuffXfermode(Mode.SRC_OVER));
                paint.setAlpha(OPAQUE);
                canvas.drawArc(pieRectf, baseDgree, childernAmounts[i]
                        / (basePieAmounts[i] / (sweepAngle + DEFAULT_OFFSET_ANGLE)), true, paint);
                baseDgree -= -sweepAngle;
            }
        }
        // 畫能量消耗小圓
        paint.setColor(Color.WHITE);
        canvas.drawCircle(centerX, centerY, DEFAULT_CIRCLE_RADIUS / 1.6f, paint);
        // 畫小圓能量消耗文本
        paint.setColor(Color.BLACK);
        paint.setTextSize(sportPowerCenterTextSize * density);
        paint.setTypeface(Typeface.create("System", Typeface.BOLD));
        paint.getTextBounds(sportPowerText, 0, sportPowerText.length(), rect);
        int tempH = rect.height();
        canvas.drawText(sportPowerText, centerX - rect.width() / 2, centerY + tempH / 2,
                paint);
        // 畫小圓頂部文本
        resetPaint();
        paint.setColor(Color.GRAY);
        paint.setTextSize(sportPowerRoundTextSize * density);
        paint.getTextBounds(SPORT_POWER_TOP_TEXT, 0, SPORT_POWER_TOP_TEXT.length(), rect);
        canvas.drawText(SPORT_POWER_TOP_TEXT, centerX - rect.width() / 2,
                centerY - tempH - rect.height() / 2, paint);
        // 畫小圓底部文本(可不重新設置paint)
        paint.setColor(Color.GRAY);
        paint.setTextSize(sportPowerRoundTextSize * density);
        paint.getTextBounds(SPORT_POWER_BOTTOM_TEXT, 0, SPORT_POWER_BOTTOM_TEXT.length(), rect);
        canvas.drawText(SPORT_POWER_BOTTOM_TEXT, centerX - rect.width() / 2,
                centerY + 2 * rect.height() + tempH / 2, paint);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章