您的位置:首页 > 移动开发 > Android开发

Android圆形饼图绘制(当饼图占比很小时描述文字的分开绘制)

2017-06-29 13:07 369 查看
由于项目中需要用到饼图,用MpAndroidChart,当饼图部分占比很小时,描述文字重叠,所以自己重新绘制了饼图,并提供饼图各部分的点击监听,效果图如下:



绘制饼图的类:

package com.karoline.views.bars;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.karoline.utils.SizeUtils;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
* Created by ${Karoline} on 2017/6/14.
*/

public class AssetKcPie extends View {//继承View类
private Context mContext;
private Paint textPaint;
private Paint arcPaint;
private Paint linePaint;

private WeakReference<Bitmap> bitmapBuffer;
private Canvas bitmapCanvas;

private float distance;
private float radius;
private int barWidth,barHeight;
private List<AssetKcData> datas;
private List<AngleSE> angleSEs;
private List<RectF> lengedRectes;
private OnSelectedListener mListener;

@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
float actionX = event.getX(); //点击点的坐标
float actionY = event.getY();
double distance = Math.sqrt(Math.pow(Math.abs(actionX-barWidth/2),2)+
Math.pow(Math.abs(actionY-barHeight/2),2));
double angle = Math.atan((actionY-barHeight/2) /(actionX-barWidth/2)) /3.14 * 180 - 90;
float X = barWidth/2,Y=(barHeight-lengedHeight)/2;
if(actionX > X && actionY<Y){
angle = 90-angle;
} else if (actionX > X && actionY>Y) {
angle = 90+angle;
}else if (actionX < X && actionY>Y) {
angle = 270-angle;
}else if (actionX < X && actionY<Y) {
angle = 270+angle;
}

if(angleSEs == null || angleSEs.size() == 0 || mListener == null) return false;

for(int i=0;i<angleSEs.size();i++){
if(distance <= radius){
if(angle > angleSEs.get(i).getStartAngle() && angle<angleSEs.get(i).getSweepAngle()){
mListener.onSelected(i); //当点击点在圆内且在扇形上时,触发监听事件
}
}else if(lengedRectes.get(i).contains(actionX,actionY)){
mListener.onSelected(i); //当点击点在描述文字上时,触发监听事件(此处是当饼图部分太小,无法点击时的补充)
}
}
return false;
}

return super.onTouchEvent(event);
}

private float totalNum;

private int lengedHeight = 0;
private boolean isLengedVisible = false;

public AssetKcPie(Context context) {
super(context);
}

public AssetKcPie(Context context, AttributeSet attrs) {
super(context, attrs);

mContext = context;

textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(SizeUtils.dp2px(context,11));

arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
arcPaint.setTextSize(radius);

linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.DKGRAY);
linePaint .setTextSize(3);

distance = SizeUtils.dp2px(context,16);

setRadius(SizeUtils.dp2px(context,80));

}

