Android自定义控件——饼状统计图

一、概述

        有时开发会遇到需要的图表,导入一个三方库太大,这时候就可以自己来自定义个图表控件,近日我就开发了个饼状图,用来显示各板块占比和信息显示,并加入了动画,某块模块放大,自定义设置板块颜色、字体颜色等功能,可直接拿来使用。

二、效果图

也可以设置成实心的饼状图

三、设计思路

        先绘制一个个弧形版块拼接成圆,同一圆心,再绘制一个小点的带透明度的白色圆盖在其上,然后绘制中心的孔,即纯白的圆盖在最上面,最后根据计算,将文字绘制在版块的角平分线上,居在透明圆和外圆的中心。

四、代码实现

1、重写draw方法

        再calculate()方法中计算外圆半径,计算数据总和。

public void draw(Canvas canvas) {
        super.draw(canvas);
        //将画布绘成白色
        canvas.drawColor(Color.WHITE);
        //平移画布,使(0,0)点至中心,便于计算
        canvas.translate(getWidth()/2,getHeight()/2);
        calculate();
        
        if (showAnimator){
            drawByAnim(canvas);    //动画绘制
        }else {
            drawByNormal(canvas);    //普通绘制
        }
    }

2、普通绘制

        这边就是给画笔设置对应颜色,若未设置则随机取色,这边还有个功能就是使该板块有凸起的效果,其实就是采取画布平移(canvas.translate),根据该板块角平分线往外拉一定距离;绘制弧形,使用方法canvas.drawArc(RectF,起始绘制角度,旋转绘制角度,是否包含圆心,画笔);最后将画布恢复到之前保存的状态即为外角平分线平移的状态;将该板块的角度累加到当前角度中去。

/**
     * 非动画绘制
     */
    private void drawByNormal(Canvas canvas){
        //循环绘制各模块
        for (int i=0;i<count;i++){
            if (0 == getData().get(i).getColor()){
                int color = getRandColor();
                mOutPaint.setColor(color);
                getData().get(i).setColor(color);
            }else {
                mOutPaint.setColor(getData().get(i).getColor());
            }
            //计算该板块角度
            float sweepAngle = getData().get(i).getData()/sumData*360f;
            //保存当前画布状态
            canvas.save();
            //当前模块角平分线的sin和cos值
            if (getData().get(i).isRaised()){
                float mathCos = (float) (Math.cos((sweepAngle/2+mCurrentDegree)/180f*Math.PI));
                float mathSin = (float) (Math.sin((sweepAngle/2+mCurrentDegree)/180f*Math.PI));
                //若该板块设置凸起,平移画布,使其与周边各块产生间距,并突出
                canvas.translate(mathCos*mSpace, mathSin*mSpace);
            }
            //绘制外圆
            canvas.drawArc(mRadiusRectF,mCurrentDegree,sweepAngle,true,mOutPaint);
            //恢复平移前的状态
            canvas.restore();

            mCurrentDegree += sweepAngle;
        }

        mAlphaPaint.setAlpha((int) (mAlpha*255));
        canvas.drawCircle(0,0,mRadius*mAlphaRadiusPercent,mAlphaPaint);
        canvas.drawCircle(0,0,mRadius*mHoleRadiusPercent,mHolePaint);

        drawTextInner(canvas);

        if (showCenterText) {
            //绘制中心文字
            Paint.FontMetrics metrics = mCenterTextPaint.getFontMetrics();
            float textHeight = Math.abs(metrics.descent - metrics.ascent);
            canvas.drawText(mCenterText, 0, textHeight / 2 - metrics.descent, mCenterTextPaint);
        }
    }

3、绘制文字

        因setTextAlign(Paint.Align.CENTER)让文字已水平居中,再设置让其垂直也居中即可;根据画笔获取FontMetrics,一开始绘制中文字就在baseline上,我们要让其绘制到ascent和descent中间,因以baseline为基准线,向上为负,所以ascent为负值;现将文字绘制在板块的角平分线上,所以我们高度只需往下平移Math.abs(metrics.descent - metrics.ascent)/2-metrics.descent即可让中文字在中心。

  //获取文字高度,因水平已居中
  String text = getData().get(i).getMsg();
  Paint.FontMetrics metrics = mBlockTextPaint.getFontMetrics();
  float textHeight = Math.abs(metrics.descent - metrics.ascent);
  float textRadius = (1 + mAlphaRadiusPercent) * mRadius / 2;
  //绘制模块文字
  canvas.drawText(text, textRadius * mathCos,
    textRadius * mathSin + textHeight / 2 - metrics.descent, mBlockTextPaint);

4、动画实现

若本次绘制的角度大于该板块的角度,那就继续进入下个循环,绘制下一板块,若本次绘制角度小于该板块角度,那就绘制到valueAngle,跳出循环,等待下一个valueAngle,再重新绘制图形。

for (int i=0;i<count;i++){
    float valueAngle = Math.min(sweepAngle, mAnimatorValue-(mCurrentDegree-mStartDegree));
    //绘制外圆
    //根据动画,本次真实需绘制角度
    canvas.drawArc(mRadiusRectF,mCurrentDegree,valueAngle,true,mOutPaint);
    //若本次绘制角度大于该版块角度,则进入下一板块,否则结束循环,重新绘制
    if (sweepAngle <= valueAngle){
         mCurrentDegree += sweepAngle;
       }else {
          break;
       }
}
animator = ValueAnimator.ofFloat(0, 360f);
            animator.setDuration(3000);
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mAnimatorValue = (float) animation.getAnimatedValue();
                    refresh(); //触发重新绘制
                }
            });
animator.addListener(new Animator.AnimatorListener() {
                ……
                @Override
                public void onAnimationEnd(Animator animation) {
                    showAnimator = false;
                    refresh();    //动画结束,重新绘制,这时动画按钮已关闭,则进入普通绘制,文字也就绘制出来了
                }
                ……
            });

五、源码地址

        该控件我已经封装好,可拿来直接使用,使用方法已在GitHub上进行简单讲解,也可进源码进行解读,还有些可优化改进的地方,大家可以给我提议或自行改进。

     GitHub:https://github.com/ljm17/PieView

        若对你有所帮助,请给我个star吧~

猜你喜欢

转载自blog.csdn.net/qq_40861368/article/details/83716485