Android-可旋转、平移的饼状图(PieChartView)

前言

这次的饼图和之前写过的都不太一样。主要是饼图的旋转是通过Button去触发,被选中的某块需要平移出来。

好了,先看一下效果图

旋转的卡顿是模拟机的原因,真机是没问题的♪(∇*)

自定义View

public class AnimatePieChartView extends View {

    public static final String TAG = "ez";
    //默认起始的旋转角度
    private final float DEFAULT_START_ANGLE = 180;
    //某块饼图平移的距离
    public static final int TRANS_DIS = -20;
    private Paint mPaintOuter;
    private Paint mPaintCenter;
    private Paint mPaintShadow;

    private int mPaddingTop;
    private int mPaddingBottom;
    private int mPaddingStart;
    private int mPaddingEnd;

    //圆心
    private int mCenterX;
    private int mCenterY;

    //开始角度
    private float mStartAngle;
    private float mMaxValue;
    //文字大小
    private float mTextSize;
    //阴影大小
    private float mShaderSize;
    //当前颜色
    private int mCurrentColor;
    //当前选中的index
    private int mIndex;
    //颜色
    private List<Integer> mPieColorList;
    //颜色占比
    private List<Float> mPieValueList;
    //饼图文字
    private List<String> mPieStringList;
    //选中角度
    private List<Float> angleList;
    //半径
    private int mRadius;
    //中心圆半径
    private int circleRadius;
    //中心圆文字
    private String text;
    //中心圆颜色
    private int centerColor;

    public AnimatePieChartView(Context context) {
        super(context, null);
    }

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

