Android自定义View——饼图

前段时间项目里面需要用到统计图来分析数据,里面有一张饼图,博主也是第一次画饼图,一开始想偷点懒在网上找了一些资料,

结果并不是很满意,只好自己根据需求设计一张饼图了。

先看看效果图:


接下来是实现原理,这里代码里面的注释比较详尽了,就不做过多说明了。

public class PieChartView extends View {

    private Context ctx;
    private DecimalFormat format;
    private List<PieChartData> mList;

    // 画笔
    private Paint arcPaint;
    private Paint linePaint;
    private Paint textPaint;

    // 圆心
    private float centerX;
    private float centerY;
    // 大圆半径
    private float radius;
    // 小圆半径
    private float radius_cover;
    private float total;
    // 初始角度
    private float startAngle;
    private float textAngle;

    private List<PointF[]> lineList;
    private List<PointF> textList;

    public PieChartView(Context context) {
        this(context, null);
    }

    public PieChartView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PieChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        ctx = context;
        lineList = new ArrayList<>();
        textList = new ArrayList<>();
        mList = new ArrayList<>();
        // 取整
        format = new DecimalFormat("##0");

        // 扇形画笔
        arcPaint = new Paint();
        // 抗锯齿
        arcPaint.setAntiAlias(true);
        // 防抖动
        arcPaint.setDither(true);
        // 设置画笔风格,实心
        arcPaint.setStyle(Paint.Style.FILL);

        // 间隔线画笔
        linePaint = new Paint();
        linePaint.setAntiAlias(true);
        linePaint.setDither(true);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(DisplayUtil.dip2px(ctx, 2));

        // 文本画笔
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setDither(true);
        textPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        // 取屏幕宽度与屏幕高度的较小值
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.EXACTLY) {
            height = heightSpecSize;
            width = Math.min(heightSpecSize, Math.min(DisplayUtil.getScreenSize(ctx)[0],
                    DisplayUtil.getScreenSize(ctx)[1]));
        } else if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.AT_MOST) {
            width = widthSpecSize;
            height = Math.min(widthSpecSize, Math.min(DisplayUtil.getScreenSize(ctx)[0],
                    DisplayUtil.getScreenSize(ctx)[1]));
        } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            width = height = Math.min(DisplayUtil.getScreenSize(ctx)[0],
                    DisplayUtil.getScreenSize(ctx)[1]);
        } else {
            width = widthSpecSize;
            height = heightSpecSize;
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        textList.clear();
        lineList.clear();
        lineList = new ArrayList<>();
        textList = new ArrayList<>();

        if (mList != null) {
            // 初始化缩放系数
            float k = 1.0f;
            // 开始画各个扇形
            for (int i = 0; i < mList.size(); i++) {
                // 限定饼图所在矩形大小
                float left = centerX - radius * k;  // 矩形左边的X坐标
                float top = centerY - radius * k;  // 矩形顶部的Y坐标
                float right = centerX + radius * k;  // 矩形右边的X坐标
                float bottom = centerY + radius * k;  // 矩形底部的Y坐标
                RectF mRectF = new RectF(left, top, right, bottom);

                // 设置扇形颜色
                arcPaint.setColor(mList.get(i).color);
                // 绘制扇形,参数:画布大小,起始角度,扇形角度,是否描边,画笔属性
                canvas.drawArc(mRectF, startAngle, mList.get(i).percent / total * 360f, true, arcPaint);

                // 记录间隔线位置
                lineList.add(getLinePointF());

                // 记录文本位置
                textAngle = startAngle + mList.get(i).percent / total * 360f / 2;
                textList.add(getTextPointF(k));

                //重新计算起始角度
                startAngle += mList.get(i).percent / total * 360f;
                // 缩小扇形半径
                k -= 0.2f;
            }
            // 中心空白:绘制圆形遮盖,颜色设置与背景色一致
            arcPaint.setColor(ContextCompat.getColor(getContext(), R.color.color2));
            canvas.drawCircle(centerX, centerY, radius_cover, arcPaint);
            //绘制间隔线
            drawLine(canvas, lineList);
            //绘制文字
            drawText(canvas);
        }

    }

    /**
     * 获取间隔线位置 [开始坐标,结束坐标]
     */
    private PointF[] getLinePointF() {
        // 从圆心开始
        float startX = centerX;
        float startY = centerY;
        // 到扇形边界结束
        float stopX = (float) (centerX + (radius + arcPaint.getStrokeWidth())
                * Math.cos(Math.toRadians(startAngle)));
        float stopY = (float) (centerY + (radius + arcPaint.getStrokeWidth())
                * Math.sin(Math.toRadians(startAngle)));
        PointF startPoint = new PointF(startX, startY);
        PointF stopPoint = new PointF(stopX, stopY);
        return new PointF[]{startPoint, stopPoint};
    }

    /**
     * 获取文本位置
     */
    private PointF getTextPointF(float k) {
        float textPointX = (float) (centerX + 0.6 * k * radius * Math.cos(Math.toRadians(textAngle)));
        float textPointY = (float) (centerY + 0.6 * k * radius * Math.sin(Math.toRadians(textAngle)));
        return new PointF(textPointX, textPointY);
    }

    /**
     * 绘制间隔线
     */
    private void drawLine(Canvas canvas, List<PointF[]> pointFs) {
        for (PointF[] fp : pointFs) {
            canvas.drawLine(fp[0].x, fp[0].y, fp[1].x, fp[1].y, linePaint);
        }
    }

    /**
     * 绘制文本
     */
    private void drawText(Canvas canvas) {
        for (int i = 0; i < textList.size(); i++) {
            textPaint.setTextAlign(Paint.Align.CENTER);
            // 字号
            textPaint.setTextSize(radius / 7);
            // 字体
            textPaint.setTypeface(Typeface.MONOSPACE);
            // 显示百分比
            canvas.drawText(format.format(mList.get(i).percent * 100 / total) + "%",
                    textList.get(i).x - 10, textList.get(i).y - 10, textPaint);
        }
    }

    /**
     * 设置扇形和遮盖圆半径
     */
    public void setRadius(int radius, int radius_cover) {
        this.radius = radius;
        this.radius_cover = DisplayUtil.dip2px(ctx, radius_cover);
    }

    /**
     * 设置间隔线的颜色
     */
    public void setLineColor(int color) {
        linePaint.setColor(color);
    }

    /**
     * 设置文本颜色
     */
    public void setTextColor(int color) {
        textPaint.setColor(color);
    }

    /**
     * 设置开始角度
     */
    public void setStartAngle(float startAngle) {
        this.startAngle = startAngle;
    }

    /**
     * 设置饼的数据
     */
    public void setPieChartData(List<PieChartData> mList) {
        total = 0;
        if (mList == null) {
            return;
        }
        for (int i = 0; i < mList.size(); i++) {
            total += mList.get(i).percent;
        }
        this.mList.clear();
        this.mList = mList;
        invalidate();
    }

}

这里还用到了一个 DisplayUtil 工具类:

public class DisplayUtil {

    /**
     * dp转px
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 获取屏幕宽度和长度
     */
    public static int[] getScreenSize(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return new int[]{outMetrics.widthPixels, outMetrics.heightPixels};
    }

}

最后附上源码

  • 32
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值