public AssetKcPie(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public void setRadius(float rs){
this.radius = rs;
}

public void setData(List<AssetKcData> dataS,float total){
this.datas = dataS;
this.totalNum = total;
invalidate();
}

public void setLenged(){ //设置是否绘制控件下方的描述
isLengedVisible = true;
lengedHeight = (int) SizeUtils.dp2px(mContext,32);
setMeasuredDimension(onWidthMeasure(getMeasuredWidth()),onHeightMeasure(getMeasuredHeight()));
}

private void drawLenged(){ //绘制控件下面的描述
lengedRectes = new ArrayList<>();
Rect rect = new Rect();
float lengedX = distance;
float lengedY = barHeight - lengedHeight + distance;
float totalWidth;
for(int i = 0;i<datas.size();i++){
RectF rectF = new RectF();
textPaint.setTextSize(SizeUtils.dp2px(mContext,13));
textPaint.setFakeBoldText(true);
textPaint.setShadowLayer(5,4,4,Color.GRAY);
textPaint.getTextBounds(datas.get(i).getDesc(),0,datas.get(i).getDesc().length(),rect);
arcPaint.setTextSize(distance/2);
arcPaint.setColor(datas.get(i).getColor());

if(!TextUtils.isEmpty(datas.get(i).getDesc())){ //当描述大于一行时,在下一行绘制

totalWidth = lengedX + distance/2 + 4 + rect.width() +distance;
if(totalWidth > barWidth){//判断描述的长度是否大于一行
lengedY = lengedY +distance;
lengedX = distance;
}

bitmapCanvas.drawRect(lengedX,lengedY-distance/2,lengedX+distance/2,lengedY ,arcPaint);//绘制描述的颜色方块
rectF.left = lengedX;
rectF.top = lengedY-distance;
lengedX = lengedX + distance/2 + 4;
bitmapCanvas.drawText(datas.get(i).getDesc(),lengedX,lengedY,textPaint);//绘制描述的颜色文字
lengedX = lengedX + rect.width() + distance;
rectF.right = lengedX;
rectF.bottom = lengedY + distance;
lengedRectes.add(rectF);//将描述说在的Recf存起来备用(注意放大点击的热区)
}
}

bitmapCanvas.save();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int Width = onWidthMeasure(widthMeasureSpec); //计算控件的宽度
int height = onHeightMeasure(heightMeasureSpec);//计算控件的高度
setMeasuredDimension(Width,height);
}

//当控件的宽度,高度发生变化是调用
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int width = getMeasuredWidth();
int hight = getMeasuredHeight();
if (bitmapBuffer == null
|| (bitmapBuffer.get().getWidth() != width)
|| (bitmapBuffer.get().getHeight() != hight)) {

if (width > 0 && hight > 0) {

bitmapBuffer = new WeakReference<Bitmap>(Bitmap.createBitmap(width, hight, Bitmap.Config.ARGB_4444));
bitmapCanvas = new Canvas(bitmapBuffer.get());
}
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

canvas.save();
bitmapBuffer.get().eraseColor(Color.TRANSPARENT);//绘制时先擦除画板上的内容(用于更新界面)

if(datas != null && datas.size() > 0 ){
if(isLengedVisible){
drawLenged();
}

angleSEs = new ArrayList<>();

RectF arcRect = new RectF(barWidth/2-radius,(barHeight-lengedHeight)/2 - radius,
barWidth/2+radius,(barHeight-lengedHeight)/2 + radius);//圆形所在的RectF,圆心与控件中心重叠
float startAngle = -90,sweepAngle = 0;
float perAngle = totalNum/360;//每一度的数量
String desc;
float lineAngle;

Rect rect = new Rect();
for(int i=0;i<datas.size();i++){
sweepAngle = datas.get(i).getNum()/perAngle; //当前值的度数
arcPaint.setColor(datas.get(i).getColor());
bitmapCanvas.drawArc(arcRect,startAngle,sweepAngle,true,arcPaint);//绘制扇形
angleSEs.add(new AngleSE(startAngle,sweepAngle+startAngle));

lineAngle = startAngle+ sweepAngle/2;//绘制描述文字的指示线,从扇形中间开始
desc = datas.get(i).getDesc()+","+datas.get(i).getSNum();
drwaLineAndText(sweepAngle,lineAngle,desc,rect,i);

startAngle += sweepAngle; *//开始角度变为扇形结束的角度,下次绘制时从前一个扇形的结束区绘制*
}
bitmapCanvas.save();
bitmapCanvas.restore();
}

Rect displayRect = new Rect(0,0,barWidth,barHeight);
Rect det = new Rect(0,0,getWidth(),getHeight());

canvas.drawBitmap(bitmapBuffer.get(),displayRect,det,null);
canvas.restore();
}

private void drwaLineAndText(float sweepAngle,float lineAngle,String desc,Rect rect,int i){
float lineStartX,lineStartY ,lineEndX,lineEndY ;

lineStartX   =   barWidth/2   +   (radius- distance)   *  (float) Math.cos(lineAngle *   3.14   /180 );
lineStartY   =   (barHeight-lengedHeight)/2   +   (radius- distance)  *   (float) Math.sin(lineAngle   *   3.14/180);
if(Math.abs(sweepAngle) <= 30){ //当偏转角度小于30°时,增加指示线的长度,避免描述文字重叠
float num = (datas.size() - i)%3;
lineEndX   =   barWidth/2   +   (radius+ distance*num*1f)   *  (float) Math.cos(lineAngle *   3.14   /180 );
lineEndY   =   (barHeight-lengedHeight)/2   +   (radius+ distance*num*1f)  *   (float) Math.sin(lineAngle   *   3.14   /180);
}else {
lineEndX   =   barWidth/2   +   (radius+ distance)   *  (float) Math.cos(lineAngle *   3.14   /180 );
lineEndY   =   (barHeight-lengedHeight)/2   +   (radius+ distance)  *   (float) Math.sin(lineAngle   *   3.14   /180);
}
bitmapCanvas.drawLine(lineStartX,lineStartY,lineEndX,lineEndY,linePaint);

textPaint.getTextBounds(desc,0,desc.length(),rect);
textPaint.setTextSize(SizeUtils.dp2px(mContext,11));
textPaint.setFakeBoldText(false);
textPaint.setShadowLayer(0,0,0,Color.TRANSPARENT);
if (lineStartX>barWidth/2) { //当指示线位于饼图右侧时,在右侧绘制第二条指示线及文字
bitmapCanvas.drawLine(lineEndX,lineEndY,lineEndX+distance/2,lineEndY,linePaint);
bitmapCanvas.drawText(desc,lineEndX+distance/2+4,lineEndY+rect.height()/2,textPaint);
}else {//当指示线位于饼图左侧时,在左侧绘制第二条指示线及文字

bitmapCanvas.drawLine(lineEndX,lineEndY,lineEndX-distance/2,lineEndY,linePaint);
bitmapCanvas.drawText(desc,lineEndX-distance/2-4-rect.width(),lineEndY+rect.height()/2,textPaint);
}
}

private int onWidthMeasure(int width){
int mode = MeasureSpec.getMode(width);
int size = MeasureSpec.getSize(width);

if(mode == MeasureSpec.EXACTLY){
barWidth = size;
}else if(mode == MeasureSpec.AT_MOST){
barWidth = width - getPaddingLeft() - getPaddingRight();
}
return barWidth;
}

private int onHeightMeasure(int height){
int mode1 = MeasureSpec.getMode(height);
int size1 = MeasureSpec.getSize(height);
int minSize = (int) SizeUtils.dp2px(mContext,120);
if(mode1 == MeasureSpec.EXACTLY){
barHeight = size1;
}else { //当控件的高度为wrapContet时计算控件的高度
if(datas != null && datas.size()>0){
barHeight = (int) radius*2 + lengedHeight + (int) distance*2 + (int) distance*datas.size() ;
}else {
barHeight = minSize - getPaddingTop() - getPaddingBottom();
}
}

return barHeight;
}

public void setOnSelectedListener(OnSelectedListener l){
mListener = l;
}

public class AngleSE{
private float startAngle;
private float sweepAngle;

public AngleSE(float startAngle, float sweepAngle) {
this.startAngle = startAngle;
this.sweepAngle = sweepAngle;
}

public float getStartAngle() {
return startAngle;
}

public float getSweepAngle() {
return sweepAngle;
}
}

public interface OnSelectedListener{ //点击监听接口
void onSelected(int position);
}
}