    public AnimatePieChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //自定义一些属性
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PieChartView);

        mRadius = a.getDimensionPixelSize(R.styleable.PieChartView_pie_radius,
                getResources().getDimensionPixelSize(R.dimen.pie_default_radius));
        circleRadius = a.getDimensionPixelSize(R.styleable.PieChartView_centerCircle_radius,
                getResources().getDimensionPixelSize(R.dimen.pie_center_radius));
        mTextSize = a.getDimension(R.styleable.PieChartView_textSize,
                getResources().getDimension(R.dimen.pie_text_size));
        mShaderSize = a.getDimension(R.styleable.PieChartView_shaderSize,
                getResources().getDimension(R.dimen.pie_shader_size));

        centerColor = getResources().getColor(R.color.color_window_background);

        a.recycle();

        mPaddingTop = getPaddingTop();
        mPaddingBottom = getPaddingBottom();
        mPaddingStart = getPaddingLeft();
        mPaddingEnd = getPaddingRight();

        initPaint();
    }

    /**
     * 初始化Paint
     */
    private void initPaint() {
        mPieColorList = new ArrayList<>();
        mPieValueList = new ArrayList<>();
        mPieStringList = new ArrayList<>();
        angleList = new ArrayList<>();


        mPaintOuter = new Paint();
        mPaintOuter.setStyle(Paint.Style.FILL);
        mPaintOuter.setAntiAlias(true);

        mPaintCenter = new Paint();
        mPaintCenter.setColor(centerColor);
        mPaintCenter.setStyle(Paint.Style.FILL);
        mPaintCenter.setAntiAlias(true);
        mPaintCenter.setTextSize(mTextSize);
        mPaintCenter.setTextAlign(Paint.Align.CENTER);

        mPaintShadow = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintShadow.setAntiAlias(true);
    }

    /**
     * 获取饼图每一部分的旋转角度
     */
    private float getRotationAngle(int i) {
        float angleR;
        float angleT = angleList.get(i);
        if (angleT <= 270f && angleT >= 90f) {
            angleR = 90f - angleT;
        } else if (angleT > 270f && angleT <= 360f) {
            angleR = 360f - angleT + 90f;
        } else if (angleT >= 0 && angleT < 90) {
            angleR = 90 - angleT;
        } else {
            angleR = 0;
        }

        for (int id = 0; id < angleList.size(); id++) {
            float temp = angleList.get(id) + angleR;
            if (temp > 360f) {
                temp -= 360f;
            } else if (temp < 0) {
                temp += 360f;
            }
            angleList.set(id, temp);
        }
        return angleR;
    }

    //测量View的宽高
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int mWidth = mRadius * 2 + mPaddingStart + mPaddingEnd;
        int mHeight = mRadius * 2 + mPaddingTop + mPaddingBottom;

        if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(mWidth, mHeight);
        } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(mWidth, heightSize);
        } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(widthSize, mHeight);
        }
    }
    
    //size改变后,重新测量View
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = mPaddingStart + (w - mPaddingStart - mPaddingEnd) / 2;
        mCenterY = mPaddingTop + (h - mPaddingTop - mPaddingBottom) / 2;
        mPaintShadow.setShader(new RadialGradient(mCenterX, mCenterY,
                circleRadius + mShaderSize,
                Color.TRANSPARENT, Color.TRANSPARENT, Shader.TileMode.CLAMP));
    }

    /**
     * 旋转前的饼图
     */
    private void drawPie(Canvas canvas, float amount,int i) {
        mPaintOuter.setColor(mCurrentColor);
        float mAngle = 360  * amount / mMaxValue;
        RectF oval = new RectF(mCenterX - mRadius, mCenterY - mRadius,mCenterX + mRadius,mCenterY + mRadius);
        canvas.drawArc(oval, mStartAngle, mAngle, true, mPaintOuter);
        mStartAngle += mAngle;
    }

    /**
     * 旋转后的饼图
     */
    private void drawPieTouch(Canvas canvas, float amount,int i) {
        mPaintOuter.setColor(mCurrentColor);
        float mAngle = 360  * amount / mMaxValue;
        float mRadiusTemp = mRadius ;
        RectF oval = new RectF(mCenterX - mRadiusTemp, mCenterY - mRadiusTemp,mCenterX + mRadiusTemp,mCenterY + mRadiusTemp);
        canvas.drawArc(oval, mStartAngle + mAngle , mAngle - mAngle  * 2, true, mPaintOuter);
        mStartAngle += mAngle;
        canvas.drawText(mPieStringList.get(i),getMeasuredWidth()/2,getMeasuredHeight()/4,mPaintCenter);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mPieValueList.size(); i++) {
            mCurrentColor = mPieColorList.get(i);
            if (i == mIndex) {
                canvas.save();
                canvas.translate(0,TRANS_DIS);
                drawPieTouch(canvas, mPieValueList.get(i),i);
                canvas.restore();
            } else {
                drawPie(canvas, mPieValueList.get(i),i);
            }
        }
        canvas.drawCircle(mCenterX, mCenterY,
                circleRadius + mShaderSize, mPaintShadow);
        mPaintCenter.setColor(centerColor);
    }

    public void setCenterText(String text){
        this.text=text;
    }

    /**
     *
     * 旋转选中的某一项
     */
    public void onAnimatedPie(int i) {
        mIndex = i;
        float angle = getRotationAngle(i);
        ValueAnimator animatorRotation;
        animatorRotation = ValueAnimator.ofFloat(mStartAngle, mStartAngle + angle);
        animatorRotation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mStartAngle = (Float) animation.getAnimatedValue();
                invalidate();
            }
        });

        int time = (int) (1000 * Math.abs(angle) / 360);

        animatorRotation.setDuration(time);
        animatorRotation.start();
    }

    /**
     * 饼图的各个属性
     *
     */
    public void setPie(List<PieData> pieList) {
        mMaxValue = 0;
        mPieColorList = new ArrayList<>();
        mPieStringList = new ArrayList<>();
        mPieValueList = new ArrayList<>();
        angleList = new ArrayList<>();

        for (PieData pie : pieList) {
            mPieColorList.add(pie.pieColor);
            mPieValueList.add(pie.pieValue);
            mMaxValue += pie.pieValue;
            mPieStringList.add(pie.pieString);
        }

        float angleTemp;
        float startAngleTemp = DEFAULT_START_ANGLE;

        for (float v : mPieValueList) {
            angleTemp = 360 * v / mMaxValue;

            angleList.add(startAngleTemp + angleTemp / 2);

            startAngleTemp += angleTemp;
        }
    }

}

