1. 程式人生 > >Android圓形餅圖繪製(當餅圖佔比很小時描述文字的分開繪製)

Android圓形餅圖繪製(當餅圖佔比很小時描述文字的分開繪製)

由於專案中需要用到餅圖,用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>