要完成一个饼状图,其实就是将一个360度分成很多份,然后每一份绘制一个扇形,这些扇形加起来正好是一个整圆。

效果:

android绘制扇形 手机扇形统计图制作_android绘制扇形

android绘制扇形 手机扇形统计图制作_ide_02

android中绘制扇形 我们可以用绘制弧形的api

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

也可以用绘制path的api

canvas.drawPath(mPath,mPaint);

path确定路径的时候需要用到 mPath.arcTo(mRectF,startAngle,sweepAngle); 来却定path的路径。

无论是绘制圆弧还是绘制path 我们都需要有一个绘制的区域 这就需要我们定义一个RectF来确定绘制的区域在onSizeChanged()方法中初始化

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTotalWidth = w - getPaddingLeft() - getPaddingRight();
        mTotalHeight = h - getPaddingTop() - getPaddingBottom();

        mRadius = (float) (Math.min(mTotalWidth,mTotalHeight)/2*0.7);

        mRectF.left = -mRadius;
        mRectF.top = -mRadius;
        mRectF.right = mRadius;
        mRectF.bottom = mRadius;

        mRectFTouch.left = -mRadius-16;
        mRectFTouch.top = -mRadius-16;
        mRectFTouch.right = mRadius+16;
        mRectFTouch.bottom = mRadius+16;
    }

mRectFTouch 是当我们手指点击到某一个扇形的时候,我们需要这个扇形突出一点,这时候我们需要将这个矩形的长宽都扩大一点。

然后就是在onDraw()中绘制了

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mDataList==null)
            return;
        canvas.translate(mTotalWidth/2,mTotalHeight/2);
        //绘制饼图的每块区域
        drawPiePath(canvas);
    }

这里先把画布移到屏幕的中间,这样坐标的原点就在屏幕中间了,绘制起来方便一些。

绘制扇形的方法:

/**
     * 绘制饼图的每块区域 和文本
     * @param canvas
     */
    private void drawPiePath(Canvas canvas) {
        //起始地角度
        float startAngle = 0;
        for(int i = 0;i<mDataList.size();i++){
            float sweepAngle = mDataList.get(i).getValue()/mTotalValue*360-1;//每个扇形的角度
            mPath.moveTo(0,0);
            if(position-1==i){
                mPath.arcTo(mRectFTouch,startAngle,sweepAngle);
            }else {
                mPath.arcTo(mRectF,startAngle,sweepAngle);
            }
            mPaint.setColor(mDataList.get(i).getColor());
            canvas.drawPath(mPath,mPaint);
            mPath.reset();
            startAngle += sweepAngle+1;
        }
    }

也可以使用drawArc的方法。

for(int i = 0;i<mDataList.size();i++){
            float sweepAngle = mDataList.get(i).getValue()/mTotalValue*360-1;//每个扇形的角度
            if(position-1==i){
               canvas.drawArc(mRectFTouch,startAngle,sweepAngle,true,mPaint);
            }else {
                canvas.drawArc(mRectF,startAngle,sweepAngle,true,mPaint);
            }
            mPaint.setColor(mDataList.get(i).getColor());
            canvas.drawArc(mRectF,startAngle,sweepAngle,true,mPaint);
            }

效果:

android绘制扇形 手机扇形统计图制作_Math_03


正常情况下我们根据传入的数据计算出其在360度中所占的度数后绘制出来的应该是每个扇形无缝连接的,有时候我们感觉上图中的每个扇形间有个小间距会更好看一点,要做到这点很简单,绘制的时候每个扇形的度数减去一度就好了。

然后就是将每个扇形所占的百分比数字绘制上去。我们可以将其绘制在每个扇形的中间,但是因为现实中可能有的扇形很大有的扇形很小,当一个扇形很小的时候在其上面写字有可能就覆盖到别的扇形了,假如连着好几个都很小呢,往上面写字更会覆盖到一起。体验无疑会很差。

