这个圆环是静态的,没有自动增加的动画

绘制并不复杂,有些细节点容易搞错,这里写出来,算是做个笔记。

先放出源码,细节点,后面会说明。建议看后面的说明。最后,会进行扩展,绘制扇形

需求/功能说明:
1、假设一个班级里有 X 个人,班级里的学习有的篮球、有的足球、有的书法、有的绘画,还有的,什么都没学
2、圆环绘制的起点,是圆环顶部,逆时针依次绘制

效果图:

代码:


import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.view.Viewclass MyView : View {private var mContext: Context? = nullprivate var vW: Int = 0private var vH: Int = 0//半径private var radius: Float = 0f//背景圆环画笔private var bgCirclePaint: Paint? = null//圆环画笔private var lanQiuCirclePaint: Paint? = nullprivate var zuQiuCirclePaint: Paint? = nullprivate var shuFaCirclePaint: Paint? = nullprivate var huiHuaCirclePaint: Paint? = null//文字画笔1private var textPaint_1: TextPaint? = null//文字画笔2private var textPaint_2: TextPaint? = null//文字1的偏移量private var textOffset_1: Float = 0f//文字2的偏移量private var textOffset_2: Float = 0f//掌握对应的矩形,用于辅助绘制 掌握 圆环private var rectF: RectF? = nullconstructor(context: Context?) : super(context) {mContext = contextinit()}constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {mContext = contextinit()}constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr) {mContext = contextinit()}private fun init() {//半径。根据布局文件,控件的宽高是 200dp。20是圆环画笔宽度。应该10就够,我这里往回缩了一点radius = UiUtils.dip2px(mContext, 100f) - 20f//背景圆画笔bgCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)bgCirclePaint?.style = Paint.Style.STROKEbgCirclePaint?.color = Color.parseColor("#888888")bgCirclePaint?.strokeWidth = 20f//篮球圆环画笔lanQiuCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)lanQiuCirclePaint?.style = Paint.Style.STROKElanQiuCirclePaint?.color = Color.parseColor("#ff0000")lanQiuCirclePaint?.strokeWidth = 20f//足球圆环画笔zuQiuCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)zuQiuCirclePaint?.style = Paint.Style.STROKEzuQiuCirclePaint?.color = Color.parseColor("#00ff00")zuQiuCirclePaint?.strokeWidth = 20f//书法圆环画笔shuFaCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)shuFaCirclePaint?.style = Paint.Style.STROKEshuFaCirclePaint?.color = Color.parseColor("#0000ff")shuFaCirclePaint?.strokeWidth = 20f//绘画画笔。不同于上面,另外一种写法:把一个画笔传进去huiHuaCirclePaint = Paint(shuFaCirclePaint)huiHuaCirclePaint?.color = Color.parseColor("#fe7c2b")//文字画笔1textPaint_1 = TextPaint(Paint.ANTI_ALIAS_FLAG)textPaint_1?.setTextSize(UiUtils.dip2px(mContext!!, 13f).toFloat());textPaint_1?.setColor(Color.parseColor("#888888"));textPaint_1?.setTextAlign(Paint.Align.CENTER);textOffset_1 = (textPaint_1!!.ascent() + textPaint_1!!.descent()) / 2//文字画笔2textPaint_2 = TextPaint(Paint.ANTI_ALIAS_FLAG)textPaint_2?.setTextSize(UiUtils.dip2px(mContext!!, 12f).toFloat());textPaint_2?.setColor(Color.parseColor("#252525"));textPaint_2?.setTextAlign(Paint.Align.CENTER);textOffset_2 = (textPaint_2!!.ascent() + textPaint_2!!.descent()) / 2}//总人数private var mTotalNum: Int = 0//篮球个数private var mLanQiuNum: Int = 0//足球个数private var mZuQiuNum: Int = 0//书法个数private var mShuFaNum: Int = 0//绘画private var mHuiHuaNum: Int = 0//没有学习的个数private var mMeiXueNum: Int = 0//打篮球的人数对应的角度private var lanQiuAngle: Float = 0f//角度开始位置private var lanQiuAngleStart: Float = 0f//角度结束位置private var lanQiuAngleEnd: Float = 0f//踢足球的人数对应的角度private var zuQiuAngle: Float = 0fprivate var zuQiuAngleStart: Float = 0fprivate var zuQiuAngleEnd: Float = 0f//书法的人数占的角度private var shuFaAngle: Float = 0fprivate var shuFaAngleStart: Float = 0fprivate var shuFaAngleEnd: Float = 0f//绘画的人数占的角度private var huiHuaAngle: Float = 0fprivate var huiHuaAngleStart: Float = 0fprivate var huiHuaAngleEnd: Float = 0ffun setData(totalNum: Int,lanQiuNum: Int,zuQiuNum: Int,shuFaNum: Int,huiHuaNum: Int,meiXueNum: Int) {mTotalNum = totalNummLanQiuNum = lanQiuNummZuQiuNum = zuQiuNummShuFaNum = shuFaNummHuiHuaNum = huiHuaNummMeiXueNum = meiXueNum//数字是否有效val numIsEffective: Boolean =(mTotalNum != 0) && (mTotalNum == mLanQiuNum + mZuQiuNum + mShuFaNum + mHuiHuaNum + mMeiXueNum)if (numIsEffective) {//数据有效,开始下面的计算val fTotalNum: Float = mTotalNum.toFloat()//篮球lanQiuAngle = if (mLanQiuNum != 0) {//数不为0360 * (mLanQiuNum / fTotalNum)} else {0f}//逆时针画,最后用到 onDraw 里,是负度数。(符号表示方向)lanQiuAngleStart = 0flanQiuAngleEnd = lanQiuAngleStart - lanQiuAngle//足球zuQiuAngle = if (mZuQiuNum != 0) {//数不为0360 * (mZuQiuNum / fTotalNum)} else {0f}zuQiuAngleStart = lanQiuAngleEndzuQiuAngleEnd = zuQiuAngleStart - zuQiuAngle//书法shuFaAngle = if (mShuFaNum != 0) {//数不为0360 * (mShuFaNum / fTotalNum)} else {0f}shuFaAngleStart = zuQiuAngleEndshuFaAngleEnd = shuFaAngleStart - shuFaAngle//绘画huiHuaAngle = if (mHuiHuaNum != 0) {//数不为0360 * (mHuiHuaNum / fTotalNum)} else {0f}huiHuaAngleStart = shuFaAngleEndhuiHuaAngleEnd = huiHuaAngleStart - huiHuaAngle//都不学的不用管,如果有剩余角度,就是 都不学 的}Log.e("篮球:", "度数:${lanQiuAngle} ;开始:${lanQiuAngleStart} ;结束:${lanQiuAngleEnd}")Log.e("足球:", "度数:${zuQiuAngle} ;开始:${zuQiuAngleStart} ;结束:${zuQiuAngleEnd}")Log.e("书法:", "度数:${shuFaAngle} ;开始:${shuFaAngleStart} ;结束:${shuFaAngleEnd}")Log.e("绘画:", "度数:${huiHuaAngle} ;开始:${huiHuaAngleStart} ;结束:${huiHuaAngleEnd}")rectF = RectF(-radius, -radius, radius, radius)invalidate()}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)vW = wvH = hrectF = RectF(-radius, -radius, radius, radius)}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)//保存下画布canvas?.save();//移动画布坐标原点到控件中心canvas?.translate(vW / 2f, vH / 2f);//旋转画布。使得正上方向为0度。顺时针为正度数方向canvas?.rotate(-90f)//canvas?.rotate(270f)//绘制一个背景圆canvas?.drawCircle(0f, 0f, radius, bgCirclePaint!!)if (lanQiuAngle > 0f) {//表示有度数。有度数,才绘制canvas?.drawArc(rectF!!, lanQiuAngleStart, -lanQiuAngle, false, lanQiuCirclePaint!!)}if (zuQiuAngle > 0f) {canvas?.drawArc(rectF!!, zuQiuAngleStart, -zuQiuAngle, false, zuQiuCirclePaint!!)}if (shuFaAngle != 0f) {canvas?.drawArc(rectF!!, shuFaAngleStart, -shuFaAngle, false, shuFaCirclePaint!!)}if (huiHuaAngle > 0f) {canvas?.drawArc(rectF!!, huiHuaAngleStart, -huiHuaAngle, false, huiHuaCirclePaint!!)}// 释放画布canvas?.restore();//释放完成后,画布角度恢复正常(正右方向为0度,顺时针为正度数)、坐标原点为控件左上角//保存下画布canvas?.save();//移动画布坐标原点到控件中心canvas?.translate(vW / 2f, vH / 2f);canvas?.drawText("比例",0f,-UiUtils.dip2px(mContext, 8f) - textOffset_1,textPaint_1!!)canvas?.drawText("${mLanQiuNum + mZuQiuNum + mShuFaNum + mHuiHuaNum}/${mTotalNum}",0f,UiUtils.dip2px(mContext, 8f) - textOffset_2,textPaint_2!!)// 释放画布canvas?.restore();}}

使用:

    <...MyViewandroid:id="@+id/my_view"android:layout_width="200dp"android:layout_height="200dp"android:layout_marginTop="30dp"android:background="#ffffff"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />//总数var totalNum: Int = 8000//篮球var lanQiuNum: Int = 1000//足球var zuQiuNum: Int = 2000//书法var shuFaNum: Int = 3000//绘画var huiHuaNum: Int = 1000//没学var meiXueNum: Int = 1000//设置数据my_view?.setData(totalNum = totalNum,lanQiuNum = lanQiuNum,zuQiuNum = zuQiuNum,shuFaNum = shuFaNum,huiHuaNum = huiHuaNum,meiXueNum = meiXueNum)

----------以上是源码------------------以上是源码------------------------以上是源码-----------------------

关于文字的绘制,没什么好说的。有兴趣的,去看一个大神写的博客

接下来,开始讲解。(自认为 看完下面讲,以后类似这种圆环,都能自己画)

牢记:度数的正负,是表示方向。正度数:顺时针;负度数,逆时针。记住这句话,再往下看

1、移动画布原点。这个操作可有可无,根据自己的需求来。我是进行了移动。
在此之前,了解下坐标系:一个控件,在绘制的时候,原点默认是左上角,则:这个时候,控件的中心坐标,就是 (vW/2,vH/2) 。vW、vH 分别是控件的宽高,下同
画布坐标系默认是这样的:

现在,我对坐标系进行移动。(save、restore是配套使用的,千万注意。关于这2个方法的理解,我也说不好,请自行调研学习)

释放完成(restore)后,画布角度恢复正常(正右方向为0度,顺时针为正度数)、坐标原点为控件左上角

canvas?.save()
canvas?.translate(vW / 2f, vH / 2f)//移动画布坐标原点到控件中心
.....//操作
canvas?.restore()

移动完成后,控件的坐标系,就变成了

我这里移动,是为了绘制方便,实际中是否需要移动,根据项目、功能需要自行决定

2、确定角度。绘制圆环(或者圆),肯定要有个起点,然后沿着自己想要的方向进行绘制。要确定自己想要的方向,就一定要知道系统的默认方向。(通过上面的坐标系,也应该猜出来了,水平向右是正方向,顺时针为正角度,验证下)(关于圆环的绘制,后面会讲)

 canvas?.save();
//移动画布坐标原点到控件中心
canvas?.translate(vW / 2f, vH / 2f);
//绘制一个背景圆
canvas?.drawCircle(0f, 0f, radius, bgCirclePaint!!)
//绘制一个圆环。起点是0度,扫过45度,正方向
canvas?.drawArc(rectF!!, 0f, 45f, false, 画笔)
canvas?.restore();


绘制了一个圆环:起点是0度位置,方向是正方向(角度为正)。由此可知:系统默认是0度起点是 水平向右,默认正方向,是顺时针。

3、确定线段宽度及线段中心。因为绘制圆环的画笔比较粗(20像素,不同的项目、功能,可能会更粗)

private var linePaint_1: Paint? = null
private var linePaint_2: Paint? = nulllinePaint_1 = Paint(Paint.ANTI_ALIAS_FLAG)
linePaint_1?.style = Paint.Style.STROKE
linePaint_1?.color = Color.parseColor("#55ff0000")
linePaint_1?.strokeWidth = 20f //画笔的宽度(粗细)是 20像素linePaint_2 = Paint(Paint.ANTI_ALIAS_FLAG)
linePaint_2?.style = Paint.Style.STROKE
linePaint_2?.color = Color.parseColor("#000000")
linePaint_2?.strokeWidth = 1f //画笔的宽度(粗细)是1像素canvas?.save();
canvas?.translate(vW / 2f, vH / 2f)//过控件中心,在竖直方向,画一条线。粗bi
canvas?.drawLine(0f,-vH/2f,0f,vH/2f,linePaint_1!!)
//过控件中心,在竖直方向,画一条线。细笔
canvas?.drawLine(0f,-vH/2f,0f,vH/2f,linePaint_2!!)
//偏离竖直中心线 10像素(粗笔宽度的一半),画一条线
canvas?.drawLine(10f,-vH/2f,10f,vH/2f,linePaint_2!!)canvas?.restore()


由此可知:在指定位置画线(曲线同理),画笔的中心在指定位置上。或者说:指定位置的点,平分了画笔的宽度

4、圆环绘制方法(代码)

canvas?.drawArc(rectF!!, 0f, 45f, false, lanQiuCirclePaint!!)

这里的5个参数,必须知道各自的含义及用法
第一参数:矩形。指定一个矩形,矩形的边和圆环(画笔粗细的中心)相切(数学上的知识点)
第二个参数:起点。即:从哪个度数开始绘制
第三个参数:扫过的角度。这里千万、千万、千万注意,是
扫过的角度,不是终点所在角度
第四个参数:useCenter(是否用中点)。下面会详细说
第五个参数:画笔

接下来依次说明上面的前4个参数(第五个画笔没必要说明)
1、说明第一个参数,我先绘制一个圆环,范围是 -30度 到 +30度:圆环线段的粗细是20像素,矩形我用1像素的边线

//半径。根据布局文件,控件的宽高是 200dp。20是圆环画笔宽度。应该10就够,我这里往回缩了一点
radius = UiUtils.dip2px(mContext, 100f) - 20f
----------
canvas?.save();//移动画布坐标原点到控件中心
canvas?.translate(vW / 2f, vH / 2f);//绘制一个背景圆
canvas?.drawCircle(0f, 0f, radius, bgCirclePaint!!)
//起点是 -30度,扫过 +60 度的角度
canvas?.drawArc(rectF!!, -30f, 60f, false, lanQiuCirclePaint!!)canvas?.drawLine(-radius, -radius, radius, -radius, linePaint_2!!)
canvas?.drawLine(-radius, -radius, -radius, radius, linePaint_2!!)
canvas?.drawLine(radius, -radius, radius, radius, linePaint_2!!)
canvas?.drawLine(-radius, radius, radius, radius, linePaint_2!!)canvas?.restore();


注意下最右边,矩形的边,和圆环中心线,是相切的(相切位置,左右各10像素)。当然,这里特别说明是圆环中心线,是因为圆环比较粗。如果绘制了画笔宽度是1像素的圆环,就是圆环和矩形相切了。

2、说明第二个参数:这个其实没什么额外的说明,就是指定了,你要在哪个角度开始绘制
3、说明第三个参数:扫过的角度。这个是易错点。为了说明这个问题,我需要绘制2个首尾相接的圆环。
设:总数是5000。第一个圆环,表示1000;第二个表示 1500。总和是 2500(占总数一半)
1000 对应的度数占比是 360*(1000/5000)=72;1500 对应的度数占比是 360*(1500/5000) = 108。即:第一个圆环,要扫过 72度;第二个圆环,要扫过 108度

canvas?.drawArc(rectF!!, 0f, 72f, false, 画笔)
canvas?.drawArc(rectF!!, 72f, 108f, false, 画笔) //因为是首尾相接,第二个圆环的开始度数,就是第一个的结束度数


4、第四个参数说明:useCenter:是否使用中点。是一个 boolean 值,分别来看下,就知道差别了。以上的圆环展示图,都是 false。现在,来看下 true 的情况

canvas?.drawArc(rectF!!, 0f, 72f, true, 画笔)


好理解吧。useCenter 为true 就是表示 圆环的起点、终点 和中心连起来


知道了上面的几点,来分析下需求中的:圆环绘制的起点,是圆环顶部,逆时针依次绘制。
1、绘制的起点,要从顶部开始,就是竖直方向上的上方;2、绘制是逆时针的,也就是负度数

现在已经知道了,系统默认的 0度对应右边、顺时针为正度数。如果要实现 从顶部开始,逆时针绘制。那就是,起点是 -90度,后续的度数,都为 负数,且每次计算,都要注意 -90度。

能不能让顶部作为起点呢?
可以的,既然可以平移坐标系,那么,也是可以旋转画布的。把画布旋转个 -90度 或者 270 度,就行了。

canvas?.save();//移动画布坐标原点到控件中心
canvas?.translate(vW / 2f, vH / 2f);
//旋转画布。使得正上方向为0度。顺时针为正度数方向
canvas?.rotate(-90f)//canvas?.rotate(270f)canvas?.drawCircle(0f, 0f, radius, bgCirclePaint!!)canvas?.drawArc(rectF!!, 0f, -72f, true, lanQiuCirclePaint!!)canvas?.restore();


简单吧。接下来,只要注意不同圆环之间的首尾相接就行。前一个的终点度数,就是下一个的起点度数


绘制扇形

假设现在需求变了,要绘制扇形了。怎么办?
好办,扇形和圆环的区别,就是是否和中点有连线。如果中点和圆环的起点、终点链接起来了,不就是扇形么?
动手写下代码:

canvas?.save();//移动画布坐标原点到控件中心
canvas?.translate(vW / 2f, vH / 2f);
//旋转画布。使得正上方向为0度。顺时针为正度数方向
canvas?.rotate(-90f)//canvas?.rotate(270f)//绘制一个背景圆
canvas?.drawCircle(0f, 0f, radius, bgCirclePaint!!)canvas?.drawArc(rectF!!, 0f, -72f, true, lanQiuCirclePaint!!)canvas?.drawArc(rectF!!, -72f, -108f, true, zuQiuCirclePaint!!)canvas?.restore();


额,形状是扇形了,但是是空心的。不符合要求,需要实心才行。

看下画笔:

//篮球圆环画笔
lanQiuCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
lanQiuCirclePaint?.style = Paint.Style.STROKE
lanQiuCirclePaint?.color = Color.parseColor("#ff0000")
lanQiuCirclePaint?.strokeWidth = 20f

注意这句话:

lanQiuCirclePaint?.style = Paint.Style.STROKE

设置画笔风格、样式的。画笔有3种样式

1.Paint.Style.STROKE:描边
2.Paint.Style.FILL_AND_STROKE:描边并填充
3.Paint.Style.FILL:填充

既然要实心,那就改成

lanQiuCirclePaint?.style = Paint.Style.FILL


看出问题了吗?仅仅是填充了内部,但是没有描边。正确的做法是:

lanQiuCirclePaint?.style = Paint.Style.FILL_AND_STROKE

自定义圆环(扇形)绘制相关推荐

  1. Android 自定义 圆环,Android 实现自定义圆环

    用途说明: 这是一个自定义的圆环图像,支持动画展示,可以自定义圆环的颜色和占比,主要用以展示一些数据占比方面展示的android圆环. 圆环实现思路: android的自定义圆环实现有很多种方法,这里 ...

  2. 在WPF中自定义你的绘制(二)

    在WPF中自定义你的绘制(二) 原文:在WPF中自定义你的绘制(二)   在WPF中自定义你的绘制(二)                                                 ...

  3. HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序

    这期是 HenCoder 自定义绘制的第 1-5 期:绘制顺序 之前的内容在这里:  HenCoder Android 开发进阶 自定义 View 1-1 绘制基础  HenCoder Android ...

  4. 绘制圆形/圆环/扇形/扇面Mesh

    using UnityEngine; using System.Collections;[RequireComponent(typeof(MeshRenderer), typeof(MeshFilte ...

  5. Android 自定义 圆环,Android自定义View之酷炫圆环(二)

    先看下最终的效果 静态: 动态: 一.开始实现 新建一个DoughnutProgress继承View public class DoughnutProgress extends View { } 先给 ...

  6. 自定义view之绘制模拟时钟

    之前在自定义view之写一个带删除按钮的Edittext中简单介绍了如何继承Edittext实现点击区域删除全部文字. 在自定义view之可伸缩的圆弧与扇形中介绍了如何制作带有动画效果的圆弧和扇形图. ...

  7. android自动画线,Android自定义View——扇形统计图

    Android 扇形统计图 先看看效果: 看上去如果觉得还行就继续往下看吧! 效果图1 效果图2 自定义View 定义成员变量 private int mHeight, mWidth;//宽高 pri ...

  8. 【eoe教程】Android中自定义视图的绘制方法

    原文链接 :http://android.eoe.cn/topic/ui 自定义视图最重要的部分是它的外观.你可以根据应用的需求简单或复杂的实现它. 这个教程包含了最常见的操作. 重写onDraw() ...

  9. Android 自定义 圆环,Android自定义view实现圆环效果实例代码

    先上效果图,如果大家感觉不错,请参考实现代码. 重要的是如何实现自定义的view效果 (1)创建类,继承view,重写onDraw和onMesure方法 public class CirclePerc ...

最新文章

  1. LuoguP1948 电话线 【二分答案/图论】
  2. python-子类和派生、继承
  3. 汇编语言 判断学生成绩是否及格
  4. spring-boot-maven-plugin插件找不到含有main的主类
  5. python怎么去重_python列表如何去重
  6. systemctl命令完全指南
  7. python学习之--内置函数:
  8. er图-为什么画er图?有哪些规范?
  9. 深入理解JVM虚拟机之高效并发
  10. (1.5万字图文)解读华为集成产品开发IPD之市场管理流程(MM流程)
  11. 基于华为路由器实现NAT
  12. windows10专业版安装详细教程
  13. 2021武汉建港中学高考成绩查询,武汉2021年部分示范高中四月调考分数线(预估)...
  14. 求原谅---好久没更新了
  15. 中国.net域名网站的“前世今生”,那些年的光辉
  16. 2022淘宝天猫京东头部主播消失后的618没有头部主播怎么领618红包?
  17. 【C语言】a+aa+aaa+...+aa...a=?
  18. Springboot+vue调查问卷管理系统(带论文)
  19. GIS教程之 在 React 中创建 Openlayers 地图(教程含源码)
  20. 基于mongodb的标签系统设计

热门文章

  1. 计算机组成原理_Cache与主存的映射方式
  2. icepak中文学习教程
  3. 计算机函数Dmax怎么算,【Excel函数】DMAX函数 - 曹海峰个人博客
  4. 【ZUFE-校内竞赛】校内常见竞赛难度分类及参赛建议(浙江财经大学信智学院角度)
  5. 语义分割综述解读--主流方法篇
  6. 【轮子】发现一个效果丰富酷炫的Android动画库
  7. 怎样找回删除的照片?学会这4招,恢复超简单!
  8. Windows 7 连接 Windows 10 共享打印机,Windows 无法连接打印机,操作失败,错误为0x0000011b 的终极解决办法
  9. c#控件弹幕效果_Android 自定义View修炼-自定义弹幕效果View
  10. android baseadapter优化,2.4.6 BaseAdapter优化