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

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

先看下效果图:git

1、抽象概念

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

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

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

    private float value;

    private float angle;

    private int color;

}
复制代码

2、肯定宽高、初始化画笔

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");
    }
复制代码

3、传入数据源

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

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;
                }
            }
        }
    }
复制代码

4、绘制

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 方法内主动刷新 Viewthis

private float startAngle;

    public void setStartAngle(float startAngle) {
        this.startAngle = startAngle;
        invalidate();
    }
复制代码
相关文章
相关标签/搜索