Android 开发自定义饼图

hzhdev, 23 六月, 2026

在 Android 中自定义饼图,核心是继承 View 并重写 onDraw() 方法,利用 Canvas.drawArc() 来绘制各个扇形区域。下面从原理到代码,带你完整实现一个自定义饼图。

核心原理

饼图的本质是将一个圆按数据比例分割成多个扇形。关键公式:

info

 
 
 
每个扇形的扫过角度 = (该数据值 / 数据总和) × 360°
绘制工具是 Canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

完整实现代码

1. 创建自定义 View 类

public class CustomPieChart extends View {

    private Paint mPaint;           // 绘制扇形的画笔
    private Paint mTextPaint;       // 绘制文字的画笔
    private List<PieEntry> mData;   // 数据集合
    private float mTotal;           // 数据总和
    private RectF mRectF;           // 饼图边界矩形
    private float mRadius;          // 饼图半径

    public CustomPieChart(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mData = new ArrayList<>();

        // 扇形画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);  // 抗锯齿
        mPaint.setStyle(Paint.Style.FILL);

        // 文字画笔
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(36);
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    // 添加数据
    public void addData(String label, float value, int color) {
        mData.add(new PieEntry(label, value, color));
        mTotal += value;
        invalidate(); // 触发重绘
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 以 View 中心为圆心,取宽高较小值的一半作为半径
        mRadius = Math.min(w, h) / 2f * 0.8f; // 留 20% 边距
        float left = w / 2f - mRadius;
        float top = h / 2f - mRadius;
        float right = w / 2f + mRadius;
        float bottom = h / 2f + mRadius;
        mRectF = new RectF(left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mData.isEmpty()) return;

        float startAngle = -90; // 从顶部(12点钟方向)开始绘制

        for (PieEntry entry : mData) {
            // 计算当前扇形的扫过角度
            float sweepAngle = (entry.value / mTotal) * 360f;

            // 设置颜色并绘制扇形
            mPaint.setColor(entry.color);
            canvas.drawArc(mRectF, startAngle, sweepAngle, true, mPaint);

            // 绘制扇形内的百分比文字
            drawPercentageText(canvas, entry, startAngle, sweepAngle);

            // 更新起始角度
            startAngle += sweepAngle;
        }
    }

    // 在扇形中间绘制百分比
    private void drawPercentageText(Canvas canvas, PieEntry entry, float startAngle, float sweepAngle) {
        // 计算扇形角平分线的角度
        float midAngle = startAngle + sweepAngle / 2f;
        double radian = Math.toRadians(midAngle);

        // 计算文字位置(半径的一半处)
        float textRadius = mRadius * 0.5f;
        float x = getWidth() / 2f + (float) (textRadius * Math.cos(radian));
        float y = getHeight() / 2f + (float) (textRadius * Math.sin(radian));

        // 绘制百分比
        String text = String.format("%.1f%%", entry.value / mTotal * 100);
        canvas.drawText(text, x, y, mTextPaint);
    }

    // 数据实体类
    private static class PieEntry {
        String label;
        float value;
        int color;

        PieEntry(String label, float value, int color) {
            this.label = label;
            this.value = value;
            this.color = color;
        }
    }
}

2. 在布局文件中使用

xml

<com.example.yourpackage.CustomPieChart
    android:id="@+id/pieChart"
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:layout_gravity="center" />

评论