所以这里从每个扇形中指出一条小直线,在外面绘制我们的百分比数字。

要绘制直线就得确定每个直线的起始点和终止点的坐标。

float pxs = (float) (mRadius*Math.cos(Math.toRadians(startAngle+sweepAngle/2)));
            float pys = (float) (mRadius*Math.sin(Math.toRadians(startAngle+sweepAngle/2)));
            float pxt = (float) ((mRadius+30)*Math.cos(Math.toRadians(startAngle+sweepAngle/2)));
            float pyt = (float) ((mRadius+30)*Math.sin(Math.toRadians(startAngle+sweepAngle/2)));
            canvas.drawLine(pxs,pys,pxt,pyt,mLinePaint);

Math.toRadians() 是将角度转换为弧度
1弧度=180/π度
1度=π/180弧度
弧度和角度的转换可以参考:弧度和角度的转换 然后是绘制文本:

//提供精确的小数位四舍五入处理。
            double resToRound = CalculateUtil.round(res,2);
            float v = startAngle % 360;
            if (startAngle % 360.0 >= 90.0 && startAngle % 360.0 <= 270.0) {
                canvas.drawText(resToRound+"%",pxt-mTextPaint.measureText(resToRound+"%"),pyt,mTextPaint);
            }else {
                canvas.drawText(resToRound+"%",pxt,pyt,mTextPaint);
            }

这里我们绘制文字的时候需要注意下坐标轴的象限,再一和四象限,我们直接在直线的终点处开始绘制就好了,但是在二和三象限中如果这么绘制的话,文字就绘制到图像上面来了。所以我们需要将绘制的起点往左移动text的大小的距离在绘制。

然后就算绘制完成了:

android绘制扇形 手机扇形统计图制作_ide_04

有时候我们需要点击某个部分来查看此部分的详情。所以需要对其设置点击事件,点击事件无非就是写个接口给外面调用就好了,关键是我们怎么确定我们所点击的地方是哪个扇形。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                float x = event.getX()-(mTotalWidth/2);
                float y = event.getY()-(mTotalHeight/2);
                //计算出角度
                float touchAngle = (float) Math.toDegrees(Math.atan2(x,y));
                if(x>0&&y>0){
                    touchAngle =90 - touchAngle;
                }else if(x<0&&y>0){
                    touchAngle = 90 - touchAngle;
                }else if(x<0&&y<0){
                    touchAngle = 90 - touchAngle;
                }else if(x>0&&y<0){
                    touchAngle +=180;
                }
                float touchRadius = (float) Math.sqrt(y * y + x * x);
                if (touchRadius< mOutRadius){
                    if(angles!=null)
                        position = getClickPosition(touchAngle);
                    if(lastClickedPosition == position){
                        lastPositionClicked = !lastPositionClicked;
                    }else {
                        lastPositionClicked = true;
                        lastClickedPosition = position;
                    }
                    invalidate();
                    if(mOnItemPieClickListener!=null){
                        mOnItemPieClickListener.onClick(position);
                    }
                }
                break;
                default:
        }
        return super.onTouchEvent(event);
    }

    private int getClickPosition(float touchAngle) {
        int position = 0;
        int totalAngle = 0;
        for (int i = 0; i < angles.length; i++) {
            totalAngle += angles[i];
            if (touchAngle <= totalAngle) {
                position = i;
                break;
            }
        }
        return position;
    }

在绘制扇形的地方将每个扇形的起绘制度保存到一个数组中,
通过Math.toDegrees(Math.atan2(x,y))将我们点击的地方转换成角度,
假设总角度为360度,把不同象限通过Math.toDegrees转换之后的结果进行调整之后成为最后的点击角度。
最后遍历之前存的每个扇形的角度,相加,当点击角度小于相加的角度的时候,此时的位置就位点击的位置

源码地址:https://github.com/chsmy/EasyChartWidget