利用kotlin实现一个饼图实例代码

前言

饼图是许多人最熟悉的图表类型,也是使用频率最高的图表类型之一,本文主要给大家介绍了关于利用kotlin实现饼图的相关内容,分享出来供大家参考学习,代码不难,所以打算用kotlin来实现,增加熟练度,下面来一起看看吧。

先看看做的是什么

看完图,我们来整理下思路

  • 饼图居中,每块区域都是一个扇形,需要canvas.drawArc根据角度来绘制
  • 需要path.arcTo定位到扇形弧度的一半来绘制折线的起点
  • 通过canvas.drawPath绘制折线,折线的长度根据饼图大小来设置比例
  • 通过canvas.drawText绘制文字,文字的大小根据饼图的大小来设置比例,绘制文字的位置需要计算文字的宽度

思路清晰后就撸起袖子加油干

知识点

我们先来了解一个概念,我们在paint画扇形的时候,对应的度数是在哪个位置呢?

看到图后应该明白了吧

绘制饼图

我们先来看看他的参数,很明显,左、上、右、下参数形成一个面板,startAngle 为起始的角度,sweepAngle 为从起始角度开始绘制多少度,useCenter为是否连接到圆心,paint为画笔

 public void drawArc(float left, float top, float right, float bottom, float startAngle,
 float sweepAngle, boolean useCenter, @NonNull Paint paint) {
 super.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint);
 }

我们以当前控件的width、height为面板来画一个圆形的饼图

 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 override fun onDraw(canvas: Canvas) {
 super.onDraw(canvas)
 canvas.drawArc(0f, 0f, width, height, 0f, 360f, true, paintRed)
 }

哇塞,好丑哦,结果显示的是一个椭圆,如果要绘制一个圆形的饼图,我们必须得保证left=top=right=bottom

设置饼图居中

 /**
 * view的宽度
 */
 var width: Float = 0f
 /**
 * view的高度
 */
 var height: Float = 0f
 /**
 * drawArc距离左边的距离
 */
 var left: Float = 0f
 /**
 * drawArc距离上边的距离
 */
 var top: Float = 0f
 /**
 * drawArc距离右边的距离
 */
 var right: Float = 0f
 /**
 * drawArc距离下边的距离
 */
 var bottom: Float = 0f
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 override fun onDraw(canvas: Canvas) {
 super.onDraw(canvas)
 canvas.drawArc(left, top, right, bottom, 0f, 360f, true, paint)
 }
 override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
 super.onSizeChanged(w, h, oldw, oldh)
 setBackgroundColor(resources.getColor(R.color.black))
 width = w.toFloat()
 height = h.toFloat()
 left = width / 4f
 top = width / 4f
 right = width - left
 bottom = width - top
 }

完美居中

接下来,我们要把上面从0度到360度多分几个步骤来绘制

 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 override fun onDraw(canvas: Canvas) {
 super.onDraw(canvas)
 ...
 canvas.drawArc(left, top, right, bottom, 0f, 20f, true, paintPuple)
 canvas.drawArc(left, top, right, bottom, 20f, 10f, true, paintGray)
 canvas.drawArc(left, top, right, bottom, 30f, 40f, true, paintGreen)
 canvas.drawArc(left, top, right, bottom, 70f, 110f, true, paintBlue)
 canvas.drawArc(left, top, right, bottom, 180f, 110f, true, paintRed)
 canvas.drawArc(left, top, right, bottom, 290f, 70f, true, paintYellow)
 }

还不错

上图的度数是写死的,现在我们来把他写活

提供一个设置个数的集合,比如农名伯伯卖水果,梨子卖了10个,香蕉卖了3个,苹果卖了7个,那么这个个数的集合为pieList=(10,3,7)。

因为饼图是根据角度来绘制的,我们必须将这个个数集合换算成角度集合,换算的过程中我们需要知道每一种水果所占总水果的比例,然后通过这个比例去乘上360度,就知道每一种水果所占的度数。

