手记

自定义View合辑(2)-饼状图

为了加强对自定义 View 的认知以及开发能力,我计划这段时间陆续来完成几个难度从易到难的自定义 View,并简单的写几篇博客来进行介绍,所有的代码也都会开源,也希望读者能给个 star 哈
GitHub 地址:https://github.com/leavesC/CustomView
也可以下载 Apk 来体验下:https://www.pgyer.com/CustomView

先看下效果图:

一、抽象概念

假设每个扇形所代表的数据的数据都是 float 类型的,这些数据需要由外部传入给 View,View 内部再来根据数据总量来计算各项数据的占比,各个扇形的角度就是以此来决定

为了简单起见,各个扇形的颜色值由 View 内部来决定,外部只需传入数据大小即可,将此概念抽象为 PercentageModel

/**
 * 作者:leavesC
 * 时间:2019/4/10 14:28
 * 描述:
 */
public class PercentageModel {

    private float value;

    private float angle;

    private int color;

}

二、确定宽高、初始化画笔

    public PercentageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        paint.setDither(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int defaultSize = dp2px(DEFAULT_SIZE);
        int width = getSize(widthMeasureSpec, defaultSize);
        int height = getSize(heightMeasureSpec, defaultSize);
        width = height = Math.min(width, height);
        setMeasuredDimension(width, height);
        Log.e(TAG, "onMeasure");
    }

三、传入数据源

外部传入的数据只包含数据量 value 这个参数而已,因此还需要在 View 内部计算数据占比,并为数据项按照顺序赋予颜色值。为了避免精度损失,还需要在最后判断占比总和是否就是 360 度,不是的话则需要将损失值赋予最后一项数据


    private List<PercentageModel> percentageModelList;

    private static final int[] COLORS = {0xff2f7e76, 0xff1ff749, 0xfff42872, 0xff4643f4, 0xe51581da, 0xff8527e4, 0xfff1b00d, 0xff26020f};

    public void setData(List<PercentageModel> percentageModelList) {
        this.percentageModelList = percentageModelList;
        initData(percentageModelList);
        invalidate();
    }

    private void initData(List<PercentageModel> percentageModelList) {
        if (percentageModelList == null || percentageModelList.size() == 0) {
            return;
        }
        float sumValue = 0;
        for (int i = 0; i < percentageModelList.size(); i++) {
            PercentageModel percentageModel = percentageModelList.get(i);
            sumValue += percentageModel.getValue();
            percentageModel.setColor(COLORS[i % COLORS.length]);
        }
        float sumAngle = 0;
        for (PercentageModel percentageModel : percentageModelList) {
            float per = percentageModel.getValue() / sumValue;
            percentageModel.setAngle(per * 360);
            sumAngle += percentageModel.getAngle();
        }
        //计算百分比时可能有一些精度损失,此处需要判断是否需要把差值补回来
        if (sumAngle < 360) {
            for (PercentageModel percentageModel : percentageModelList) {
                if (percentageModel.getAngle() != 0) {
                    percentageModel.setAngle(360 - sumAngle + percentageModel.getAngle());
                    break;
                }
            }
        }
    }

四、绘制

    private RectF rect = new RectF();

    @Override
    protected void onDraw(Canvas canvas) {
        if (percentageModelList == null || percentageModelList.size() == 0) {
            return;
        }
        float currentStartAngle = startAngle;
        canvas.translate(getWidth() / 2, getHeight() / 2);
        float r = (float) (Math.min(getWidth(), getHeight()) / 2 * 0.95);
        rect.left = -r;
        rect.top = -r;
        rect.right = r;
        rect.bottom = r;
        for (PercentageModel percentageModel : percentageModelList) {
            paint.setColor(percentageModel.getColor());
            canvas.drawArc(rect, currentStartAngle, percentageModel.getAngle(), true, paint);
            currentStartAngle += percentageModel.getAngle();
        }
    }

通过全局变量 startAngle 来指定第一个扇形的起始角度,并将其 set 方法开放给外部,并在 set 方法内主动刷新 View

    private float startAngle;

    public void setStartAngle(float startAngle) {
        this.startAngle = startAngle;
        invalidate();
    }
0人推荐
随时随地看视频
慕课网APP