一、数据需求

来分析下,用户需要提供怎样的数据,首先要有数据的值,然后还需要对应的数据名称,以及颜色。绘制PieChart需要什么呢,由图可以看出,需要百分比值,扇形角度,色块颜色。所以总共属性有:

[代码]java代码:1

2

3

4

5

6

7public class PieData {

private String name;

private float value;

private float percentage;

private int color = 0;

private float angle = 0;

}

各属性的set与get请自行增加。

二、构造函数

构造函数中,增加一些xml设置,创建一个attrs.xml

[代码]xml代码:1

2

3

4

5

6

7

8<?xml version="1.0" encoding="utf-8"?>

这是只设置了一部分属性,如果你有强迫症希望全部设置的话,可以自行增加。在PieChart中使用TypedArray进行属性的获取。建议使用如下的写法,可以避免在没有设置属性时,也运行getXXX方法。

[代码]java代码:1

2

3TypedArray array =   context.obtainStyledAttributes(attrs, R.styleable.PieChart,   defStyleAttr,defStyleRes);

int n = array.getIndexCount();

for (int i=0; i

三、动画函数

绘制一个完整的圆,旋转的角度为360,动画时间为可set参数,默认5秒,监听animatedValue参数,用于与绘制时进行计算。

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17private void initAnimator(long duration){

if (animator !=null &&animator.isRunning()){

animator.cancel();

animator.start();

}else {

animator=ValueAnimator.ofFloat(0,360).setDuration(duration);

animator.setInterpolator(timeInterpolator);

animator.addUpdateListener(new   ValueAnimator.AnimatorUpdateListener() {

@Override

public   void onAnimationUpdate(ValueAnimator animation) {

animatedValue   = (float) animation.getAnimatedValue();

invalidate();

}

});

animator.start();

}

}

四、onMeasure

View默认的onMeasure方法中,并没有根据测量模式,对布局宽高进行调整,所以为了适应wrap_content的布局设置,需要对onMeasure方法进行重写。

[代码]java代码:1

2

3

4

5protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)   {

int width =   measureDimension(widthMeasureSpec);

int height =   measureDimension(heightMeasureSpec);

setMeasuredDimension(width,height);

}

重写的onMeasure方法,调用了自定义的measureDimension方法处理数据,完成后交给系统的setMeasuredDimension方法。接下来看下自定义的measureDimension方法。

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18private int measureDimension(int measureSpec){

int size = measureWrap(mPaint);

int specMode =   MeasureSpec.getMode(measureSpec);

int specSize =   MeasureSpec.getSize(measureSpec);

switch (specMode){

case MeasureSpec.UNSPECIFIED:

size   = measureWrap(mPaint);

break;

case MeasureSpec.EXACTLY:

size   = specSize;

break;

case MeasureSpec.AT_MOST:

//合适尺寸不得大于View的尺寸

size   = Math.min(specSize,measureWrap(mPaint));

break;

}

return size;

}

measureDimension根据测量的类型,分别计算尺寸的长度。EXACTLY是在xml中定义match_parent以及具体的数值是使用,而AT_MOST则是在wrap_content时使用,measureWrap方法用于计算当前PieChart的最小合适长度,接下来看看这个方法。

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35protected void onSizeChanged(int w, int h, int oldw,   int oldh) {

super.onSizeChanged(w, h, oldw,   oldh);

mWidth =   w-getPaddingLeft()-getPaddingRight();//适应padding设置

mHeight =   h-getPaddingTop()-getPaddingBottom();//适应padding设置

mViewWidth = w;

mViewHeight = h;

//标准圆环

//圆弧

r = (float)   (Math.min(mWidth,mHeight)/2*widthScaleRadius);// 饼状图半径

// 饼状图绘制区域

rectF.left = -r;

rectF.top = -r;

rectF.right =r;

rectF.bottom = r;

//白色圆弧

//透明圆弧

rTra = (float)   (r*radiusScaleTransparent);

rectFTra.left = -rTra;

rectFTra.top = -rTra;

rectFTra.right = rTra;

rectFTra.bottom = rTra;

//白色圆

rWhite = (float) (r*radiusScaleInside);

//浮出圆环

//圆弧

// 饼状图半径

rF = (float)   (Math.min(mWidth,mHeight)/2*widthScaleRadius*offsetScaleRadius);

// 饼状图绘制区域

rectFF.left = -rF;

rectFF.top = -rF;

rectFF.right = rF;

rectFF.bottom = rF;

...

}

