资产饼状图

前言:

最近需要项目中需要使用到饼状图呈现用户的账户资金相关信息,于是顺手写了一个。

 

 

效果图:

 资产饼状图_第1张图片

 

 

主要思路:

环形饼状图实际上是多个扇形拼接成的,最后在中间覆盖白色圆,达到一个空心的效果。区块之间的间隔则是固定的一个角度偏移值。

 

涉及的主要类:

PIEChatView.class      该类继承自View,为控件的主体类

PIEChatUtils.class      自定义工具类,主要是浮点数加减乘除的封装,保留小数位数,确保计算时不出异常

PIEItem.class          数据实体类,控件使用的是ArrayList的集合

DeviceUtil.class        自定义工具类,获取屏幕相关信息,例如屏幕宽高

StringUtil.class         自定义工具类,做数字金额的转换,例如‘2.0’转换成‘2.00元’

MainActivity.class      程序入口,用于测试控件

 

 

相关类详细介绍:

PIEChatView.class全部代码:

package cn.com.cg.piechatview;

 

import java.util.ArrayList;

import android.annotation.SuppressLint;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.LinearGradient;

import android.graphics.Paint;

import android.graphics.RectF;

import android.graphics.Shader;

import android.os.Handler;

import android.graphics.Paint.Style;

import android.util.AttributeSet;

import android.view.View;

import cn.com.core.util.DeviceUtil;

import cn.com.core.util.StringUtil;

 

/**

 * 饼状图控件,包括相关文字描述信息

 * 主体概括:将圆形饼状图放置在屏幕右侧,文字描述放在左侧,同时限制文字描述的长度,防止文字超长导致遮盖现象

 * 设计思路:

 * 1.环形饼状图的绘制其实是多个扇形的拼接过程,中间的白色圆实际上是最后覆盖上去的,这里实际上只需要计算扇形的起始角度和便宜角度即可

 * 2.左边的描述无非时在指定的坐标上绘制圆形或者文字

 * 控件说明:

 * 1.展示多种类型的资金,根据资金占比的不同,在饼状图上呈现不同的区块

 * 2.最多限制五种类型,当大于五种类型时,会将第五种及后续的类型合并为一种类型,取名其他,并将这些类型的金额一起合并为其他类型的总金额

 *      3.当前饼状图做了一个渲染的动态效果,即:将任意区块分成50等份,通过handler的延时达到动态绘制的效果

 *      4.当前饼状图要求传入的数据类型为一个ArrayList类型的集合,PIEItem为单个类型的实体,name属性为描述,value属性为数值

 *      5.xml布局文件引用该控件时,设置的widthheight对应的是饼状图的半径,例如设置为50dp,如果设置为match_parent或者wrap_content,则默认直径为屏幕宽度一半减去左右间距

 * @author chenguo

 *

 */

