安卓自定義View之帶動畫的餅狀圖

安卓自定義View之帶動畫的餅狀圖

因爲是金融類的app,包含有很多資產數據,需求是使用餅狀圖展示總資產的不同資產組成,不同資產間有白色間隔線,較大的資產會有放大的動畫效果,整體進行動畫繪製展示,效果如下:
這裏以兩個組成爲例展示
整體代碼如下(全網唯一):

package com.xx.xx.ui.view.widget;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

import com.xx.xx.utils.JRDimensionUtil;

/**
 * @ClassName: PieChart
 * @Description: 自定義的總資產扇形圖
 * @Author yanxu5
 * @Date: 2019/5/26
 */
public class PieChart extends View {
    private float[] item;// 每一項的值
    private float total;// 總共的值
    private String[] colors;// 傳過來的顏色
    private float[] itemsAngle;// 每一項所佔的角度
    private float[] itemsBeginAngle;// 每一項的起始角度
    private float mRadius;// 半徑
    private float animatedValue;// AnimatedValue
    private static final String[] DEFAULT_ITEMS_COLORS = {"#FF8200", "#EF4352"};// 默認顏色
    private float mStartX, mStartY, mCenterXY;// 起始值
    private Paint mPaint, mLinePaint;// paint
    private RectF mOval;// RectF
    private int mTagCount;// 大於0的個數

    public PieChart(Context context) {
        this(context, null);
    }

    public PieChart(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PieChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPieChart();
    }

    /**
     * 初始化
     */
    private void initPieChart() {
        mRadius = JRDimensionUtil.dp2px(getContext(), 110);
        mStartX = JRDimensionUtil.dp2px(getContext(), 110);
        mStartY = JRDimensionUtil.dp2px(getContext(), 110);
        mCenterXY = mRadius;
        float addRadius = JRDimensionUtil.dp2px(getContext(), 5);
        float leftTop = 0;
        float rightBottom = 2 * mRadius;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStrokeWidth(JRDimensionUtil.dp2px(getContext(), 2));// 設置線寬
        mLinePaint.setColor(Color.parseColor("#FFFFFF"));
        mOval = new RectF(leftTop + addRadius, leftTop + addRadius, rightBottom - addRadius, rightBottom - addRadius);
        mOvalMax = new RectF(leftTop, leftTop, rightBottom, rightBottom); // 關鍵思路:逆向思維,實際上先定義的大扇形矩形,再反推的小扇形矩形
    }

    /**
     * 設置每一項的值
     *
     * @param item item
     */
    public void setItem(float[] item) {
        if (item != null && item.length > 0) {
            calculateTotal();
            refreshItemsAngles();
            colors = DEFAULT_ITEMS_COLORS;
        }
    }

    /**
     * 初始化所有
     *
     * @param item   item
     * @param colors 顏色
     */
    public void initSrc(float[] item, String[] colors) {
        this.item = item;
        mTagCount = 0;// 每次初始化重置個數
        for (float anItem : item) {
            if (anItem > 0) mTagCount++;
        }
        calculateTotal();
        if (total > 0) {
            setItem(item);
            setColors(colors);
            notifyDraw();
        } else if (total == 0) {
            for (int i = 0; i < item.length; i++) {
                item[i] = 1;// 當爲0的時候將不會畫圓
            }
            initSrc(item, colors);
        }
    }

    /**
     * 設置每一項的顏色
     *
     * @param colors 顏色
     */
    public void setColors(String[] colors) {
        if (colors != null && colors.length > 0) {
            this.colors = colors;
        }
    }