测量宽高的方式类似于TextView,根据PieChart中的图名与百分比文本的宽度进行计算的。其中stringId是在处理数据的过程中,计算出的拥有最长字符的区域Id。

从代码中可以看出,wrap_content情况下的,PieChart的宽高就等于百分比字符长度的4倍,加上图名的长度。

五、onSizeChanged

在此函数中,获取当前View的宽高以及根据padding值计算出的实际绘制区域的宽高,同时进行PieChart绘制所需的半径以及布局位置设置。

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35protected void onSizeChanged(int w, int h, int oldw,   int oldh) {

super.onSizeChanged(w, h, oldw,   oldh);

mWidth =   w-getPaddingLeft()-getPaddingRight();//适应padding设置

mHeight =   h-getPaddingTop()-getPaddingBottom();//适应padding设置

mViewWidth = w;

mViewHeight = h;

//标准圆环

//圆弧

r = (float)   (Math.min(mWidth,mHeight)/2*widthScaleRadius);// 饼状图半径

// 饼状图绘制区域

rectF.left = -r;

rectF.top = -r;

rectF.right =r;

rectF.bottom = r;

//白色圆弧

//透明圆弧

rTra = (float)   (r*radiusScaleTransparent);

rectFTra.left = -rTra;

rectFTra.top = -rTra;

rectFTra.right = rTra;

rectFTra.bottom = rTra;

//白色圆

rWhite = (float)   (r*radiusScaleInside);

//浮出圆环

//圆弧

// 饼状图半径

rF = (float)   (Math.min(mWidth,mHeight)/2*widthScaleRadius*offsetScaleRadius);

// 饼状图绘制区域

rectFF.left = -rF;

rectFF.top = -rF;

rectFF.right = rF;

rectFF.bottom = rF;

...

}

六、onDraw

onDraw分为绘制扇形,绘制文本,绘制图名三个部分。绘制扇形和文本时需要与Valueanimator的监听值进行计算,完成动画;另外还要在Touch时进行交互,完成浮出动画。

在进行具体的绘制之前,需要坐标原点平移至中心位置,并且判断数据是否为空。

1、绘制扇形

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17float currentStartAngle = 0;// 当前起始角度

canvas.save();

canvas.rotate(mStartAngle);

float drawAngle;

for (int i=0; i=0){

drawAngle   = Math.min(pie.getAngle()-1,animatedValue-currentStartAngle);

}else {

drawAngle   = 0;

}

if (i==angleId){

drawArc(canvas,currentStartAngle,drawAngle,pie,rectFF,rectFTraF,reatFWhite,mPaint);

}else {

drawArc(canvas,currentStartAngle,drawAngle,pie,rectF,rectFTra,rectFIn,mPaint);

}

currentStartAngle +=   pie.getAngle();

}

canvas.restore();

·         根据当前的初始角度旋转画布。初始化扇形的起始角度,通过累加计算出下一次的起始角度。

·         drawArc用于绘制扇形,和上一篇最后的环形图片一样,通过一大一小两个扇形进行补集运算,获得可知半径的及宽度的圆环,只不过这里多了一个为了立体效果而增加的半透明圆弧。

·         绘制扇形时,使用当前的动画值减去起始角度与当前的扇形经过的角度对比取小,作为当前扇形的需要绘制的经过角度。减1是为了生存扇形区域之间的间隔。

·         angleId用于Touch时显示点击是哪一块扇形,具体判断会在TouchEvent中进行。

2、绘制文本

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13//扇形百分比文字

currentStartAngle = mStartAngle;

for (int i=0; ipieAngles[i]-pie.getAngle()/2&&percentFlag)   {

if (i   == angleId) {

drawText(canvas,pie,currentStartAngle,numberFormat,true);

} else {

if   (pie.getAngle() > minAngle) {

drawText(canvas,pie,currentStartAngle,numberFormat,false);

}

}

currentStartAngle   += pie.getAngle();

}

}

* 文本是有方向的,无法在画布旋转后绘制,所以初始化当前扇形的起始角度为PieChart的起始角度。

