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);
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值