前言:前段时间做的工程,自己一个人小打小闹的修改着,代码不完善之处请在评论指出,谢谢!
工程来源:github AndroidCharts:https://github.com/dacer/AndroidCharts
修改:使得PieView可以显示数字和颜色
我的需求:将每间教室的名称显示在图上,同时根据数值 按比例涂色
效果图:
代码1:PieView修改后的代码:
package com.androidcharts;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.util.ArrayList;
/**
* Created by Dacer on 11/13/13.
*/
public class PieView extends View {
private Paint textPaint;
private Paint redPaint;
private Paint linePaint;
private Paint whitePaint;
private int mViewWidth;
private int mViewHeight;
private int textSize;
private int pieRadius;
private Point pieCenterPoint;
private Point tempPoint;
private Point tempPointRight;
private int lineLength;
private float leftTextWidth;
private float rightTextWidth;
private float topTextHeight;
private int lineThickness;
private RectF cirRect;
private Rect textRect;
private ArrayList<String> areanameList;
private ArrayList<PieHelper> pieArrayList = new ArrayList<PieHelper>();
private ArrayList<PieHelper> pieArrayList_ = new ArrayList<PieHelper>(); //单纯备份传进的角度
private final int TEXT_COLOR = Color.parseColor("#9B9A9B");
private final int GRAY_COLOR = Color.parseColor("#D4D3D4");
private final int RED_COLOR = Color.argb(50, 255, 0, 51);
private String[] colorArray = {"#e74c3c","#2980b9","#1abc9c"};
private Runnable animator = new Runnable() {
@Override
public void run() {
boolean needNewFrame = false;
for(PieHelper pie : pieArrayList){
pie.update();
if(!pie.isAtRest()){
needNewFrame = true;
}
}
if (needNewFrame) {
postDelayed(this, 10);
}
invalidate();
}
};
public PieView(Context context){
this(context,null);
}
public PieView(Context context, AttributeSet attrs){
super(context, attrs);
textSize = MyUtils.sp2px(context, 15);
lineThickness = MyUtils.dip2px(context, 1);
lineLength = MyUtils.dip2px(context, 10);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setColor(TEXT_COLOR);
textPaint.setTextSize(textSize);
textPaint.setTextAlign(Paint.Align.CENTER);
Paint.FontMetrics fm = new Paint.FontMetrics();
textPaint.getFontMetrics(fm);
textRect = new Rect();
textPaint.getTextBounds("18",0,1,textRect);
redPaint = new Paint(textPaint);
redPaint.setColor(RED_COLOR);
linePaint = new Paint(textPaint);
linePaint.setColor(GRAY_COLOR);
linePaint.setStrokeWidth(lineThickness);
whitePaint = new Paint(linePaint);
whitePaint.setColor(Color.WHITE);
tempPoint = new Point();
pieCenterPoint = new Point();
tempPointRight = new Point();
cirRect = new RectF();
leftTextWidth = textPaint.measureText("18");
rightTextWidth = textPaint.measureText("6");
topTextHeight = textRect.height();
}
//传进画图需要的数据
public void setDate(ArrayList<PieHelper> helperList){
if(helperList != null && !helperList.isEmpty()){
this.pieArrayList_ = helperList;
int pieSize = pieArrayList.isEmpty()? 0:pieArrayList.size();
for(int i=0;i<helperList.size();i++){
if(i>pieSize-1){
// float mStart = helperList.get(i).getStart();
pieArrayList.add(new PieHelper(0,0,helperList.get(i)));
}else{
pieArrayList.set(i, pieArrayList.get(i).setTarget(helperList.get(i)));
}
}
int temp = pieArrayList.size() - helperList.size();
for(int i=0; i<temp; i++){
pieArrayList.remove(pieArrayList.size()-1);
}
}else {
pieArrayList.clear();
}
removeCallbacks(animator);
post(animator);
}
//传进要画在图上的数据
public void setareanameList(ArrayList<String> AreaNameList){
this.areanameList = AreaNameList;
}
@Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas);
if(pieArrayList != null){
//涂色
for(int k=0; k<pieArrayList.size(); k++){
redPaint.setColor(Color.parseColor(colorArray[k%3]));
canvas.drawArc(cirRect,pieArrayList.get(k).getStart(),pieArrayList.get(k).getSweep(),true,redPaint);
}
//画数据
for(int k=0; k< pieArrayList_.size(); k++){
//angle:12点方向起始,顺时针的角度
double angle = pieArrayList_.get(k).getStart()-270+pieArrayList_.get(k).getSweep()/2;
//90-angle: 360-(angle-90) 转化成平面坐标系中角度,3点起始,逆时针
float x = (float) (pieCenterPoint.x+Math.cos(Math.toRadians(90-angle))*pieRadius*0.8);
float y = (float) (pieCenterPoint.y-Math.sin(Math.toRadians(90-angle))*pieRadius*0.8);
canvas.drawText(areanameList.get(k),x, y, textPaint);
}
}
}
//背景,主要是外圈圆
private void drawBackground(Canvas canvas){
//外圈白
//canvas.drawCircle(pieCenterPoint.x,pieCenterPoint.y,pieRadius+lineLength/2, whitePaint);
//圆
canvas.drawCircle(pieCenterPoint.x,pieCenterPoint.y,pieRadius+lineThickness,linePaint);
//内圈白
canvas.drawCircle(pieCenterPoint.x,pieCenterPoint.y,pieRadius,whitePaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mViewWidth = measureWidth(widthMeasureSpec);
mViewHeight = measureHeight(heightMeasureSpec);
pieRadius = mViewWidth*5/12-lineLength*2-(int)(textPaint.measureText("18")/2);
pieCenterPoint.set(mViewWidth/2-(int)rightTextWidth/2+(int)leftTextWidth/2,
mViewHeight/2+textSize/2-(int)(textPaint.measureText("18")/2));
cirRect.set(pieCenterPoint.x-pieRadius,
pieCenterPoint.y-pieRadius,
pieCenterPoint.x+pieRadius,
pieCenterPoint.y+pieRadius);
setMeasuredDimension(mViewWidth, mViewHeight);
}
private int measureWidth(int measureSpec){
int preferred = 3;
return getMeasurement(measureSpec, preferred);
}
private int measureHeight(int measureSpec){
int preferred = mViewWidth;
return getMeasurement(measureSpec, preferred);
}
private int getMeasurement(int measureSpec, int preferred){
int specSize = View.MeasureSpec.getSize(measureSpec);
int measurement;
switch(View.MeasureSpec.getMode(measureSpec)){
case View.MeasureSpec.EXACTLY:
measurement = specSize;
break;
case View.MeasureSpec.AT_MOST:
measurement = Math.min(preferred, specSize);
break;
default:
measurement = preferred;
break;
}
return measurement;
}
}
代码2:实例化PieView,传递数据
final PieView pieView = (PieView)this.findViewById(R.id.pie_view);
Setpie(pieView);
private void Setpie(PieView pieView){
ArrayList<PieHelper> pieHelperArrayList = new ArrayList<PieHelper>();
int SHour =0;
int SMin =0;
int EHour =0;
int EMin =0;
int degree =0;
int degreenew =0;
int r = dataList.length;
for (int i=0; i<r/3; i++){
Log.e("totalblanknum", "=" + totalblanknum);
Log.e("dataList[1+i*3]", "=" + dataList[1+i*3]);
degree = Integer.parseInt(dataList[1+i*3])*360/totalblanknum ;
Log.e("degree", "=" + degree);
degreenew = degree+ degreenew;
Log.e("degreenew", "=" + degreenew);
EHour = degreenew/30;
EMin = degreenew%30*2;
if(i+1 == r/3)
{
EHour = 12;
EMin = 0;
}
//Log.e("EHour", "=" + EHour);
//Log.e("EMin", "=" + EMin);
pieHelperArrayList.add(new PieHelper(SHour,SMin,EHour,EMin));
SHour =EHour;
SMin =EMin;
}
pieView.setareanameList(bottomList); //传进area名称
pieView.setDate(pieHelperArrayList);
}
说明:
1.dataList数组是一个 字符串数组,数据结构:name1,blanknum1,totalnum1,name2,blanknum2,totalnum2,,,(参考上篇文章BarView的修改)
这里根据blanknum1,blanknum2,blanknum3...来确定PieView的比例,将name1,name2,name3...绘制在图上对应扇区
2.totalblanknum是 数据的总和,因为这个pieview是我界面的一部分,这个值在之前已经求出,就是相加:
for (int i=0; i<r/3; i++)
totalblanknum = totalblanknum + Integer.parseInt(dataList[1+i*3]);
3.PieHelper 是AndroidCharts中的一个数据类型,因为话扇形需要知道 起始角度和终止角度 以及方向坐标轴等,因此原作者自定义了一个数据类型
现将PieHelper.java贴出:
package com.androidcharts;
/**
* Created by Dacer on 11/14/13.
*/
public class PieHelper {
private float start;
private float end;
private float targetStart;
private float targetEnd;
int velocity = 5;
public PieHelper(float startDegree, float endDegree, PieHelper targetPie){
start = startDegree;
end = endDegree;
targetStart = targetPie.getStart();
targetEnd = targetPie.getEnd();
}
public PieHelper(int startHour,int startMin,int endHour,int endMin){
start = 270+startHour*30+startMin*30/60;
end = 270+endHour*30+endMin*30/60;
while(end<start){
end+=360;
}
}
public PieHelper(int startHour,int startMin,int startSec,int endHour,int endMin,int endSec){
start = 270+startHour*15+startMin*15/60+startSec*15/3600;
end = 270+endHour*15+endMin*15/60+endSec*15/3600;
while(end<start){
end+=360;
}
}
PieHelper setTarget(float targetStart,float targetEnd){
this.targetStart = targetStart;
this.targetEnd = targetEnd;
return this;
}
PieHelper setTarget(PieHelper targetPie){
targetStart = targetPie.getStart();
targetEnd = targetPie.getEnd();
return this;
}
boolean isAtRest(){
return (start==targetStart)&&(end==targetEnd);
}
void update(){
start = updateSelf(start, targetStart, velocity);
end = updateSelf(end, targetEnd, velocity);
}
public float getSweep(){
return end-start;
}
public float getStart(){
return start;
}
public float getEnd(){
return end;
}
private float updateSelf(float origin, float target, int velocity){
if (origin < target) {
origin += velocity;
} else if (origin > target){
origin-= velocity;
}
if(Math.abs(target-origin)<velocity){
origin = target;
}
return origin;
}
}
说明:
1. 每一个PieHelper数据包括四个参数:
new PieHelper(SHour,SMin,EHour,EMin)
起始小时,起始分钟,终止小时,终止分钟 来确定一个扇区,12小时计时法
2. 根据数据得到起始角度:
首先根据某个数据占总数的比例得到degree,即扇形圆心角:degree = Integer.parseInt(dataList[1+i*3])*360/totalblanknum ;
在上一个扇形的角度基础上加上这个圆心角作为起始角度:degreenew = degree+ degreenew;
由起始角度得到终止hour,min
EHour = degreenew/30;
EMin = degreenew%30*2;
起始hour,min是上一个扇形的终止hour,min
SHour =EHour;
SMin =EMin;
3.由于默认0度 是x轴正方向,即3点钟钟方向,因此在PieHelper中将SHour,EHour,SMin ,EMin 化成角度后 都加上了 270°
4.PieView.java 中拿到 ArrayList<PieHelper> helperList 如何确定 标记数据的坐标
for(int k=0; k< pieArrayList_.size(); k++){
//angle:12点方向起始,顺时针的角度
double angle = pieArrayList_.get(k).getStart()-270+pieArrayList_.get(k).getSweep()/2;
//90-angle: 360-(angle-90) 转化成平面坐标系中角度,3点起始,逆时针
float x = (float) (pieCenterPoint.x+Math.cos(Math.toRadians(90-angle))*pieRadius*0.8);
float y = (float) (pieCenterPoint.y-Math.sin(Math.toRadians(90-angle))*pieRadius*0.8);
canvas.drawText(areanameList.get(k),x, y, textPaint);
}
因为要用到正余弦,故将角度转换回 平面直角坐标系中的角度。然后利用公式,根据圆心角坐标以及半径和角度 求出要绘制的数据的坐标
注意这里的
Math.cos(Math.toRadians(90-angle))
中的Math.toRadians()函数。
5:我将画数据的步骤和画外围圆圈的步骤放在一起,绘制的时候是数据和圆圈先显示出来,然后扇形绘制,看过demo的都知道pieview和barview绘制都是一个动态的过程。
相当于拿一条带颜色的线扫过圆面,涂上颜色,得到图形。我用了一个和 pieArrayList 数据完全相同的 list pieArrayList_来标记要显示的数据,而用pieArrayList来绘制图形
防止每一次圆面刷新数据都会重新显示因此闪烁的后果,如果大家有别的办法可以在评论里贴出来。