Android圆形饼图绘制(当饼图占比很小时描述文字的分开绘制)
2017-06-29 13:07
369 查看
由于项目中需要用到饼图,用MpAndroidChart,当饼图部分占比很小时,描述文字重叠,所以自己重新绘制了饼图,并提供饼图各部分的点击监听,效果图如下:
绘制饼图的类:
调用示例:
附调用示例的xml文件:
绘制饼图的类:
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>
相关文章推荐
- Android自定义控件之——文字圆形边框(将文字绘制在圆中间)
- Android自定义控件之——文字圆形边框(将文字绘制在圆中间)
- android百度地图:在地图上绘制点、线、多边形、圆形和文字
- android百度地图:在地图上绘制点、线、多边形、圆形和文字
- Android自定义控件之——文字圆形边框(将文字绘制在圆中间)
- Android使用XML Shape绘制带阴影效果的圆形按钮
- android 用canvas 绘制简单圆形时钟
- Android中绘制圆形头像
- android绘制圆形图片的两种方式示例
- android WebView 文字 、图片分开加载
- Android 开发进阶:自定义 View 1-3 文字的绘制
- Android使用XML Shape绘制带阴影效果的圆形按钮
- Android绘制圆形百分比加载圈效果
- Android圆形头像的绘制(一)之绘制的几种方法
- Android使用Canvas绘制圆形进度条效果
- Android 头像上传 相机+图库 绘制圆形头像
- android bitmap绘制文字自动换行
- <转>Android开发:用Drawable XML绘制带阴影效果的圆形按钮
- Android绘制圆形图片及点击效果
- Android 学习 之 图形绘制篇 获取要绘制的文字的宽度/长度