    /**
     * onDraw方法
     *
     * @param canvas canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (item == null || item.length == 0) {// 如無數據
            // 畫外圓
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(Color.parseColor("#F5F5F5"));
            canvas.drawCircle(mCenterXY, mCenterXY, JRDimensionUtil.dp2px(getContext(), 105), mPaint);
            // 畫內圓
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(Color.parseColor("#FFFFFF"));
            canvas.drawCircle(mCenterXY, mCenterXY, JRDimensionUtil.dp2px(getContext(), 85), mPaint);
        } else {
            // 畫扇形
            for (int i = 0; i < item.length; i++) {
                mPaint.setColor(Color.parseColor(colors[i]));
                if (Math.min(itemsAngle[i], animatedValue - 90 - itemsBeginAngle[i]) >= 0) {
                    canvas.drawArc(mOval, itemsBeginAngle[i], Math.min(itemsAngle[i], animatedValue - 90 - itemsBeginAngle[i]), true, mPaint);
                }
            }
            // 畫變大的扇形 //TODO 佔比較大模塊放大的效果暫時取消
            /*if (animatedValue == 360 && !isEquality(item)) {
                int i = compareNumber(item);
                mPaint.setColor(Color.parseColor(colors[i]));
                canvas.drawArc(mOvalMax, itemsBeginAngle[i], Math.min(itemsAngle[i], animatedValue - 90 - itemsBeginAngle[i]), true, mPaint);
            }*/
            // 畫間隔白線
            for (int i = 0; i < item.length; i++) {
                if (item.length > 1 && mTagCount > 1)
                    canvas.drawLine(mStartX, mStartY, mStartX + (float) Math.sin(Math.toRadians(90 + itemsBeginAngle[i])) * mStartX, mStartY - (float) Math.cos(Math.toRadians(90 + itemsBeginAngle[i])) * mStartY, mLinePaint);
            }
            // 畫內圓
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(Color.parseColor("#FFFFFF"));
            canvas.drawCircle(mCenterXY, mCenterXY, JRDimensionUtil.dp2px(getContext(), 85), mPaint);
        }
    }

    /**
     * 開始繪製
     */
    public void startDraw() {
        if (item != null && item.length > 0 && colors != null && colors.length > 0) {
            initAnimator();
        }
    }

    /**
     * 初始化動畫
     */
    private void initAnimator() {
        ValueAnimator anim = ValueAnimator.ofFloat(0, 360);
        anim.setDuration(1500);
        anim.setInterpolator(new LinearInterpolator());
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                animatedValue = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        anim.start();
    }

    /**
     * 計算總數
     */
    private void calculateTotal() {
        total = 0;
        for (float i : item) {
            total += i;
        }
    }

    /**
     * 根據每個item的大小,獲得item所佔的角度和起始角度
     */
    private void refreshItemsAngles() {
        if (item != null && item.length > 0) {
            float[] itemsRate = new float[item.length];// 每一塊佔的比例
            itemsBeginAngle = new float[item.length];// 每一個角度臨界點
            itemsAngle = new float[item.length];// 每一個角度臨界點
            float beginAngle = -90;// 初始角度-90度
            for (int i = 0; i < item.length; i++) {
                itemsRate[i] = (float) (item[i] * 1.0 / total * 1.0);
            }
            for (int i = 0; i < itemsRate.length; i++) {
                if (i >= 1) {
                    beginAngle = 360 * itemsRate[i - 1] + beginAngle;
                }
                itemsBeginAngle[i] = beginAngle;
                itemsAngle[i] = 360 * itemsRate[i];
            }
        }
    }

    /**
     * 通知開始繪畫
     */
    public void notifyDraw() {
        invalidate();
    }

    /**
     * 控件可獲得的空間
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        float widthHeight = 2 * (mRadius);
        setMeasuredDimension((int) widthHeight, (int) widthHeight);
    }

    /**
     * 判斷各組成是否相等
     *
     * @param item 資產組成
     * @return 是否相等
     */
    private boolean isEquality(float[] item) {
        for (int i = 1; i < item.length; i++) {
            if (item[i] != item[0]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 返回最大值的索引
     *
     * @param item 資產組成
     * @return 最大的資產的角標
     */
    private int compareNumber(float[] item) {
        float max = item[0];
        int maxPosition = 0;
        for (int i = 1; i < item.length; i++) {
            if (item[i] > max) {
                max = item[i];
                maxPosition = i;
            }
        }
        return maxPosition;
    }

}

調用方法也很簡單

mPieChartView.initSrc(ratios, colors);
mPieChartView.startDraw();
以上爲自定義帶動畫的餅狀圖的全部代碼,如有其他疑問可以留言,會及時進行回覆
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章