public class PIEChatView extends View {

 

private Context context;//上下文对象

private String xmlW;//xml布局中设定的饼状图宽度

private String xmlH;//xml布局中设定的饼状图高度

 

private int screenWidth;//屏幕宽度

 

private int screenHeight;//屏幕高度

 

private double outerR;//控件最外圆半径

private double innerR;//控件最内圆半径

private ArrayList arrR;//内圆半径数组

 

private  ArrayList  valueArr;//饼状图各个item占整个圆的百分比(即角度集合)

 

private int itemCount;//item统计数

 

private static final int maxItem=5;//最多五个item,多出的item将全部合并到第五个item

 

private ArrayList itemArr;

 

private double itemMargin;//饼状图每个item之间的间距

 

private double siiMargin=10;//次内圆距离内圆的间距

private double csiMargin=10;//中圆距次内圆的间距

private double socMargin=10;//次外圆距离中圆的间距

private double osoMargin=100;//外圆距离次外圆的间距

private double tbSpace=60;//控件的上下左右内间距

private double textMargin=60;//描述的字的行间距

private Paint paint;//画笔对象

private int scaleType=6;//所有计算保留的小数位数,这里保留6为小数,达到比较精确的值

 

protected int number=1;//渲染次数,做一个延时效果

 

protected boolean isFinish;//渲染是否完成,是否渲染到第最后一个item

private RectF outerRectF;//外圆矩阵

 

private RectF souterRectF;//次外圆矩阵

 

private RectF centerRectF;//中圆矩阵

 

private RectF sinnerRectF;//次内圆矩阵

 

private RectF innerRectF;//内圆矩阵

private float textSize=30;//描述字体大小

private float textDefualtSize=15;//默认大小

 

private Handler handler=new Handler() {

@SuppressLint("HandlerLeak")

public void handleMessage(android.os.Message msg) {

if (number < 50) {  

                number++;  

                invalidate();  

                handler.sendEmptyMessageDelayed(1, 100);  

            } else {  

                isFinish = true;  

                invalidate();

//                number=1;

            }  

};

};

 

 

 

 

public PIEChatView(Context context, AttributeSet attrs) {

this(context, attrs , 0);

// TODO Auto-generated constructor stub

}

 

public PIEChatView(Context context) {

this(context,null);

// TODO Auto-generated constructor stub

}

 

 

@SuppressWarnings("unused")

public PIEChatView(Context context,AttributeSet attrs,int def){

super(context,attrs,def);

this.context=context;

//设置控件半径值,根据屏幕分辨率大小设置

for(int i =0 ;i < attrs.getAttributeCount();i++){

if("layout_height".equals(attrs.getAttributeName(i))){

xmlW = attrs.getAttributeValue(i);

}else if("layout_width".equals(attrs.getAttributeName(i))){

xmlH = attrs.getAttributeValue(i);

}

}

toSetR();

}

 

private void toSetR() {

// TODO Auto-generated method stub

screenWidth=DeviceUtil.getMetricsWidth(context);//屏幕宽度

screenWidth=(int)(screenWidth-tbSpace);

screenHeight=DeviceUtil.getMetricsHeight(context);//屏幕高度

initR();

outerRectF = new RectF((float)(screenWidth-2*tbSpace-2*outerR),(float)tbSpace,(float)(screenWidth-tbSpace),(float)(outerR*2+2*tbSpace));

souterRectF =new RectF((float)(screenWidth-2*tbSpace-2*outerR+osoMargin),(float)(tbSpace+osoMargin),(float)(screenWidth-tbSpace-osoMargin),(float)(outerR*2+2*tbSpace-osoMargin));

centerRectF = new RectF((float)(screenWidth-2*tbSpace-2*outerR+osoMargin+socMargin),(float)(tbSpace+osoMargin+socMargin),(float)(screenWidth-tbSpace-osoMargin-socMargin),(float)(outerR*2+2*tbSpace-osoMargin-socMargin));

sinnerRectF=new RectF((float)(screenWidth-2*tbSpace-2*outerR+osoMargin+socMargin+csiMargin),(float)(tbSpace+osoMargin+socMargin+csiMargin),(float)(screenWidth-tbSpace-osoMargin-socMargin-csiMargin),(float)(outerR*2+2*tbSpace-osoMargin-socMargin-csiMargin));

innerRectF = new RectF((float)(screenWidth-2*tbSpace-2*outerR+osoMargin+socMargin+csiMargin+siiMargin),(float)(tbSpace+osoMargin+socMargin+csiMargin+siiMargin),(float)(screenWidth-tbSpace-osoMargin-socMargin-csiMargin-siiMargin),(float)(outerR*2+2*tbSpace-osoMargin-socMargin-csiMargin-siiMargin));

}

 

 

private void initR() {

// TODO Auto-generated method stub

float width=0;

float height=0;

try {

if ("-1".equals(xmlW)||"-2".equals(xmlW)||"-1".equals(xmlH)||"-2".equals(xmlH)){//宽度被设定为MachParent或者wrapcontent或者高度被设定为MachParent或者wrapcontent

outerR=DeviceUtil.getMetricsWidth(context)/2-2*tbSpace;

}else {

width=Float.valueOf(xmlW.substring(0,xmlW.length()-3)).floatValue();

height=Float.valueOf(xmlH.substring(0,xmlH.length()-3)).floatValue();

}

} catch (Exception e) {

// TODO: handle exception

outerR=350;

}

if (width==height&&width==0) {

}else if (width>=height&&width!=0) {

width=DeviceUtil.dip2px(context, height);

outerR=width;

}else if (height>width) {

width=DeviceUtil.dip2px(context, width);

outerR=width;

}

innerR=outerR-osoMargin-socMargin-csiMargin-siiMargin;

if (innerR<0) {

innerR=0;

}

initArrR();

}

/**

 * 初始化各个半径,内圆,次内圆,中圆,次外圆,外圆,目前初始化五个半径

 */

private void initArrR() {

// TODO Auto-generated method stub

arrR=new ArrayList();

arrR.add(innerR);

arrR.add(innerR+siiMargin);

arrR.add(innerR+siiMargin+csiMargin);

arrR.add(innerR+siiMargin+csiMargin+socMargin);

arrR.add(innerR+siiMargin+csiMargin+socMargin+osoMargin);

itemMargin=(arrR.get(1)-arrR.get(0))/2;

}

 

public void initPIEChatView(ArrayList numArr){

if (numArr!=null) {

valueArr=getValueArr(numArr);//得到角度的集合

}else {

itemCount=0;

}

//重新绘制饼状图

if (valueArr.size()!=0&&valueArr.size()-1!=0) {

textMargin=PIEChatUtils.div(arrR.get(arrR.size()-1)*2,valueArr.size()-1,4);

}

invalidate();

}

 

 

private ArrayList getValueArr(ArrayList numArr) {

// TODO Auto-generated method stub

double count = 0;

if (numArr.size()>maxItem) {

//numArr集合的第五个及后边的item合并成一个item

numArr=mergedItem(numArr);

}

itemArr=numArr;

itemCount=numArr.size();

for (int i = 0; i < numArr.size(); i++) {

count+=numArr.get(i).getValue();

}

if (count==0) {

PIEItem pie = new PIEItem();

pie.setName("暂无数据");

pie.setValue(1);

numArr.add(pie);

}

//最终item的数值对应的圆环的占比

double angleScale=360-itemCount*itemMargin;

double perLength=PIEChatUtils.div(angleScale,count,scaleType);//item的每个单位所占角度,保留6为小数,达到比较精确位数

ArrayList arr=new ArrayList();

for (int j = 0; j < numArr.size(); j++) {

arr.add(PIEChatUtils.mul(numArr.get(j).getValue(),perLength));

}

return arr;

}

 

 

/**

 * numArr的第五个item及其后边的item合并成第五个item

 * @param numArr

 * @return 

 */

private ArrayList mergedItem(ArrayList numArr) {

// TODO Auto-generated method stub

double count=0;

for (int i = maxItem-1; i < numArr.size(); i++) {

count+=numArr.get(i).getValue();

}

PIEItem item=new PIEItem();

item.setName("其他");

item.setValue(count);

numArr.set(maxItem-1, item);

ArrayList arr=new ArrayList();

for (int i = 0; i < maxItem; i++) {

arr.add(numArr.get(i));

}

return arr;

}

 

// 获取笔  

private Paint getPaint() {  

if (paint == null)  {

paint = new Paint();

}  

return paint;  

}  

  