梨子的占比为10/(10+3+7)=1/2,可得梨子占饼图的度数为1/2*360=180度,按照这种方式计算,香蕉和苹果占饼图的度数分别为54度和126度,那么,饼图的分布也就出来了

现在,我们来定义一个个数集合,计算出比例的集合和度数的集合,下面是比例的集合,度数的集合我们在绘制的时候再去计算

 /**
 * 个人分类集合
 */
 var pieList = arrayListOf(10f,3f,7f)

 /**
 * 饼图所占的比例
 */
 var scaleList = arrayListOf<Float>()
 /**
 * 个数分类的总量
 */
 var total: Float = 0f
 override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
 super.onSizeChanged(w, h, oldw, oldh)
 //计算个数的总和
 total = pieList.sum()
 //存储比例值
 for (a in pieList) {
  scaleList.add(a.div(total))
 }
 }

比例集合拿到了,接下来,我们去循环这个比例值,然后将比例值乘上360度,计算出角度值,供drawArc的sweepAngle使用,但是,我们还缺少一个startAngle起始角度,我们可以定义一个起始角度为0度,然后每次根据计算出的角度值sweepAngle去累加起始度数,用代码来实现下

 /**
 * 记录当前画饼图的度数
 */
 var currentDegree: Float = 0f

 /**
 * 累加饼图的度数作为下一个绘制的起始度数
 */
 var srctorDegree: Float = 0f
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 override fun onDraw(canvas: Canvas) {
 super.onDraw(canvas)
 for (scale in scaleList) {
  val paint = Paint()
  paint.strokeWidth = dip(10.0f).toFloat()
  paint.isAntiAlias = true
  //定义一个随机生成的颜色数,来区分不同的扇形区域
  val hex = "#" + Integer.toHexString((-16777216 * Math.random()).toInt())
  paint.color = Color.parseColor(hex)
  //角度数
  srctorDegree = scale * 360
  canvas.drawArc(left, top, right, bottom, currentDegree, srctorDegree, true, paint)
  //累加角度
  currentDegree += srctorDegree
 }
 }

ok,现在我们可以随机的去定义个数来生成占比的饼图了

绘制折线

接下来,我们来绘制折线,折线的起点是每个扇形弧上的一半,path的arcTo方法也可以绘制圆,且方法参数使用也是一样,我们可以让arcTo跟着canvas.drawArc一块画,arcTo的startAngle起始角度为canvas.drawArc起始角度加上sweepAngle度数的一半,这样,就定位到了弧边的一半,arcTo的sweepAngle为0就行了,我们只定位,不绘制

  ...
  canvas.drawArc(left, top, right, bottom, currentDegree, srctorDegree, true, paint)
  val path = Path()
  path.arcTo(left, top, right, bottom, currentDegree + srctorDegree / 2, 0f, false)
  ...

现在,path的位置定位到弧边的一半了,接下来,我们要知道当前path的坐标然后根据坐标去绘制折线,

  val bounds = RectF()
  //将path当前的坐标赋值给bounds
  path.computeBounds(bounds, true)

现在拿到坐标了,我们再来看看效果图,折线和文字呈四个方向,我们不如把饼图分成四个区域,以圆心为坐标轴原点,切分四个象限:

  • 第一象限:折线为右上,文字在折线右边
  • 第二象限:折线为左上,文字在折线左边
  • 第三象限:折线为左下,文字在折线左边
  • 第四象限:折线为右下,文字在折线右边