调用示例:

package com.karoline.uiviews;

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.karoline.R;
import com.karoline.codelibrary.BaseToolBarActivity;
import com.karoline.views.bars.AssetKcData;
import com.karoline.views.bars.AssetKcPie;
import com.karoline.views.bars.AssetLenged;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;

public class AsssetKcActivity extends BaseToolBarActivity implements AssetKcPie.OnSelectedListener {
@BindView(R.id.assetkc_desc)
TextView assetkcDesc;
@BindView(R.id.assetkc_chart)
AssetKcPie assetkcPie;
@BindView(R.id.assetkc_desc1)
TextView assetkcDesc1;
@BindView(R.id.assetkc_chart1)
AssetKcPie assetkcChart1;
@BindView(R.id.assetkc_lenged)
AssetLenged assetkcLenged;

private int mode = 1;
private List<Integer> perColorList;
private List<String> descList = new ArrayList<>();
private List<AssetKcData> kcDatas;

@Override
protected int getContentView() {
return R.layout.activity_assset_kc;
}

@Override
protected void init(Bundle savedInstanceState) {
setTitle("库存概览");
assetkcPie.setOnSelectedListener(this);//添加监听
assetkcPie.setLenged();

assetkcDesc1.setText("");

kcDatas = new ArrayList<>();
perColorList = new ArrayList<>();
perColorList.add(Color.parseColor("#f36c60"));
perColorList.add(Color.parseColor("#fba6c8"));
perColorList.add(Color.parseColor("#7986cb"));
perColorList.add(Color.parseColor("#4fc3f7"));
perColorList.add(Color.parseColor("#cfd8dc"));
perColorList.add(Color.parseColor("#4db6ac"));
perColorList.add(Color.parseColor("#aed581"));
perColorList.add(Color.parseColor("#fff176"));
perColorList.add(Color.parseColor("#ffb74d"));

updateView();
}

private void updateView() {
List<AssetKcData> list = new ArrayList<>();
list.add(new AssetKcData(Color.rgb(205, 92, 92), "工器具", 318243f));
list.add(new AssetKcData(Color.rgb(255, 193, 37), "消耗品", 674937f));
list.add(new AssetKcData(Color.parseColor("#cfd8dc"), "设施设备工装", 212664f));
list.add(new AssetKcData(Color.rgb(10, 149, 237), "原材料", 35420464f));
assetkcPie.setData(list, 318243 + 674937 + 212664 + 35420464);
}

@Override
public void onSelected(int position) {
List<AssetKcData> list = new ArrayList<>();
switch (position) {
case 0:
assetkcDesc1.setText("工器具金额");
assetkcChart1.setVisibility(View.VISIBLE);
list.clear();
list.add(new AssetKcData(perColorList.get(0), "电动工具", 38189.16f));
list.add(new AssetKcData(perColorList.get(1), "手动工具", 25459.44f));
list.add(new AssetKcData(perColorList.get(2), "仪器仪表", 12729.72f));
list.add(new AssetKcData(perColorList.get(3), "照明灯具", 12729.72f));
list.add(new AssetKcData(perColorList.get(4), "通讯工具", 25459.44f));
list.add(new AssetKcData(perColorList.get(5), "杂项工具", 12729.72f));

descList.clear();
descList.add("电动工具");
descList.add("手动工具");
descList.add("仪器仪表");
descList.add("照明灯具");
descList.add("通讯工具");
descList.add("杂项工具");

assetkcChart1.setData(list, 127297.20f);
assetkcLenged.setData(perColorList, descList);
break;
case 1:
assetkcDesc1.setText("消耗品金额");
assetkcChart1.setVisibility(View.VISIBLE);
list.clear();
list.add(new AssetKcData(perColorList.get(0), "刀具", 134987.4f));
list.add(new AssetKcData(perColorList.get(1), "防寒防汛", 53994.96f));
list.add(new AssetKcData(perColorList.get(2), "劳防用品", 80992.44f));

descList.clear();
descList.add("刀具");
descList.add("防寒防汛");
descList.add("劳防用品");

assetkcChart1.setData(list, 269974.8f);
assetkcLenged.setData(perColorList, descList);
break;
case 2:
assetkcDesc1.setText("设施设备工装金额");
assetkcChart1.setData(null, 100f);
assetkcLenged.setData(null, null);
break;
case 3:
assetkcDesc1.setText("原材料金额");
assetkcChart1.setVisibility(View.VISIBLE);
list.clear();
list.add(new AssetKcData(perColorList.get(0), "备品备件(专用)", 1416818.56f));
list.add(new AssetKcData(perColorList.get(1), "备品备件(通用)", 1558500.416f));
list.add(new AssetKcData(perColorList.get(2), "紧固件", 4250455.68f));
list.add(new AssetKcData(perColorList.get(3), "水暖配件", 708409.28f));
list.add(new AssetKcData(perColorList.get(4), "电器电料", 2833637.12f));
list.add(new AssetKcData(perColorList.get(5), "灯类", 2125227.84f));
list.add(new AssetKcData(perColorList.get(6), "辅料", 566727.424f));
list.add(new AssetKcData(perColorList.get(7), "材料", 283363.712f));
list.add(new AssetKcData(perColorList.get(8), "化工原料", 425045.568f));

descList.clear();
descList.add("备品备件(专用)");
descList.add("备品备件(通用)");
descList.add("紧固件");
descList.add("水暖配件");
descList.add("电器电料");
descList.add("灯类");
descList.add("辅料");
descList.add("材料");
descList.add("化工原料");

assetkcChart1.setData(list, 14168185.6f);
assetkcLenged.setData(perColorList, descList);
break;
}
}
}


附调用示例的xml文件:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#e0f7fa"
android:padding="16dp"
android:orientation="vertical">

<TextView
android:id="@+id/assetkc_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="当前库存金额"
android:textSize="16sp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<com.karoline.views.bars.AssetKcPie
android:id="@+id/assetkc_chart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#e0f7fa"
android:gravity="center"
android:minHeight="220dp" />
</LinearLayout>
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
android:padding="16dp"
android:orientation="vertical">

<TextView
android:id="@+id/assetkc_desc1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="当前库存金额"
android:textSize="16sp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<com.karoline.views.bars.AssetKcPie
android:id="@+id/assetkc_chart1"
android:layout_width="match_parent"
android:layout_height="280dp"
android:gravity="center" />
</LinearLayout>
<com.karoline.views.bars.AssetLenged
android:id="@+id/assetkc_lenged"
android:layout_width="match_parent"
android:layout_height="72dp" />
</LinearLayout>
</LinearLayout>
</ScrollView>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息