 // 修改笔的颜色  

    private Paint getShadeColorPaint() {  

        Shader mShader = new LinearGradient(300, 50, 300, 400,  

                new int[] { Color.parseColor("#55FF7A00"), Color.TRANSPARENT }//通过调整该颜色,从而调整折现到X坐标直接的阴影

        , null, Shader.TileMode.CLAMP);  

        // 新建一个线性渐变,前两个参数是渐变开始的点坐标,第三四个参数是渐变结束的点的坐标。连接这2个点就拉出一条渐变线了,玩过PS的都懂。然后那个数组是渐变的颜色。下一个参数是渐变颜色的分布,如果为空,每个颜色就是均匀分布的。最后是模式,这里设置的是循环渐变  

        getPaint().setShader(mShader);  

        return getPaint();  

    }

    

    @Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

setMeasuredDimension((int)(DeviceUtil.getMetricsWidth(context)),(int)(2*outerR+3*tbSpace));

}

@Override

protected void onLayout(boolean changed, int left, int top, int right,

int bottom) {

// TODO Auto-generated method stub

super.onLayout(changed, left, top, right, bottom);

}

@Override

protected void onDraw(Canvas canvas) {

// TODO Auto-generated method stub

super.onDraw(canvas);

 

//初始化画笔

getPaint().reset();

getPaint().setAntiAlias(true);//抗锯齿效果

getPaint().setStyle(Style.FILL);

//绘制

if (valueArr!=null&&valueArr.size()>0) {//当有有效的数据时渲染

getPaint().setStrokeWidth(1);

float count=0;

//绘制饼状图

for(int i=0;i<valueArr.size();i++){

//绘制饼状图右边的描述

if (i==0) {

getPaint().setColor(Color.parseColor("#ff76bff0"));//黄色

}else if (i==1) {

getPaint().setColor(Color.parseColor("#ff3352cc"));//红色

}else if (i==2) {

getPaint().setColor(Color.parseColor("#fff46060"));//深蓝色

}else if (i==3) {

getPaint().setColor(Color.parseColor("#ffffd322"));//浅蓝色

}else {

getPaint().setColor(Color.parseColor("#ffa755dd"));//紫色

}

getPaint().setTextSize(textSize);

canvas.drawCircle((float)tbSpace, (float)(1.5*tbSpace+i*textMargin), (float)10, paint);

canvas.drawText(itemArr.get(i).getName().length()>6?itemArr.get(i).getName().substring(0,6)+"...":itemArr.get(i).getName(),(float)(2*tbSpace), (float)(1.5*tbSpace+PIEChatUtils.div(textSize, 3, 4)+i*textMargin), paint);

canvas.drawText(StringUtil.getAmountResult(itemArr.get(i).getValue()+""),(float)(2*tbSpace+180), (float)(1.5*tbSpace+PIEChatUtils.div(textSize, 3, 4)+i*textMargin), paint);

getPaint().setTextSize(textDefualtSize);

if (i==0) {

canvas.drawArc(outerRectF, 0,valueArr.get(i).floatValue()/50*number,true, paint);

getPaint().setColor(Color.WHITE);

canvas.drawArc(souterRectF, 0,valueArr.get(i).floatValue()/50*number,true, paint);

getPaint().setColor(Color.RED);

canvas.drawArc(centerRectF, 0,valueArr.get(i).floatValue()/50*number,true, paint);

getPaint().setColor(Color.parseColor("#ff6699cc"));

canvas.drawArc(sinnerRectF, 0,valueArr.get(i).floatValue()/50*number,true, paint);

getPaint().setColor(Color.parseColor("#ffccffcc"));

canvas.drawArc(innerRectF, 0,valueArr.get(i).floatValue()/50*number,true, paint);

getPaint().setColor(Color.WHITE);

canvas.drawArc(souterRectF, 0,360,true, paint);

}else if (i==valueArr.size()-1) {

canvas.drawArc(outerRectF, count,valueArr.get(i).floatValue()/50*number,true, paint);

getPaint().setColor(Color.WHITE);

canvas.drawArc(souterRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin*2)/50*number,true, paint);

getPaint().setColor(Color.RED);

canvas.drawArc(centerRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin*2)/50*number,true, paint);

getPaint().setColor(Color.parseColor("#ff6699cc"));

canvas.drawArc(sinnerRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin*2)/50*number,true, paint);

getPaint().setColor(Color.parseColor("#ffccffcc"));

canvas.drawArc(innerRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin*2)/50*number,true, paint);

