项目里面的需求, 当时搜索到 MPAndroidChart 库, 可以实现, 但是只是一个需求就引用偌大的一个库, 感觉不太爽, 打算自己自定义一个 参考 + 实践
一惯例先上效果图
二 GitHub
代码地址, 欢迎指正 https://github.com/MNXP/XPPieChart
三思路
空心图(一个大圆中心绘制一个小圆)
根据数据算出所占的角度
根据动画获取当前绘制的角度
根据当前角度获取 Paint 使用的颜色
动态绘制即将绘制的 和 绘制已经绘制的部分(最重要)
四实现
1 空心图 (一个大圆中心绘制一个小圆) 初始化数据
- paint = new Paint();
- paint.setAntiAlias(true);
- paint.setStyle(Paint.Style.FILL_AND_STROKE);
- screenW = DensityUtils.getScreenWidth(context);
- int width = DensityUtils.dip2px(context, 15);// 圆环宽度
- int widthXY = DensityUtils.dip2px(context, 10);// 微调距离
- int pieCenterX = screenW / 2;// 饼状图中心 X
- int pieCenterY = screenW / 3;// 饼状图中心 Y
- int pieRadius = screenW / 4;// 大圆半径
- // 整个饼状图 rect
- pieOval = new RectF();
- pieOval.left = pieCenterX - pieRadius;
- pieOval.top = pieCenterY - pieRadius + widthXY;
- pieOval.right = pieCenterX + pieRadius;
- pieOval.bottom = pieCenterY + pieRadius + widthXY;
- // 里面的空白 rect
- pieOvalIn = new RectF();
- pieOvalIn.left = pieOval.left + width;
- pieOvalIn.top = pieOval.top + width;
- pieOvalIn.right = pieOval.right - width;
- pieOvalIn.bottom = pieOval.bottom - width;
- // 里面的空白画笔
- piePaintIn = new Paint();
- piePaintIn.setAntiAlias(true);
- piePaintIn.setStyle(Paint.Style.FILL);
- piePaintIn.setColor(Color.parseColor("#f4f4f4"));
2 根据数据算出所占的角度
使用递归保证 cakeValues 的值的总和必为 100, 然后根据值求出角度
- private void settleCakeValues(int i) {
- float sum = getSum(cakeValues, i);
- CakeValue value = cakeValues.get(i);
- if (sum <= 100f) {
- value.setItemValue(100f - sum);
- cakeValues.set(i, value);
- } else {
- value.setItemValue(0);
- settleCakeValues(i - 1);
- }
- }
3 根据动画获取当前绘制的角度
curAngle 就是当前绘制的角度, drawArc()就是绘制的方法
- cakeValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float mAngle = obj2Float(animation.getAnimatedValue("angle"));
- curAngle = mAngle;
- drawArc();
- }
- });
4 根据当前角度获取 Paint 使用的颜色
根据当前的角度, 计算当前是第几个 item, 通过
paint.setColor(Color.parseColor(cakeValues.get(colorIndex).getColors()));
来设置 paint 的颜色
- private int getCurItem(float curAngle) {
- int res = 0;
- for (int i = 0; i <itemFrame.length; i++) {
- if (curAngle <= itemFrame[i] * ANGLE_NUM) {
- res = i;
- break;
- }
- }
- return res;
- }
5 动态绘制即将绘制的 和 绘制已经绘制的部分
最重要的一步, 我的需求是 4 类, 用不同的颜色
绘制当前颜色的扇形, curStartAngle 扇形的起始位置, curSweepAngle 扇形的终止位置
- paint.setColor(Color.parseColor(cakeValues.get(colorIndex).getColors()));
- float curStartAngle = 0;
- float curSweepAngle = curAngle;
- if (curItem> 0) {
- curStartAngle = itemFrame[curItem - 1] * ANGLE_NUM;
- curSweepAngle = curAngle - (itemFrame[curItem - 1] * ANGLE_NUM);
- }
- canvas.drawArc(pieOval, curStartAngle, curSweepAngle, true, paint);
绘制已经绘制的扇形根据 curItem 判断绘制过得扇形
- for (int i = 0; i < curItem; i++) {
- paint.setColor(Color.parseColor(cakeValues.get(i).getColors()));
- if (i == 0) {
- canvas.drawArc(pieOval, startAngle,(float) cakeValues.get(i).getItemValue() * ANGLE_NUM, true, paint);
- continue;
- }
- canvas.drawArc(pieOval,itemFrame[i - 1] * ANGLE_NUM,(float) cakeValues.get(i).getItemValue() * ANGLE_NUM, true, paint);
- }
绘制中心的圆
canvas.drawArc(pieOvalIn, 0, 360, true, piePaintIn);
6 特别注意
isFirst 判断是够是第一次绘制(绘制完成后, home 键进入后台, 再次进入, 不需要动态绘制)
- @Override
- protected void onDraw(Canvas canvas) {
- if (isFirst && isDrawByAnim) {
- drawCakeByAnim();
- }
- isFirst = false;
- }
isDrawByAnim 判断是否需要动画绘制
drawCake()为静态绘制饼状图
- public void surfaceCreated(SurfaceHolder holder) {
- if (!isFirst||!isDrawByAnim)
- drawCake();
- }
以上就是简单的实现动态绘制饼状图, 待完善, 以后会更新如有建议和意见, 请及时沟通
来源: https://juejin.im/post/5aa8deccf265da238a30247d