调用方式

    //每个饼图的占比
    private float[] mPies;
    //饼图
    private AnimatePieChartView mAnimatePieChartView;
    //初始化当前的index
    private int mIndex=0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animate_pie_view_layout);
        findView();
        mAnimatePieChartView = (AnimatePieChartView) findViewById(R.id.id_animate_pie_view);
        setPieData(mAnimatePieChartView);
    }
    //findViewById
    private void findView(){
        Button btnPre= (Button) findViewById(R.id.id_btn_pre);
        Button btnNext= (Button) findViewById(R.id.id_btn_next);

        btnPre.setOnClickListener(this);
        btnNext.setOnClickListener(this);
    }
    //初始化饼图属性
    private void setPieData(AnimatePieChartView animatePieChartView) {
        final TypedArray typedArray = getResources().obtainTypedArray(R.array.ring_colors);
        final int size = 5;
        final int length = typedArray.length();
        //饼图数据集合
        List<PieData> pieEntryList = new ArrayList<>();
        mPies = new float[]{20f, 30f, 10f, 50f, 15f};
        String[] positions=new String[]{"上单","打野","中单","下路","辅助"};
        int color ;
        for(int i = 0; i < size; i++) {
            //饼图每块的颜色
            if(i >= length) {
                color = typedArray.getColor(length - 1, 0);
            } else {
                color = typedArray.getColor(i, 0);
            }
            //每块饼图所需要的数据
            PieData pe = new PieData(mPies[i],positions[i] , color);
            
            pieEntryList.add(pe);
        }
        animatePieChartView.setPie(pieEntryList);
        if(size > 1) {
            animatePieChartView.onTouchPie(0);
        } else {
            animatePieChartView.invalidate();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.id_btn_pre:
                if (mIndex <= 0) {
                    mIndex = mPies.length -1;
                } else {
                    mIndex = mIndex - 1;
                }
                animated(mIndex);
                break;
            case R.id.id_btn_next:
                if (mIndex >= mPies.length - 1) {
                    mIndex = 0;
                } else {
                    mIndex = mIndex + 1;
                }
                animated(mIndex);
                break;
        }
    }
    //旋转
    private void animated(int index) {
        if(mPies.length > 1) {
            mAnimatePieChartView.onAnimatedPie(index);
        }
    }

具体的实现和调用方式就这么多了。

自定义的属性和代码可查看具体代码地址。 代码地址

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android干货园

Android自定义组合控件---教你如何自定义下拉刷新和左滑删除

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/80...

691
来自专栏向治洪

android自定义状态栏颜色

我们知道IOS上的应用,状态栏的颜色总能与应用标题栏颜色保持一致,用户体验很不错,那安卓是否可以呢?若是在安卓4.4之前,答案是否定的,但在4.4之后,谷歌允...

2106
来自专栏向治洪

介绍几个好用的android自定义控件

首先看效果图, ? 看下这两个界面,第一个中用到了一个自定义的FlowRadioGroup,支持复合子控件,自定义布局; 第二个界面中看到了输入的数字 自...

2797
来自专栏三好码农的三亩自留地

Android九宫格控件-可在ListView和RecyclerView中使用

熟悉Android App开发的同学,肯定都清楚,如果要显示多张图片,类似九宫格,可以用GridView或者GridLayout来做,但是如果需求要求在List...

862
来自专栏Android开发与分享

【Android】RecyclerView的使用

2515
来自专栏向治洪

android scrollview嵌套listview计算高度的问题

 ScrollView中只能放一个控件,一般都放LinearLayout,orientation属性值为vertical。在LinearLayout中放需要呈...

1746
来自专栏向治洪

android沉浸式状态栏的实现

在style.xml中添加 <style name="Theme.Timetodo" parent="@android:style/Theme.Holo.L...

1957
来自专栏三好码农的三亩自留地

Android-教你写小米系统应用--"我的小米"

前面的文章中,我们已经了解了如何去自定义一个ViewGroup,可以在onLayout中自由的对子View进行位置设定,我们今天这里刚好需要对上面需求提到的三部...

862
来自专栏Jack的Android之旅

一个支持Fragment,View,图片轮播的Banner

之前有一个项目中有用到轮播,不过不是简单的轮播图片就完了,而是要轮播很多个View,一开始我的想法和大家一样在github在一个算了,哈哈,不过在试用了很多个项...

863
来自专栏开发之途

Android 仿微信底部渐变Tab

1513

扫码关注云+社区