那么,接下来就是如何判断当前起始点在哪个象限了,先以第一象限为例,如果当前的坐标大于饼图横轴方向一半,并且小于饼图纵轴方向的一半,那么就是第一象限,其他依次类推

 /**
 * 横线的长度
 */
 var lineae: Int = 30

 /**
 * 斜线的长度
 */
 var slantLine: Int = 30
 override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
 super.onSizeChanged(w, h, oldw, oldh)
  //计算横线的比例
  lineae = (width / 30f).toInt()
  //计算斜线的比例
  slantLine = (width / 40f).toInt()
 }

 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 override fun onDraw(canvas: Canvas) {
 super.onDraw(canvas)

 for (scale in scaleList) {
  ...
  val path = Path()
  path.arcTo(left, top, right, bottom, currentDegree + srctorDegree / 2, 0f, false)
  val bounds = RectF()
  path.computeBounds(bounds, true)
  //第一象限
  if (bounds.left >= width / 2 && bounds.top <= width / 2) {
  path.lineTo(bounds.left + lineae, bounds.top)
  path.lineTo(bounds.left + lineae + slantLine, bounds.top - slantLine)
  canvas.drawPath(path, paintLine)
  //第二象限
  } else if (bounds.left <= width / 2 && bounds.top <= width / 2) {
  path.lineTo(bounds.left - lineae, bounds.top)
  path.lineTo(bounds.left - lineae - slantLine, bounds.top - slantLine)
  canvas.drawPath(path, paintLine)
  //第三象限
  } else if (bounds.left <= width / 2 && bounds.top >= width / 2) {
  path.lineTo(bounds.left - lineae, bounds.top)
  path.lineTo(bounds.left - lineae - slantLine, bounds.top + slantLine)
  canvas.drawPath(path, paintLine)
  //第四象限
  } else {
  path.lineTo(bounds.left + lineae, bounds.top)
  path.lineTo(bounds.left + lineae + slantLine, bounds.top + slantLine)
  canvas.drawPath(path, paintLine)
  }
  }
  ...
 } 

哎呀,出来了

绘制文字

接下来就是绘制文字了,第一、四象限还好,文字可以在折线后面跟着画,但是二、三象限的文字就不允许了,我们必须往前移动文字宽度的距离才能完美衔接到折线上,所以,我们来定义一个计算文字的方法

 /**
 * 获取文字的宽度
 */
 private fun getStringWidth(str: String): Float = paintLine.measureText(str)

文字是会随着饼图的大小进行改变的,所以设置文字大小的比例

 paintLine.textSize = dip(width / 100).toFloat()

接下来就开始绘制文字吧

 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 override fun onDraw(canvas: Canvas) {
 super.onDraw(canvas)
  ...
  //获取当前的百分比文字
  val textStr = String.format("%.2f%%", scale * 100)
  //获取文字的宽度
  val textWidth = getStringWidth(textStr)
  //第一象限
  if (bounds.left >= width / 2 && bounds.top <= width / 2) {
  ...
  canvas.drawText(textStr, bounds.left + lineae + slantLine, bounds.top - slantLine, paintText)
  ...
  //第二象限
  } else if (bounds.left <= width / 2 && bounds.top <= width / 2) {
  ...
  canvas.drawText(textStr, bounds.left - lineae - slantLine - textWidth, bounds.top - slantLine, paintText)
  ...
  //第三象限
  } else if (bounds.left <= width / 2 && bounds.top >= width / 2) {
  ...
  canvas.drawText(textStr, bounds.left - lineae - slantLine - textWidth, bounds.top + lineae, paintText)
  ...
  //第四象限
  } else {
  ...
  canvas.drawText(textStr, bounds.left + lineae + slantLine, bounds.top + slantLine, paintText)
  ...
  }
 }

嗯,还不错,

然后我们再看看效果图,饼图中间还有一块与背景色一样的黑圆,这不跟简单了嘛

 //定义中间黑圆的画笔
 paintCicle.color = resources.getColor(R.color.black)
 paintCicle.isAntiAlias = true
 paintCicle.style = Paint.Style.FILL
  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
  override fun onDraw(canvas: Canvas) {
  super.onDraw(canvas)
  ...
  //在循环结束饼图的时候,以饼图的原点为中心画圆
  canvas.drawCircle(width / 2, width / 2, width / 8, paintCicle)
  }