* 然后循环绘制文本,当扇形绘制到当前区域的1/2时,开始绘制当前区域的文字。为了防止文本遮挡视线,在绘制前需要判断此扇形经过的角度是否大于最小显示角度。

* angleId用于Touch时显示点击是哪一块扇形,具体判断会在TouchEvent中进行。

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13private void drawText(Canvas canvas, PieData pie ,float   currentStartAngle, NumberFormat numberFormat,boolean flag){

int textPathX = (int)   (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r +   rTra) / 2);

int textPathY = (int)   (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r +   rTra) / 2);

mPoint.x = textPathX;

mPoint.y = textPathY;

String[] strings;

if (flag){

strings   = new String[]{pie.getName() + "",   numberFormat.format(pie.getPercentage()) + ""};

}else {

strings   = new String[]{numberFormat.format(pie.getPercentage()) + ""};

}

textCenter(strings, mPaint,   canvas, mPoint, Paint.Align.CENTER);

}

drawText函数的主要作用就是根据传入的Pie,获取大小扇形的半径合除以2,角度取一半,计算出扇形中心点,然后进行文本绘制。最后累加当前扇形的起始角度,用于下一个扇形使用。

3、绘制图名

[代码]java代码:1

2

3

4

5

6

7

8

9//饼图名

mPaint.setColor(centerTextColor);

mPaint.setTextSize(centerTextSize);

mPaint.setTextAlign(Paint.Align.CENTER);

//根据Paint的TextSize计算Y轴的值

mPoint.x=0;

mPoint.y=0;

String[] strings = new String[]{name+""};

textCenter(strings,mPaint,canvas,mPoint, Paint.Align.CENTER);

绘制图名的部分就比较简单了,和之前绘制单个Pie时类似,获取x,y坐标为(0,0),然后使用textCenter多行文本绘制函数进行文本绘制。

七、onTouchEvent

onTouchEvent用于处理当前的点击事件,具体内容在第一篇文章中已经进行了说明,这里使用其中的ACTION_DOWN与ACTION_UP事件。

[代码]java代码:01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33public boolean onTouchEvent(MotionEvent event) {

if (touchFlag&&mPieData.size()>0){

switch (event.getAction()){

case   MotionEvent.ACTION_DOWN:

float   x = event.getX()-(mWidth/2);

float   y = event.getY()-(mHeight/2);

float   touchAngle = 0;

if   (x<0&&y<0){

touchAngle   += 180;

}else   if (y<0&&x>0){

touchAngle   += 360;

}else   if (y>0&&x<0){

touchAngle   += 180;

}

touchAngle   +=Math.toDegrees(Math.atan(y/x));

touchAngle   = touchAngle-mStartAngle;

if   (touchAngle<0){

touchAngle   = touchAngle+360;

}

float   touchRadius = (float) Math.sqrt(y*y+x*x);

if   (rTra< touchRadius && touchRadius< r){

angleId   = -Arrays.binarySearch(pieAngles,(touchAngle))-1;

invalidate();

}

return   true;

case   MotionEvent.ACTION_UP:

angleId   = -1;

invalidate();

return   true;

}

}

return super.onTouchEvent(event);

}

·         运行之前需要判断PieChart是否开启了点击效果,同时需要判断数据不为空。

·         在用户点击下的时候,获取当前的坐标,计算出这个点与原点的距离以及角度。通过距离可以判断出是否点击在了扇形区域上,而通过角度可以判断出点击了哪一个区域。将判断出的区域Id传递给angleId值,就像我们之前在onDraw中说的那样,重新绘制,根据angleId浮出指定的扇形区域。

·         用户手指离开屏幕时,重置angleId为默认值,并使用invalidate()函数,重新绘制onDraw中变化的部分。

八、小结

经过之前4篇的知识准备,终于迎来了本章的PieChart的具体实现。在本文中重温了之前的绘制流程的各个函数,VlaueAnimator函数,以及Canvas、Path的使用方法,并使用这些方法完成了一个自定义饼图的绘制。