//isFinish=true;

getPaint().setColor(Color.WHITE);

canvas.drawArc(souterRectF, 0,360,true, paint);

}else{

canvas.drawArc(outerRectF, count,valueArr.get(i).floatValue()/50*number,true, paint);

getPaint().setColor(Color.WHITE);

canvas.drawArc(souterRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin)/50*number,true, paint);

getPaint().setColor(Color.RED);

canvas.drawArc(centerRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin)/50*number,true, paint);

getPaint().setColor(Color.parseColor("#ff6699cc"));

canvas.drawArc(sinnerRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin)/50*number,true, paint);

getPaint().setColor(Color.parseColor("#ffccffcc"));

canvas.drawArc(innerRectF, count-(float)itemMargin,(valueArr.get(i).floatValue()+(float)itemMargin)/50*number,true, paint);

getPaint().setColor(Color.WHITE);

canvas.drawArc(souterRectF, 0,360,true, paint);

}

count+=valueArr.get(i).floatValue()+itemMargin;

}

//渲染到了最后一个item

if (isFinish) {

getPaint().setColor(Color.WHITE);

canvas.drawArc(souterRectF, 0,360,true, paint);

}else {

handler.sendEmptyMessage(1);  

}

}

 

 

 

 

 

}

 

}

 

 

 

PIEChatUtils.class全部代码:

package cn.com.cg.piechatview;

 

import android.annotation.SuppressLint;

import android.content.Context;

import android.icu.math.BigDecimal;

 

@SuppressLint("NewApi")

public class PIEChatUtils {

//两个Double数相加

 

public static Double add(Double v1,Double v2){

 

BigDecimal b1 = new BigDecimal(v1.toString());

 

BigDecimal b2 = new BigDecimal(v2.toString());

 

return b1.add(b2).doubleValue();}

 

 

 

 

public static Double sub(Double v1,Double v2){

 

BigDecimal b1 = new BigDecimal(v1.toString());

 

BigDecimal b2 = new BigDecimal(v2.toString());

 

return b1.subtract(b2).doubleValue();

}

 

 

 

 

public static Double mul(Double v1,Double v2){

 

BigDecimal b1 = new BigDecimal(v1.toString());

 

BigDecimal b2 = new BigDecimal(v2.toString());

 

return b1.multiply(b2).doubleValue();

}

 

 

 

public static Double div(Double v1,Double v2,int scale){

 

BigDecimal b1 = new BigDecimal(v1.toString());

 

BigDecimal b2 = new BigDecimal(v2.toString());

 

return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();}

 

 

 

 

 

/**

 * 保留两位小数

 * @return

 */

public static String getTwoDecimal(double d){

//     DecimalFormat df = new DecimalFormat("#.00");

//     df.format(d);

//    return df.format(d);

return String.format("%.2f", d);

}

/**

 * 保留六位小数

 * @return

 */

public static String getSixDecimal(double d){

//     DecimalFormat df = new DecimalFormat("#.00");

//     df.format(d);

//    return df.format(d);

return String.format("%.6f", d);

}

 

/**

 * 保留三位小数

 * @return

 */

public static String getThreeDecimal(double d){

//     DecimalFormat df = new DecimalFormat("#.00");

//     df.format(d);

//    return df.format(d);

return String.format("%.3f", d);

}

 

 

public static double div(double a1, double b1, int scale) {

if (scale < 0) {  

throw new IllegalArgumentException("error");  

}

BigDecimal a2 = new BigDecimal(Double.toString(a1));  

BigDecimal b2 = new BigDecimal(Double.toString(b1));  

return a2.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();  

}

 

}

 

 

 