然后我们暴露一个方法,提供给Activity去调用

 /**
  * 设置扇形参数
  */
 fun setPieData(a: ArrayList<Float>) {
  pieList.clear()
  pieList.addAll(a)
  invalidate()
 }

那么,Activity就可以这么去调用了

 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_second)
  pie1.setPieData(arrayListOf(1f,10f,15f,9f,15f))
  pie2.setPieData(arrayListOf(3f,8f,15f,7f,9f))
  pie3.setPieData(arrayListOf(9f,3f,7f,3f,4f,2f,1f))
 }

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

您可能感兴趣的文章:

  • kotlin 官方学习教程之基础语法详解
  • Kotlin 开发环境详解及简单实例
  • Kotlin 的注解类详解及实例
  • 使用Kotlin开发Android应用的初体验
  • Kotlin开发Android应用实例详解
  • 使用Kotlin开发Android应用教程
  • Kotlin 单例实例详解
  • Kotlin 基础教程之反射
  • Kotlin 编程三分钟入门
(0)

相关推荐

  • 使用Kotlin开发Android应用教程

    使用Kotlin开发Android应用 1.Kotlin介绍 [Kotlin](https://kotlinlang.org/) Kotlin是一门基于JVM的编程语言,它正成长为Android开发中用于替代Java语言的继承者.Java是世界上使用最多的编程语言之一,当其他编程语言为更加便于开发者使用而不断进化时,Java并没有像预期那样及时跟进. Java缺失的很多特性在最新的修订版中逐渐覆盖到了,但Android开发者暂时还没能够使用它们.这就使得类似Kotlin这样的语言有了用武之地了:

  • 使用Kotlin开发Android应用的初体验

    昨晚,最近一届的谷歌IO大会正式将Kotlin确定为了官方开发语言,作为一名Android开发鸟,怎么能不及时尝尝鲜呢? Kotlin的简要介绍 在开发之前,很多同学一定有很多疑问,Kotlin到底有啥好处,怎么和现有的项目共存呢?Java那么些特性Kotlin都有吗?嗯,让我们一一来看. Kotlin 非常适合开发 Android 应用程序,将现代语言的所有优势带入 Android 平台而不会引入任何新的限制: 兼容性:Kotlin 与 JDK 6 完全兼容,保障了 Kotlin 应用程序可以

  • Kotlin 编程三分钟入门

    为什么使用Kotlin 项目一期在收尾了终于有时间折腾了,一个多月以来Kotlin从入门到现在,坚持用来开发的切身感受.因为语法与Java的区别挺大的一开始很想放弃,如果不是因为项目在使用,想必很少人会尝试这样一门小众语言,但是习惯后会发现这些年究竟浪费多少时间在写无用的Java代码了,Kotlin在兼容Java的基础上还能大大提升开发效率.Kotlin有许多特性但对于开发来说,快速适应和学习更为重要,很多时候我们都是在不明白其原因就开始使用的,正如我们不可能把Retrofit原理研究透才使用它

  • Kotlin 开发环境详解及简单实例

    Hello Kotlin 在前段时间举办的Google I/O 2017上,Google宣布Kotlin成为Android官方的开发语言,这个最初发布于2011年的语言在短短的时间内就吸引了大量的开发者,而Google使得它进入了更多人的视线. Kotlin是一种开源的基于JVM的变成语言,由JetBeans公司开发(大概除了使用VS的.net开发者意外,都会或多或少听说或使用过IDEA吧),名字取自圣彼得堡附近的一个小岛(Koltin island). Kotlin是一种简单的语言,其主要目标

  • Kotlin 基础教程之反射

    Kotlin 基础教程之反射 概述 反射是语言与库中的一组功能, 可以在运行时刻获取程序本身的信息.在Kotlin中,不仅可以通过发射获取类的信息,同时可以获取函数和属性的信息.也就是说,在在运行时刻得到一个函数或属性的名称和数据类型) 可以通过简单的函数式, 或交互式的编程方式实现. 在Java平台上, 使用反射功能所需要的运行时组件是作为一个单独的JAR文件发布的( kotlinreflect.jar). 这是为了对那些不使用反射功能的应用程序, 减少其运行库的大小. 如果你需要使用反射,

  • kotlin 官方学习教程之基础语法详解

    kotlin 官方学习教程之基础语法详解 Google 在今天的举行了 I/O 大会,大会主要主要展示内有容 Android O(Android 8.0)系统.Google Assistant 语音助手.Google 智能音箱.人工智能.机器学习.虚拟现实等.作为一个 Android 开发者,我关心的当然是 Android O(Android 8.0)系统了,那么关于 Android O 系统的一个重要消息是全面支持 Kotlin 编程语言,使得 Kotlin 成为了 Android 开发的官方

  • Kotlin 的注解类详解及实例

    Kotlin 的注解类详解及实例 注解声明 注解是将元数据附加到代码的方法.要声明注解,请将 annotation 修饰符放在类的前面: annotation class Fancy 注解的附加属性可以通过用元注解标注注解类来指定: @Target 指定可以用 该注解标注的元素的可能的类型(类.函数.属性.表达式等): @Retention 指定该注解是否 存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true): @Repeatable 允许 在单个元素上多次

  • Kotlin开发Android应用实例详解

    Kotlin开发Android应用实例详解 相关文章:关于Kotlin语言的基础介绍: http://www.jb51.net/article/114086.htm 我们简单的知道了Kotlin这门新语言的优势,也接触了一些常见的语法及其简单的使用,相信你会对它有浓厚的兴趣,暂且理解为对它感兴趣吧,哈哈哈.那么,我们该如何在Android中应用这门新的语言呢?今天的这篇文章带你学习使用Kotlin开发Android应用,并对比我们传统语言Java,让你真真切切的感受到他的美和优雅. 配置 项目g

  • Kotlin 单例实例详解

    Kotlin 单例实例详解 单例的实现方法,可以通过同伴对象,或者 lazy. 示例: class Hello private constructor() { companion object { val instance = Hello() } } 通过 lazy 实现 class Hello private constructor() { private object Holder { val INSTANCE = Hello() } companion object { val insta

  • 利用kotlin实现一个饼图实例代码

    前言 饼图是许多人最熟悉的图表类型,也是使用频率最高的图表类型之一,本文主要给大家介绍了关于利用kotlin实现饼图的相关内容,分享出来供大家参考学习,代码不难,所以打算用kotlin来实现,增加熟练度,下面来一起看看吧. 先看看做的是什么 看完图,我们来整理下思路 饼图居中,每块区域都是一个扇形,需要canvas.drawArc根据角度来绘制 需要path.arcTo定位到扇形弧度的一半来绘制折线的起点 通过canvas.drawPath绘制折线,折线的长度根据饼图大小来设置比例 通过canv

  • 利用Oracle数据库发送邮件的实例代码

    --发送邮件的主过程如下所述: Procedure send_mail_ (p_From Varchar2, --邮件发送人 p_Fromuser Varchar2, --发件人昵称 p_Touser Varchar2, --接受人昵称 p_To Varchar2, --邮件接收人 p_Cc Varchar2, --邮件抄送人 p_Subject Varchar2, --邮件标题 p_Message Varchar2, --邮件内容 p_User Varchar2, --邮件验证用户 p_Mai

  • Android 画一个太极图实例代码

    今天练手一下,一起来画个太极图吧~ 最终效果如下: 最终效果 一般都是先讲原理,我就反其道而行,先讲实现吧. 1.继承实现初始化方法 继承View,实现基本的构造函数: public TestView(Context context) { this(context, null); } public TestView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TestView(Context c

  • JS+canvas画一个圆锥实例代码

    以下是我们给大家分享是实例代码: <html> <head> <title>我的第一个 HTML 页面</title> </head> <body> <canvas id='cvs' width='1000' height="800"> </canvas> <script> const cvs =document.getElementById('cvs'); // 计算画布的宽度

  • 利用kotlin实现一个打方块的小游戏实例教程

    前言 今天来做个打方块的小游戏,继续熟悉kotlin的语法,更多关于kotlin的语法大家可以参考这篇文章://www.jb51.net/article/114069.htm 看下要实现的效果图: 看着效果图好像挺难的样子,但理清思绪后,你会发现特别的简单,还是那句话,学习方法最重要 思路 1.构造界面 : 这个部分比较简单,根据控件的比例来画小球.挡板和击打的方块,所有击打的方块存储在一个集合里面,方块里面存储的信息有left.top.right.bottom位置信息和是否被击打过了的标志 2

  • HTML5 实现的一个俄罗斯方块实例代码

    示例简单,运行地址为:http://chendd.cn/demo/html/canvas/elsfk.html,得需要支持html5浏览器的环境. 实现的功能:方块旋转(W键).自动下落.移动(ASD).消行.快速下落(空格键).下落阴影.游戏结束. 为实现功能:消行时的计分.等级.以及不同等级的下落速度等. 学习了xiaoE的Java版本的俄罗斯方块后,自己动手使用html5的canvas实现的, 参考效果图如下: 详细代码如下: <!DOCTYPE html> <html> &

  • 利用Python计算圆周率π的实例代码

    前言 A货:什么!你不会背圆周率(鄙夷的眼神) 3.1415926535 8979323846 26433... 桥哥:我会算呀 !!! 一.圆周率的历史 1.中国 ★ 魏晋时期,刘徽曾用使正多边形的边数逐渐增加去逼近圆周的方法 (即「割圆术」),求得π的近似值3.1416. ★ 汉朝时,张衡得出π的平方除以16等于5/8,即π等于10的开方(约为3.162).虽然这个值不太准确,但它简单易理解,所以也在亚洲风行了一阵. ★ 王蕃(229-267)发现了另一个圆周率值,这就是3.156, 但没有

  • 利用Java制作字符动画实例代码

    前言 今晚闲来无事,整理了一下电脑中尘封已久的旧代码,看着那些年自己写过的代码,踩过的坑,顿时老泪纵横.正当在感叹之际,突然发现在"马克思"文件夹下出现了一个好玩的项目,那就是N年前刚学Java时写的GIF转字符动画的小玩具,虽然是个小玩意,但是在当时能搞点东西出来还是非常有成就感的. 正文 效果展示 原图,某两年半练习生 转成字符动画后的练习生 实现原理 其实字符动画的实现原理比较简单,这里我们抛开GIF,直接拿一张静态图片来说明. 首先我们要把原图转成灰度图,这样图片中每个像素就只

  • Python利用requests模块下载图片实例代码

    本文主要介绍的是关于Python利用requests模块下载图片的相关,下面话不多说了,来一起看看详细的介绍吧 MySQL中事先保存好爬取到的图片链接地址. 然后使用多线程把图片下载到本地. 示例代码: # coding: utf-8 import MySQLdb import requests import os import re from threading import Thread import datetime header = {'User-Agent': 'Mozilla/5.0

  • 基于Django框架利用Ajax实现点赞功能实例代码

    概要: 要实现点赞功能,需要实现的有:谁进行的点赞.什么时候进行点赞.点赞的对象是谁.每一个对象的点赞数量是多少.点赞过后还需要能够取消点赞,为了是点赞后的信息能够及时的显示在前端页面,就需要使用Ajax来异步请求数据,实现实时显示. 下面话不多说了,来随着小编一起看看详细的介绍吧 模型分析: 创建的模型需要记录的数据有:点赞者.点赞对象.点赞时间.点赞的数量,由于前面三个属性主要用于记录点赞的状态,而点赞数量主要用于记录某篇文章的点赞数量,所以这里最好把点赞数量单独放在一个模型中.这里就创建了

随机推荐