Android自定義控件:餅狀圖

效果圖

效果圖

效果圖

</div>

實(shí)現(xiàn)原理分析

  • 每段弧線(xiàn)的繪制:根據(jù)每個(gè)數(shù)據(jù)所占總數(shù)的百分比得出該弧線(xiàn)的度數(shù)(一共360度),然后使用canvas.drawArc繪制即可。至于弧線(xiàn)的顏色,我們可以隨機(jī)生成。
  • 弧線(xiàn)中數(shù)據(jù)的繪制:只要能獲取到每段弧線(xiàn)的中心點(diǎn)在view中的坐標(biāo),我們就能在弧線(xiàn)中心坐標(biāo)處繪制數(shù)據(jù)。那么如何獲取呢?答案是三角函數(shù)(沒(méi)學(xué)好的,百度去百度鏈接)。每段弧線(xiàn)的中心點(diǎn)與Y軸的夾角我們是知道的(根據(jù)上一步可以算出),然后斜邊是圓弧的半徑,已知夾角和斜邊,使用cos和sin,每段弧線(xiàn)的中心點(diǎn)與圓心的距離就能算出來(lái),那么弧線(xiàn)中心點(diǎn)在整個(gè)view的坐標(biāo)就得到了,繪制數(shù)據(jù)就是輕輕松松的了。

代碼中有詳細(xì)的注釋?zhuān)唧w代碼下面貼出。

項(xiàng)目地址

完整的代碼和項(xiàng)目大家可以到我的github中查看,里面有相關(guān)的使用方法,同時(shí)這個(gè)項(xiàng)目上傳到了maven倉(cāng)庫(kù),可以通過(guò)gradle直接使用

compile 'com.zhijieeeeee:pieviewlibrary:2.0.0'

github地址:https://github.com/zhijieeeeee/PieView

實(shí)現(xiàn)代碼

public class PieView2 extends View {

    /**
     * 使用wrap_content時(shí)默認(rèn)的尺寸
     */
    private static final int DEFAULT_WIDTH = 800;
    private static final int DEFAULT_HEIGHT = 800;

    /**
     * 中心坐標(biāo)
     */
    private int centerX;
    private int centerY;

    /**
     * 半徑
     */
    private float radius;

    /**
     * 弧形外接矩形
     */
    private RectF rectF;

    /**
     * 中間文本的大小
     */
    private Rect centerTextBound = new Rect();

    /**
     * 數(shù)據(jù)文本的大小
     */
    private Rect dataTextBound = new Rect();

    /**
     * 扇形畫(huà)筆
     */
    private Paint mArcPaint;

    /**
     * 中心文本畫(huà)筆
     */
    private Paint centerTextPaint;

    /**
     * 數(shù)據(jù)畫(huà)筆
     */
    private Paint dataPaint;

    /**
     * 數(shù)據(jù)源數(shù)字?jǐn)?shù)組
     */
    private int[] numbers;

    /**
     * 數(shù)據(jù)源名稱(chēng)數(shù)組
     */
    private String[] names;

    /**
     * 數(shù)據(jù)源總和
     */
    private int sum;

    /**
     * 顏色數(shù)組
     */
    private int[] colors;

    private Random random = new Random();

    //自定義屬性 Start

    /**
     * 中間字體大小
     */
    private float centerTextSize = 80;

    /**
     * 數(shù)據(jù)字體大小
     */
    private float dataTextSize = 30;

    /**
     * 中間字體顏色
     */
    private int centerTextColor = Color.BLACK;

    /**
     * 數(shù)據(jù)字體顏色
     */
    private int dataTextColor = Color.BLACK;

    /**
     * 圓圈的寬度
     */
    private float circleWidth = 100;

    //自定義屬性 End

    public PieView2(Context context) {
        super(context);
        init();
    }

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