java绘图扇形_PieChart扇形图的实现相关推荐

  1. Android自定义View之扇形饼状图

    前言:继上次写了自定义圆形进度条后,今天给大家带来自定义扇形饼状图.先上效果图: 是不是很炫?看上去还有点立体感.下面带大家一起来瞧一瞧吧. 一.定义成员变量,重写构造方法 看着这个效果图,我们可以想 ...

  2. Java语言学习思维导图

    Java语言学习思维导图

  3. Java绘图之设置字型和颜色

    Java绘图中,显示文字的方法主要有三种: (1)drawString(String str,int x,int y):在指定的位置显示字符串. (2)drawChars(char data[],in ...

  4. python二维图颜色函数_Python绘图之二维图与三维图详解

    各位工程师累了吗? 推荐一篇可以让你技术能力达到出神入化的网站"持久男" 1.二维绘图 a. 一维数据集 用 Numpy ndarray 作为数据传入 ply 1. import ...

  5. ComplexHeatmap |理解绘图逻辑绘制热图

    作者:严涛 浙江大学作物遗传育种在读研究生(生物信息学方向)伪码农,R语言爱好者,爱开源. 之前热图三部曲介绍了使用ggplot2和pheatmp绘制热图 R语言学习 - 热图绘制 (heatmap) ...

  6. IDEA查看Java类的UML关系图

    1.说明 通过IDEA自带的Diagarm功能, 可以方便的查看Java类的UML关系图, 同时能有选择的查看变量.方法和构造器等, 以及对查看对象的访问权限进行过滤, 可以自由编辑生成的关系图, 任 ...

  7. 【零基础学Java】—对象的内存图(八)

    [零基础学Java]-对象的内存图(八) 一.一个对象的内存图 二.两个对象的内存图 三.使用对象类型作为方法的参数 public class PhoneParam {public static vo ...

  8. Java最全思维导图知识汇总

    本篇主要总结java知识思维导图框架,总结内容从基础到高级,到Java开发.可供Java学习者学习. Java知识总结: 01.Java程序设计(基础) 02.Java程序设计(专题) 03.客户端网 ...

  9. java绘图- 绘图用法(基于Graphics2D)

    java绘图(基于Graphics2D) 1.绘图基本操作 请参考下面基础示例: 1 int width = 200, height = 250;2 //创建图片对象3 BufferedImage i ...

最新文章

  1. TOMCAT6中一个警告“Parameters:Invalid chunk ignored ”
  2. SqlServer表死锁的解决方法
  3. quartus中pin planner中分配引脚的对话框不见了,怎么找回(附方法)
  4. 三维匹配_机器视觉——双目视觉的基础知识(视差深度、标定、立体匹配)
  5. 【日常小记】linux中强大且常用命令:find、grep
  6. 【报错笔记】Navicat连接数据库显示2003错误,无法连接到数据库
  7. codeforces contest 1140(D~G)
  8. 新的一年,碎片化学习前端,我推荐这几个公众号~
  9. Python错误:TypeError: string indices must be integers
  10. Android 开发者们,如何使用 Python 来扩展 adb 命令?
  11. LeetCode-21.合并两个有序链表(链表+递归)
  12. TL摄像头如何放到html去直播,使用flash插件来调用pc的摄像头如何将它嵌入到TML页面中...
  13. 3DSMAX制作超时空未来动画场景-3D建模场景模型教程
  14. 谢霆锋断言暂不再婚 赞张柏芝教子有方
  15. 计算机主机cpu内存,两分钟看懂计算机中CPU、内存、硬盘的工作原理
  16. SQL语句按照姓名首字母排序
  17. fatal unable to auto-detect email address (got ‘...@...(none)‘)
  18. Lenovo T460 Fn功能键切换
  19. 智能车竞赛技术报告 | 智能视觉组 - 北京科技大学智能视觉组
  20. 20条最狠的潜规则!读完又爱又恨!

热门文章

  1. 电脑如何高效工作——软件介绍
  2. HMDI中视频消隐解释
  3. [论文笔记]彻底讲透U-net医学影像分割-小样本
  4. php 工商银行公众号支付代码_php实现工商银行在线支付接口
  5. Oracle数据库基础3-常用函数与技巧
  6. 数据结构期末考试复习知识点
  7. ecshop怎么写原生php,ecshop模板中直接写php的方法
  8. Android游戏-愤怒的小鸟(Android studio)
  9. Git学习——Git基本工作原理(入门级教程,通过玩转Git本地仓库,帮助新手快速入手Git)
  10. 催款锁机程序信捷12轴设备程序一共十级密码到时间锁机 含一屏多机和到时间锁机程序