自定義View合輯(2)-餅狀圖

leavesC發表於2019-05-04

為了加強對自定義 View 的認知以及開發能力,我計劃這段時間陸續來完成幾個難度從易到難的自定義 View,並簡單的寫幾篇部落格來進行介紹,所有的程式碼也都會開源,也希望讀者能給個 star 哈 GitHub 地址:github.com/leavesC/Cus… 也可以下載 Apk 來體驗下:www.pgyer.com/CustomView

先看下效果圖:

自定義View合輯(2)-餅狀圖

一、抽象概念

假設每個扇形所代表的資料的資料都是 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();
    }
複製程式碼

相關文章