功能
一种显示树形结构的图表,内圆是外圆的父节点,一层层显示绘制
效果图


使用示例
package com.eroad.product.tools;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class SunBurstChart extends View {
private float density; //屏幕密度
private float viewWidth; //控件宽度
private float viewHeight; //控件高度
private float pieCenterX, pieCenterY, pieRadius; //圆心X轴坐标,圆心Y轴坐标,圆半径
private Paint textPaint, piePaint, linePaint, criclePaint, ringPaint, inTextPaint;//文本画笔,扇形画笔,线画笔,内圆画笔,圆环画笔
private RectF pieOval; //矩形边界框
private String TAG = "SunBurstChart";
private float textSize = 14; //字体大小
private float cricle; //内圆半径
//扇形的弧度
private ArrayList<Float> arraydegrees;
private List<Point> inPoints;//内圆用以记录每个矩形的度数
//内圆的饼图
private RectF pieInOval; //内圆矩形边界框
private float inTotalValue; //总数
//内圆扇形的弧度
private ArrayList<Float> inArraydegrees;
private List<Nodes> mDatas;
private boolean animate = false;
private int depth = 1;
private Path textPath;
private float ringWidth = 3f;//圆环宽度
private float inTextSize = 12; //内圆字体大小
int[] colors = {Color.parseColor("#0288D1"), Color.parseColor("#F8BBD0"), Color.parseColor("#D4E157"), Color.parseColor("#FF6E40")}; //颜色值
public SunBurstChart(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public SunBurstChart(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
//获取屏幕密度
Display display = ((Activity) context).getWindowManager().getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
density = displayMetrics.density;
}
public SunBurstChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
//获取控件的高度和宽度
viewWidth = getWidth();
viewHeight = getHeight();
this.init();
drawNormal(canvas);
}
private float animatedValue;//圆环动画
private void initAnimator() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 360);
anim.setDuration(5000);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animatedValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.start();
}
public void startDraw() {
if (mDatas.size() > 0 && animate) {
initAnimator();
} else {
postInvalidate();
}
}
/**
* 没有任何动画的圆
*
* @param canvas
*/
public void drawNormal(Canvas canvas) {
float start = 0.0f;
if (mDatas == null) {
return;
}
drawSunBurstChart(canvas);
}
Random random = new Random();
private void drawSunBurstChart(Canvas canvas) {
float start = 0.0f;
int level = 2;
int lastIndex = 0;
for (int i = 0; i < mDatas.size(); i++) {
Nodes parentNode = mDatas.get(i);
double value = parentNode.getValue();
String title = parentNode.getName();
int index = random.nextInt(colors.length - 1);
if (index == lastIndex) {
index = random.nextInt(colors.length - 1);
}
int color = colors[index];
float sweep = (float) (value / inTotalValue * 360);//计算度数
inArraydegrees.add(sweep);
drawChild(start, canvas, sweep, parentNode.getChildNodes(), level);
drawInCircleAndLine(i, canvas, start, sweep, value, title, color);
ringPaint.setStrokeWidth(ringWidth); //设置圆环的宽度
canvas.drawCircle(pieCenterX, pieCenterY, cricle, ringPaint); //画出圆环
inPoints.get(i).x = (int) start;
inPoints.get(i).y = (int) (start) + (int) sweep;
start += sweep;
lastIndex = index;
}
}
private void drawChild(float start, Canvas canvas, double angle, List<Nodes> childNodes, int level) {
double childTotalValue = 0;
for (int i = 0; i < childNodes.size(); i++) {
Nodes child = childNodes.get(i);
double value = child.getValue();
childTotalValue += value;
}
for (int i = 0; i < childNodes.size(); i++) {
Nodes child = childNodes.get(i);
double value = child.getValue();
String title = child.getName();
float sweep = (float) (value / childTotalValue * angle);//计算度数
if (child.hasChild()) {
float tempstart = start;
drawChild(tempstart, canvas, sweep, child.getChildNodes(), level + 1);
}
int color = colors[random.nextInt(colors.length - 1)];
arraydegrees.add(sweep);
piePaint.setColor(color);//设置颜色
pieOval = new RectF(pieCenterX - cricle * level, pieCenterY - cricle * level, pieCenterX + cricle * level, pieCenterY + cricle * level);
if (Math.min(sweep, animatedValue - start) >= 0 && animate) {
canvas.drawArc(pieOval, start, Math.min(sweep - 1, animatedValue - start), true, piePaint);//画扇形
} else {
canvas.drawArc(pieOval, start, sweep, true, piePaint);//画扇形
canvas.drawArc(pieOval, start, sweep, true,
ringPaint);
}
float radius = cricle * level - cricle / 2f;
int middleAngle = (int) (start + sweep / 2);
float textPointX = (float) (pieOval.centerX() + radius * Math.cos(Math.toRadians(middleAngle)));
float textPointY = (float) (pieOval.centerY() + radius * Math.sin(Math.toRadians(middleAngle)));
float textPointEndX = (float) (pieInOval.centerX() + cricle * (level - 1) * Math.cos(Math.toRadians(middleAngle)));
float textPointEndY = (float) (pieInOval.centerY() + cricle * (level - 1) * Math.sin(Math.toRadians(middleAngle)));
inTextPaint.setTextAlign(Paint.Align.LEFT);
textPath.reset();
textPath.moveTo(textPointX, textPointY);
textPath.lineTo(textPointEndX, textPointEndY);
canvas.drawTextOnPath(title, textPath, 0, 0, inTextPaint);
start += sweep;
}
}
/**
* 内圆正常画圆和画线
*
* @param i
* @param canvas
* @param start
* @param sweep
*/
public void drawInCircleAndLine(int i, Canvas canvas, float start, float sweep, double value, String title, int color) {
criclePaint.setColor(color);//设置颜色
if (animate) {
canvas.drawArc(pieInOval, start, Math.min(sweep, animatedValue - start), true, criclePaint);//画扇形
} else {
canvas.drawArc(pieInOval, start, sweep, true, criclePaint);//画扇形
canvas.drawArc(pieInOval, start, sweep, true, ringPaint);//画边框
}
// 绘制圆环上扇形的文字
int middleAngle = (int) (start + sweep / 2);
float radius = (cricle) / 3f;
float textPointX = (float) (pieInOval.centerX() + radius * Math.cos(Math.toRadians(middleAngle)));
float textPointY = (float) (pieInOval.centerY() + radius * Math.sin(Math.toRadians(middleAngle)));
float textPointEndX = (float) (pieInOval.centerX() + cricle * Math.cos(Math.toRadians(middleAngle)));
float textPointEndY = (float) (pieInOval.centerY() + cricle * Math.sin(Math.toRadians(middleAngle)));
inTextPaint.setTextAlign(Paint.Align.LEFT);
textPath.reset();
textPath.moveTo(textPointX, textPointY);
textPath.lineTo(textPointEndX, textPointEndY);
canvas.drawPath(textPath, linePaint);
canvas.drawTextOnPath(title, textPath, 0, 0, inTextPaint);
}
//初始化画笔和画刷
public void init() {
float min = Math.min(viewWidth, viewHeight);//获取屏幕中高宽中的最小值,用以确认圆心位置
pieCenterX = viewWidth / 2;
pieCenterY = viewHeight / 2;
pieRadius = 2 * min / 5;//获得饼图的半径
cricle = pieRadius / depth;//内圆的半径
textSize = 12 * density;//获取字体的大小
//得到饼图的矩形
pieOval = new RectF(pieCenterX - cricle, pieCenterY - cricle, pieCenterX + cricle, pieCenterY + cricle);
//初始化文字的画笔
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextSize(textSize);
textPaint.setColor(Color.parseColor("#939393"));// 颜色
//初始化饼图的画笔
piePaint = new Paint();
piePaint.setAntiAlias(true);
piePaint.setStyle(Paint.Style.FILL);
//初始化线的画笔
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setStyle(Paint.Style.FILL);
linePaint.setStrokeWidth(1);
linePaint.setColor(Color.parseColor("#939393"));// 颜色
//初始化内圆的画笔
criclePaint = new Paint();
criclePaint.setAntiAlias(true);
criclePaint.setStyle(Paint.Style.FILL);
criclePaint.setColor(Color.parseColor("#ffffff"));
pieInOval = new RectF(pieCenterX - cricle, pieCenterY - cricle, pieCenterX + cricle, pieCenterY + cricle);
ringPaint = new Paint();//圆环画笔
ringPaint.setColor(Color.parseColor("#ffffff")); //设置圆环的颜色
ringPaint.setStyle(Paint.Style.STROKE); //设置空心
ringPaint.setAntiAlias(true); //消除锯齿
inTextPaint = new Paint();//初始化内圆的字体画笔
inTextPaint.setAntiAlias(true);
inTextPaint.setStyle(Paint.Style.FILL);
inTextPaint.setTextSize(inTextSize);
inTextPaint.setColor(Color.parseColor("#ffffff"));// 颜色
arraydegrees = new ArrayList<Float>();
inArraydegrees = new ArrayList<Float>();
//饼图放大部分的矩形
textPath = new Path();
}
/**
* 设置数据源
*
* @param nodes
*/
public void setDatas(List<Nodes> nodes) {
mDatas = nodes;
inPoints = new ArrayList<>();
inPoints = new ArrayList<>();
for (Nodes node : nodes) {
inTotalValue += node.getValue();
Point point = new Point();
inPoints.add(point);
countNodeDepth(node.getChildNodes());
}
Log.i(TAG, "setDatas: " + depth);
postInvalidate();//刷新
}
int depth1 = 2;
public int countNodeDepth(List<Nodes> nodes) {
for (Nodes node : nodes) {
if (node.hasChild()) {
depth1++;
countNodeDepth(node.getChildNodes());
} else {
depth = Math.max(depth1, depth);
depth1 = 2;
continue;
}
}
return depth1;
}
}
数据结构
package com.eroad.product.tools;
import java.util.List;
/**
* 节点实体类
* @author Administrator
*
*/
public class Nodes {
private String id;
//父节点
private String pid;
private String name;
private double value;
List<Nodes> childNodes;
private boolean hasChild;
public Nodes(String id, String pid, String name, double value) {
this.id = id;
this.pid = pid;
this.name = name;
this.value = value;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Nodes> getChildNodes() {
return childNodes;
}
public void setChildNodes(List<Nodes> childNodes) {
this.childNodes = childNodes;
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
public boolean hasChild() {
return !(childNodes == null || childNodes.size() == 0);
}
}
测试代码
package com.example.linecharttest.tab;
import com.eroad.product.tools.Nodes;
import com.eroad.product.tools.SunBurstChart;
import com.example.linecharttest.R;
import android.app.Activity;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
/**
* 可点击的饼状图
* @author wanjuanjuan
*
*/
public class TestActivity extends Activity{
private SunBurstChart ringview;
private String TAG = "TestActivity"; //TAG
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ring);
ringview = (SunBurstChart) findViewById(R.id.ring);
List<Nodes> parentNodes = new ArrayList<Nodes>();
parentNodes.add(new Nodes("1","1","udp",2112));
parentNodes.add(new Nodes("2","2","tcp",2000));
parentNodes.add(new Nodes("3","3","其他",2000));
parentNodes.add(new Nodes("4","4","更多",1000));
for (int i= 0;i<parentNodes.size();i++){
Nodes parentNode = parentNodes.get(i);
List<Nodes> childNodes = new ArrayList<Nodes>();
if(parentNode.getId().equals("1")){
childNodes.add(new Nodes("5","1","餐饮",250));
childNodes.add(new Nodes("6","1","娱乐",100));
childNodes.add(new Nodes("7","1","学习",120));
}
if(parentNode.getId().equals("2")){
childNodes.add(new Nodes("9","2","餐饮",200));
childNodes.add(new Nodes("10","2","娱乐",100));
}
if(parentNode.getId().equals("3")){
childNodes.add(new Nodes("11","3","餐饮",300));
childNodes.add(new Nodes("12","3","学习",120));
}
if(parentNode.getId().equals("4")){
childNodes.add(new Nodes("13","4","餐饮",100));
childNodes.add(new Nodes("14","4","娱乐",100));
childNodes.add(new Nodes("15","4","人际关系",160));
}
parentNode.setChildNodes(childNodes);
}
for (int i= 0;i<parentNodes.size();i++){
Nodes parentNode = parentNodes.get(i);
List<Nodes> childNodes = parentNode.getChildNodes();
for (int j= 0;j<childNodes.size();j++){
Nodes childNode = childNodes.get(j);
List<Nodes> childchildNodes = new ArrayList<Nodes>();
String id = j*10+"";
childchildNodes.add(new Nodes(id, childNode.getId(), "餐饮", 200));
childchildNodes.add(new Nodes(id, childNode.getId(), "娱乐", 100));
childNode.setChildNodes(childchildNodes);
}
}
for (int i= 0;i<parentNodes.size();i++){
Nodes parentNode = parentNodes.get(i);
List<Nodes> childNodes = parentNode.getChildNodes();
for (int j= 0;j<childNodes.size();j++){
List<Nodes> childchildNodes = childNodes.get(j).getChildNodes();
for (int k= 0;k<childchildNodes.size();k++){
Nodes childchildNode = childchildNodes.get(k);
List<Nodes> childchildchildNodes = new ArrayList<Nodes>();
String id = k*10+"";
childchildchildNodes.add(new Nodes(id, childchildNode.getId(), "餐饮", 20));
childchildchildNodes.add(new Nodes(id, childchildNode.getId(), "娱乐", 10));
childchildchildNodes.add(new Nodes(id, childchildNode.getId(), "学习", 12));
childchildNode.setChildNodes(childchildchildNodes);
}
}
}
ringview.setDatas(parentNodes);
ringview.startDraw();
}
}
正在开发的功能
动画,手势放大,显示优化(现在还有点丑)
- 目前还是初步开发阶段,后面慢慢完善,给个start鼓励鼓励我吧
项目地址,欢迎star