Android自定义饼状图,支持点击弹出扇形

2024-08-22 18:18

本文主要是介绍Android自定义饼状图,支持点击弹出扇形,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇主要是记录了如何实现简单的折线图,支持点击弹出提示;这篇主要是实现另外一种图表–饼状图。

1 先上效果图

图片1

2 分析

第一看看到这个图,有过画扇形经验的同学会不屑,这个不简单吗?主要就是将所有的值相加,然后用每个值去除于总值,得到对于的一个扇形的角度,逐个画上去就好。这个说法大体是对的,但等到真正实施,还是有些小细节需要注意的。
for (int i = 0; i < numbers.size(); i++) {if (i == numbers.size() - 1) {sweepAngle = 360 - startAngle;} else {sweepAngle = (int) (numbers.get(i) * 1.0f / total * 360);}

看上边的代码,为什么最后一个我没有用最后的item值去除于总值,而是通过用360减去其他所有角度的总和呢?因为,我们使用除法进行计算,会很有可能得到带小数的结果,但画扇形接受的角度都是int型,这就会产生误差,导致最后不能刚好画满整个圆。于是需要采用这种方式来避免

3 支持点击弹出扇形

好了,一个简单的饼状图我们就完成了。但我们肯定不能仅限于此啊,现在开源的图表库框架都是带有很多功能的。我们也可以按照他们的思路,来添加一些功能上去。

图片2
图片3

3.1 看上边两张图,很多图表库都支持该种操作,但点击某个扇形时,扇形会向外弹出,感觉是被切出来一样。
3.2 看了效果,我们可以先整理下思路:
3.2.1 首先,需要判断手指是点击了哪个扇形(区域),怎么判断呢?
我这里有一种方式,可以保存每个扇形对于的角度的范围(把画扇形的起始角度当作零度);当发生点击事件时,可以通过点击点与圆点连线,计算出该线处在的角度,通过比较每个扇形的角度范围,判断点击发生在哪个扇形上。
3.2.2 点击区域问题解决了,还有另外一个重点:如何让扇形弹出,并且保证弹出的两边是对称的?下边用个图来说,这里需要用到数学的知识,不记得的需要上网搜搜了

这里写图片描述

3.2.3 关键代码
@Overridepublic boolean onTouchEvent(MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {float x = event.getX();float y = event.getY();int radius = 0;// 第一象限if (x >= getMeasuredWidth() / 2 && y >= getMeasuredHeight() / 2) {radius = (int) (Math.atan((y - getMeasuredHeight() / 2) * 1.0f/ (x - getMeasuredWidth() / 2)) * 180 / Math.PI);}// 第二象限if (x <= getMeasuredWidth() / 2 && y >= getMeasuredHeight() / 2) {radius = (int) (Math.atan((getMeasuredWidth() / 2 - x)/ (y - getMeasuredHeight() / 2))* 180 / Math.PI + 90);}// 第三象限if (x <= getMeasuredWidth() / 2 && y <= getMeasuredHeight() / 2) {radius = (int) (Math.atan((getMeasuredHeight() / 2 - y)/ (getMeasuredWidth() / 2 - x))* 180 / Math.PI + 180);}// 第四象限if (x >= getMeasuredWidth() / 2 && y <= getMeasuredHeight() / 2) {radius = (int) (Math.atan((x - getMeasuredWidth() / 2)/ (getMeasuredHeight() / 2 - y))* 180 / Math.PI + 270);}for (int i = 0; i < points.size(); i++) {Point point = points.get(i);if (point.x <= radius && point.y >= radius) {select = i;// Toast.makeText(context, "点击了" + point,// Toast.LENGTH_SHORT)// .show();invalidate();return true;}}return true;}return super.onTouchEvent(event);}
看到,为了角度计算好理解,我是通过划分四个象限来进行的;其中,采用point保存了每个扇形的起始和结束的角度,并添加到points中

4 整个饼状图的源码送上

public class CircleChartView extends View {private List<Double> numbers;private List<Point> points;private double total;private RectF normalOval;private RectF selectOval;private Paint paint;private Context context;public static final int[] colors = { android.R.color.holo_blue_light,android.R.color.holo_green_light, android.R.color.holo_red_light,android.R.color.holo_orange_light, android.R.color.holo_purple };public CircleChartView(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;numbers = new ArrayList<Double>();normalOval = new RectF();selectOval = new RectF();paint = new Paint();paint.setAntiAlias(true);paint.setStyle(Style.FILL);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);if (widthMode == MeasureSpec.AT_MOST|| heightMode == MeasureSpec.AT_MOST) {width = 400;height = 400;}setMeasuredDimension(width, height);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);normalOval.left = (float) (getMeasuredWidth() * 0.1);normalOval.top = (float) (getMeasuredHeight() * 0.1);normalOval.right = (float) (getMeasuredWidth() * 0.9);normalOval.bottom = (float) (getMeasuredHeight() * 0.9);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (!numbers.isEmpty()) {int startAngle = 0;int sweepAngle = 0;for (int i = 0; i < numbers.size(); i++) {if (i == numbers.size() - 1) {sweepAngle = 360 - startAngle;} else {sweepAngle = (int) (numbers.get(i) * 1.0f / total * 360);}if (select >= 0 && i == select) {selectOval.left = (float) (getMeasuredWidth() * 0.1);selectOval.top = (float) (getMeasuredHeight() * 0.1);selectOval.right = (float) (getMeasuredWidth() * 0.9);selectOval.bottom = (float) (getMeasuredHeight() * 0.9);Point point = points.get(select);int middle = (point.x + point.y) / 2;if (middle <= 90) {int top = (int) (Math.sin(Math.toRadians(middle)) * 15);int left = (int) (Math.cos(Math.toRadians(middle)) * 15);selectOval.left += left;selectOval.right += left;selectOval.top += top;selectOval.bottom += top;}if (middle > 90 && middle <= 180) {middle = 180 - middle;int top = (int) (Math.sin(Math.toRadians(middle)) * 15);int left = (int) (Math.cos(Math.toRadians(middle)) * 15);selectOval.left -= left;selectOval.right -= left;selectOval.top += top;selectOval.bottom += top;}if (middle > 180 && middle <= 270) {middle = 270 - middle;int left = (int) (Math.sin(Math.toRadians(middle)) * 15);int top = (int) (Math.cos(Math.toRadians(middle)) * 15);selectOval.left -= left;selectOval.right -= left;selectOval.top -= top;selectOval.bottom -= top;}if (middle > 270 && middle <= 360) {middle = 360 - middle;int top = (int) (Math.sin(Math.toRadians(middle)) * 15);int left = (int) (Math.cos(Math.toRadians(middle)) * 15);selectOval.left += left;selectOval.right += left;selectOval.top -= top;selectOval.bottom -= top;}paint.setColor(getResources().getColor(colors[i]));canvas.drawArc(selectOval, startAngle, sweepAngle, true,paint);} else {paint.setColor(getResources().getColor(colors[i]));canvas.drawArc(normalOval, startAngle, sweepAngle, true,paint);}points.get(i).x = startAngle;points.get(i).y = startAngle + sweepAngle;startAngle += sweepAngle;}}}public void setNumbers(List<Double> numbers) {this.numbers.clear();this.numbers.addAll(numbers);points = new ArrayList<Point>();for (Double item : numbers) {total += item;Point point = new Point();points.add(point);}invalidate();}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {float x = event.getX();float y = event.getY();int radius = 0;// 第一象限if (x >= getMeasuredWidth() / 2 && y >= getMeasuredHeight() / 2) {radius = (int) (Math.atan((y - getMeasuredHeight() / 2) * 1.0f/ (x - getMeasuredWidth() / 2)) * 180 / Math.PI);}// 第二象限if (x <= getMeasuredWidth() / 2 && y >= getMeasuredHeight() / 2) {radius = (int) (Math.atan((getMeasuredWidth() / 2 - x)/ (y - getMeasuredHeight() / 2))* 180 / Math.PI + 90);}// 第三象限if (x <= getMeasuredWidth() / 2 && y <= getMeasuredHeight() / 2) {radius = (int) (Math.atan((getMeasuredHeight() / 2 - y)/ (getMeasuredWidth() / 2 - x))* 180 / Math.PI + 180);}// 第四象限if (x >= getMeasuredWidth() / 2 && y <= getMeasuredHeight() / 2) {radius = (int) (Math.atan((x - getMeasuredWidth() / 2)/ (getMeasuredHeight() / 2 - y))* 180 / Math.PI + 270);}for (int i = 0; i < points.size(); i++) {Point point = points.get(i);if (point.x <= radius && point.y >= radius) {select = i;// Toast.makeText(context, "点击了" + point,// Toast.LENGTH_SHORT)// .show();invalidate();return true;}}return true;}return super.onTouchEvent(event);}private int select = -1;
}

5 总结

可以看到,包括饼状图描绘、点击效果等,更多的是需要一些数学知识进行计算。所以,要想搞好开发,还是要全面发展,哈哈哈!

6 源码下载

源码下载

这篇关于Android自定义饼状图,支持点击弹出扇形的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097025

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

定价129元!支持双频 Wi-Fi 5的华为AX1路由器发布

《定价129元!支持双频Wi-Fi5的华为AX1路由器发布》华为上周推出了其最新的入门级Wi-Fi5路由器——华为路由AX1,建议零售价129元,这款路由器配置如何?详细请看下文介... 华为 Wi-Fi 5 路由 AX1 已正式开售,新品支持双频 1200 兆、配有四个千兆网口、提供可视化智能诊断功能,建

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

CSS自定义浏览器滚动条样式完整代码

《CSS自定义浏览器滚动条样式完整代码》:本文主要介绍了如何使用CSS自定义浏览器滚动条的样式,包括隐藏滚动条的角落、设置滚动条的基本样式、轨道样式和滑块样式,并提供了完整的CSS代码示例,通过这些技巧,你可以为你的网站添加个性化的滚动条样式,从而提升用户体验,详细内容请阅读本文,希望能对你有所帮助...

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo