安卓自定义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();

demo已经上传至github,需要的小伙伴可以看一下–>传送门

以上为自定义带动画的饼状图的全部代码,如有其他疑问可以留言,会及时进行回复
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值