    public PieView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PieView);
        centerTextSize = typedArray.getDimension(R.styleable.PieView_centerTextSize, centerTextSize);
        dataTextSize = typedArray.getDimension(R.styleable.PieView_dataTextSize, dataTextSize);
        circleWidth = typedArray.getDimension(R.styleable.PieView_circleWidth, circleWidth);
        centerTextColor = typedArray.getColor(R.styleable.PieView_centerTextColor, centerTextColor);
        dataTextColor = typedArray.getColor(R.styleable.PieView_dataTextColor, dataTextColor);
        typedArray.recycle();
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        mArcPaint = new Paint();
        mArcPaint.setStrokeWidth(circleWidth);
        mArcPaint.setAntiAlias(true);
        mArcPaint.setStyle(Paint.Style.STROKE);

        centerTextPaint = new Paint();
        centerTextPaint.setTextSize(centerTextSize);
        centerTextPaint.setAntiAlias(true);
        centerTextPaint.setColor(centerTextColor);

        dataPaint = new Paint();
        dataPaint.setStrokeWidth(2);
        dataPaint.setTextSize(dataTextSize);
        dataPaint.setAntiAlias(true);
        dataPaint.setColor(dataTextColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measureWidthSize = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeightSize = MeasureSpec.getSize(heightMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (measureWidthMode == MeasureSpec.AT_MOST
                && measureHeightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        } else if (measureWidthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(DEFAULT_WIDTH, measureHeightSize);
        } else if (measureHeightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(measureWidthSize, DEFAULT_HEIGHT);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = getMeasuredWidth() / 2;
        centerY = getMeasuredHeight() / 2;
        //設(shè)置半徑為寬高最小值的1/4
        radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 4;
        //設(shè)置扇形外接矩形
        rectF = new RectF(centerX - radius,
                centerY - radius,
                centerX + radius,
                centerY + radius);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        calculateAndDraw(canvas);
    }

    /**
     * 計(jì)算比例并且繪制扇形和數(shù)據(jù)
     */
    private void calculateAndDraw(Canvas canvas) {
        if (numbers == null || numbers.length == 0) {
            return;
        }
        //扇形開(kāi)始度數(shù)
        int startAngle = 0;
        //所占百分比
        float percent;
        //所占度數(shù)
        float angle;
        for (int i = 0; i < numbers.length; i++) {
            percent = numbers[i] / (float) sum;
            //獲取百分比在360中所占度數(shù)
            if (i == numbers.length - 1) {//保證所有度數(shù)加起來(lái)等于360
                angle = 360 - startAngle;
            } else {
                angle = (float) Math.ceil(percent * 360);
            }
            //繪制第i段扇形
            drawArc(canvas, startAngle, angle, colors[i]);
            startAngle += angle;

            //繪制數(shù)據(jù)
            if (numbers[i] <= 0) {
                continue;
            }
            //當(dāng)前弧線(xiàn)中心點(diǎn)相對(duì)于縱軸的夾角度數(shù),由于扇形的繪制是從三點(diǎn)鐘方向開(kāi)始,所以加90
            float arcCenterDegree = 90 + startAngle - angle / 2;
            drawData(canvas, arcCenterDegree, i, percent);
        }
        //繪制中心數(shù)字總和
        canvas.drawText(sum + "", centerX - centerTextBound.width() / 2, centerY + centerTextBound.height() / 2, centerTextPaint);
    }

    /**
     * 計(jì)算每段弧度的中心坐標(biāo)
     *
     * @param degree 當(dāng)前扇形中心度數(shù)
     */
    private float[] calculatePosition(float degree) {
        //由于Math.sin(double a)中參數(shù)a不是度數(shù)而是弧度,所以需要將度數(shù)轉(zhuǎn)化為弧度
        //而Math.toRadians(degree)的作用就是將度數(shù)轉(zhuǎn)化為弧度
        //sin 一二正,三四負(fù) sin(180-a)=sin(a)
        //扇形弧線(xiàn)中心點(diǎn)距離圓心的x坐標(biāo)
        float x = (float) (Math.sin(Math.toRadians(degree)) * radius);
        //cos 一四正,二三負(fù)
        //扇形弧線(xiàn)中心點(diǎn)距離圓心的y坐標(biāo)
        float y = (float) (Math.cos(Math.toRadians(degree)) * radius);

        //每段弧度的中心坐標(biāo)(扇形弧線(xiàn)中心點(diǎn)相對(duì)于view的坐標(biāo))
        float startX = centerX + x;
        float startY = centerY - y;

        float[] position = new float[2];
        position[0] = startX;
        position[1] = startY;
        return position;
    }

    /**
     * 繪制數(shù)據(jù)
     *
     * @param canvas  畫(huà)布
     * @param degree  第i段弧線(xiàn)中心點(diǎn)相對(duì)于縱軸的夾角度數(shù)
     * @param i       第i段弧線(xiàn)
     * @param percent 數(shù)據(jù)百分比
     */
    private void drawData(Canvas canvas, float degree, int i, float percent) {
        //弧度中心坐標(biāo)
        float startX = calculatePosition(degree)[0];
        float startY = calculatePosition(degree)[1];

        //獲取名稱(chēng)文本大小
        dataPaint.getTextBounds(names[i], 0, names[i].length(), dataTextBound);
        //繪制名稱(chēng)數(shù)據(jù),20為縱坐標(biāo)偏移量
        canvas.drawText(names[i],
                startX - dataTextBound.width() / 2,
                startY + dataTextBound.height() / 2 - 20,
                dataPaint);


        //拼接百分比并獲取文本大小
        DecimalFormat df = new DecimalFormat("0.0");
        String percentString = df.format(percent * 100) + "%";
        dataPaint.getTextBounds(percentString, 0, percentString.length(), dataTextBound);

        //繪制百分比數(shù)據(jù),20為縱坐標(biāo)偏移量
        canvas.drawText(percentString,
                startX - dataTextBound.width() / 2,
                startY + dataTextBound.height() * 2 - 20,
                dataPaint);
    }

    /**
     * 繪制扇形
     *
     * @param canvas     畫(huà)布
     * @param startAngle 開(kāi)始度數(shù)
     * @param angle      扇形的度數(shù)
     * @param color      顏色
     */
    private void drawArc(Canvas canvas, float startAngle, float angle, int color) {
        mArcPaint.setColor(color);
        //-0.5和+0.5是為了讓每個(gè)扇形之間沒(méi)有間隙
        canvas.drawArc(rectF, startAngle - 0.5f, angle + 0.5f, false, mArcPaint);
    }

    /**
     * 生成隨機(jī)顏色
     */
    private int randomColor() {
        int red = random.nextInt(256);
        int green = random.nextInt(256);
        int blue = random.nextInt(256);
        return Color.rgb(red, green, blue);
    }

    /**
     * 設(shè)置數(shù)據(jù)
     *
     * @param numbers 數(shù)字?jǐn)?shù)組
     * @param names   名稱(chēng)數(shù)組
     */
    public void setData(int[] numbers, String[] names) {
        if (numbers == null || numbers.length == 0 || names == null || names.length == 0) {
            return;
        }
        if (numbers.length != names.length) {
            //名稱(chēng)個(gè)數(shù)與數(shù)字個(gè)數(shù)不相等
            return;
        }
        this.numbers = numbers;
        this.names = names;
        colors = new int[numbers.length];
        for (int i = 0; i < this.numbers.length; i++) {
            //計(jì)算總和
            sum += numbers[i];
            //隨機(jī)顏色
            colors[i] = randomColor();
        }
        //計(jì)算總和數(shù)字的寬高
        centerTextPaint.getTextBounds(sum + "", 0, (sum + "").length(), centerTextBound);
        invalidate();
    }
}

自定義屬性

<declare-styleable name="PieView">
    <attr name="centerTextSize" format="dimension" />
    <attr name="dataTextSize" format="dimension" />
    <attr name="centerTextColor" format="color" />
    <attr name="dataTextColor" format="color" />
    <attr name="circleWidth" format="dimension" />
</declare-styleable>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容