要完成一个饼状图,其实就是将一个360度分成很多份,然后每一份绘制一个扇形,这些扇形加起来正好是一个整圆。
效果:
android中绘制扇形 我们可以用绘制弧形的api
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
也可以用绘制path的api
canvas.drawPath(mPath,mPaint);
path确定路径的时候需要用到 mPath.arcTo(mRectF,startAngle,sweepAngle); 来却定path的路径。
无论是绘制圆弧还是绘制path 我们都需要有一个绘制的区域 这就需要我们定义一个RectF来确定绘制的区域在onSizeChanged()方法中初始化
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mTotalWidth = w - getPaddingLeft() - getPaddingRight();
mTotalHeight = h - getPaddingTop() - getPaddingBottom();
mRadius = (float) (Math.min(mTotalWidth,mTotalHeight)/2*0.7);
mRectF.left = -mRadius;
mRectF.top = -mRadius;
mRectF.right = mRadius;
mRectF.bottom = mRadius;
mRectFTouch.left = -mRadius-16;
mRectFTouch.top = -mRadius-16;
mRectFTouch.right = mRadius+16;
mRectFTouch.bottom = mRadius+16;
}
mRectFTouch 是当我们手指点击到某一个扇形的时候,我们需要这个扇形突出一点,这时候我们需要将这个矩形的长宽都扩大一点。
然后就是在onDraw()中绘制了
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mDataList==null)
return;
canvas.translate(mTotalWidth/2,mTotalHeight/2);
//绘制饼图的每块区域
drawPiePath(canvas);
}
这里先把画布移到屏幕的中间,这样坐标的原点就在屏幕中间了,绘制起来方便一些。
绘制扇形的方法:
/**
* 绘制饼图的每块区域 和文本
* @param canvas
*/
private void drawPiePath(Canvas canvas) {
//起始地角度
float startAngle = 0;
for(int i = 0;ifloat sweepAngle = mDataList.get(i).getValue()/mTotalValue*360-1;//每个扇形的角度
mPath.moveTo(0,0);
if(position-1==i){
mPath.arcTo(mRectFTouch,startAngle,sweepAngle);
}else {
mPath.arcTo(mRectF,startAngle,sweepAngle);
}
mPaint.setColor(mDataList.get(i).getColor());
canvas.drawPath(mPath,mPaint);
mPath.reset();
startAngle += sweepAngle+1;
}
}
也可以使用drawArc的方法。
for(int i = 0;ifloat sweepAngle = mDataList.get(i).getValue()/mTotalValue*360-1;//每个扇形的角度
if(position-1==i){
canvas.drawArc(mRectFTouch,startAngle,sweepAngle,true,mPaint);
}else {
canvas.drawArc(mRectF,startAngle,sweepAngle,true,mPaint);
}
mPaint.setColor(mDataList.get(i).getColor());
canvas.drawArc(mRectF,startAngle,sweepAngle,true,mPaint);
}
效果:
正常情况下我们根据传入的数据计算出其在360度中所占的度数后绘制出来的应该是每个扇形无缝连接的,有时候我们感觉上图中的每个扇形间有个小间距会更好看一点,要做到这点很简单,绘制的时候每个扇形的度数减去一度就好了。
然后就是将每个扇形所占的百分比数字绘制上去。我们可以将其绘制在每个扇形的中间,但是因为现实中可能有的扇形很大有的扇形很小,当一个扇形很小的时候在其上面写字有可能就覆盖到别的扇形了,假如连着好几个都很小呢,往上面写字更会覆盖到一起。体验无疑会很差。
所以这里从每个扇形中指出一条小直线,在外面绘制我们的百分比数字。
要绘制直线就得确定每个直线的起始点和终止点的坐标。
float pxs = (float) (mRadius*Math.cos(Math.toRadians(startAngle+sweepAngle/2)));
float pys = (float) (mRadius*Math.sin(Math.toRadians(startAngle+sweepAngle/2)));
float pxt = (float) ((mRadius+30)*Math.cos(Math.toRadians(startAngle+sweepAngle/2)));
float pyt = (float) ((mRadius+30)*Math.sin(Math.toRadians(startAngle+sweepAngle/2)));
canvas.drawLine(pxs,pys,pxt,pyt,mLinePaint);
Math.toRadians() 是将角度转换为弧度
1弧度=180/π度
1度=π/180弧度
弧度和角度的转换可以参考:弧度和角度的转换
然后是绘制文本:
//提供精确的小数位四舍五入处理。
double resToRound = CalculateUtil.round(res,2);
float v = startAngle % 360;
if (startAngle % 360.0 >= 90.0 && startAngle % 360.0 <= 270.0) {
canvas.drawText(resToRound+"%",pxt-mTextPaint.measureText(resToRound+"%"),pyt,mTextPaint);
}else {
canvas.drawText(resToRound+"%",pxt,pyt,mTextPaint);
}
这里我们绘制文字的时候需要注意下坐标轴的象限,再一和四象限,我们直接在直线的终点处开始绘制就好了,但是在二和三象限中如果这么绘制的话,文字就绘制到图像上面来了。所以我们需要将绘制的起点往左移动text的大小的距离在绘制。
然后就算绘制完成了:
有时候我们需要点击某个部分来查看此部分的详情。所以需要对其设置点击事件,点击事件无非就是写个接口给外面调用就好了,关键是我们怎么确定我们所点击的地方是哪个扇形。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
float x = event.getX()-(mTotalWidth/2);
float y = event.getY()-(mTotalHeight/2);
float touchAngle = 0;
if (x<0&&y<0){ //2 象限
touchAngle += 180;
}else if (y<0&&x>0){ //1象限
touchAngle += 360;
}else if (y>0&&x<0){ //3象限
touchAngle += 180;
}
//Math.atan(y/x) 返回正数值表示相对于 x 轴的逆时针转角,返回负数值则表示顺时针转角。
//返回值乘以 180/π,将弧度转换为角度。
touchAngle +=Math.toDegrees(Math.atan(y/x));
if (touchAngle<0){
touchAngle = touchAngle+360;
}
float touchRadius = (float) Math.sqrt(y*y+x*x);
if (touchRadius< mRadius){
position = -Arrays.binarySearch(angles,(touchAngle))-1;
invalidate();
if(mOnItemPieClickListener!=null){
mOnItemPieClickListener.onClick(position-1);
}
}
break;
}
return super.onTouchEvent(event);
}
在绘制扇形的地方将每个扇形的起始角度保存到一个数组中,
Math.toDegrees(Math.atan(y/x));将我们点击的地方转换成角度,
通过-Arrays.binarySearch(angles,(touchAngle))-1;方法计算出position,
Arrays.binarySearch的用法可以百度下。
计算出pisition后再接口中返回点击事件就OK了。
源码地址:https://github.com/chsmy/EasyChartWidget