PIEItem.class全部代码:

package cn.com.cg.piechatview;

 

 

/**

 * 饼状图每个item的数值以及其名称

 * @author chenguo

 *

 */

public class PIEItem {

private double value;

private String name;

public double getValue() {

return value;

}

public void setValue(double value) {

this.value = value;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

 

 

DeviceUtil.class 部分方法:

/**

 * @brief 获取手机屏幕尺寸 高度

 * @param context

 *            上下文

 * @return int

 */

public static int getMetricsHeight(Context context) {

// String str = "";

DisplayMetrics dm = new DisplayMetrics();

dm = context.getResources().getDisplayMetrics();

int screenHeight = dm.heightPixels;// 屏幕高(像素,如:800px)

return screenHeight;

}

 

/**

 * @brief 获取手机屏幕尺寸 宽度

 * @param context

 *            上下文

 * @return int

 */

public static int getMetricsWidth(Context context) {

// String str = "";

DisplayMetrics dm = new DisplayMetrics();

dm = context.getResources().getDisplayMetrics();

int screenWidth = dm.widthPixels;// 屏幕高(像素,如:800px)

return screenWidth;

}

 

 

 

StringUtil.class部分方法:

// 返回小数点金额显示方法

public static String getAmountResult(String aString) {

aString = getdeleYuan(aString);

if (TextUtils.isEmpty(aString)) {

return "-";

}

if (".".equals(String.valueOf(aString.charAt(0)))) {

aString = "0" + aString;

}

if (!aString.contains(".")) {

aString += ".00";

}

if (".".equals(aString.subSequence(aString.length() - 2,

aString.length() - 1))) {

aString += "0";

}

aString = addComma(aString) + "元";

//aString = addComma(aString);

String str[] = aString.split("\\,");

if (str.length > 1) {

if ("-".equals(str[0])) {

String strr = "-";

for (int i = 1; i < str.length; i++) {

if (i == 1) {

strr += str[i];

} else {

strr += ",";

strr += str[i];

}

}

return strr;

} else {

return aString;

}

} else {

return aString;

}

}

 

 

 

MainActivity.class中使用控件:

 

//在拿到数据后调用该方法初始化控件,这里使用的是测试数据

private void initViews() {

// TODO Auto-generated method stub

pie_view=(PIEChatView)findViewById(R.id.pie_view);

numArr=getArr();

pie_view.initPIEChatView(numArr);

}

 

private ArrayList getArr() {

// TODO Auto-generated method stub

ArrayList pieArr=new ArrayList();

PIEItem i1=new PIEItem();

i1.setName("账户余额");

i1.setValue(175);

PIEItem i2=new PIEItem();

i2.setName("聚能赚");

i2.setValue(130);

PIEItem i3=new PIEItem();

i3.setName("富利快线");

i3.setValue(101);

PIEItem i4=new PIEItem();

i4.setName("理点财");

i4.setValue(79);

PIEItem i5=new PIEItem();

i5.setName("总资产");

i5.setValue(13);

PIEItem i6=new PIEItem();

i6.setName("总资产");

i6.setValue(79);

PIEItem i7=new PIEItem();

i7.setName("总资产");

i7.setValue(80);

pieArr.add(i1);

pieArr.add(i2);

pieArr.add(i3);

pieArr.add(i4);

pieArr.add(i5);

pieArr.add(i6);

pieArr.add(i7);

return pieArr;

}

 

 

总结:该控件其实没有什么难度可言,就是一个角度的计算和绘制过程,其过程中还包括一些对数据的拆分、转换,细心一点就没啥大问题。

你可能感兴趣的:(Android开发,饼状图)