Android圓形餅圖繪製(當餅圖佔比很小時描述文字的分開繪製)
阿新 • • 發佈:2019-01-22
由於專案中需要用到餅圖,用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>