package com.dacer.androidcharts; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.RectF; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; /** * Created by Dacer on 9/26/14. */ public class PieView extends View { public interface OnPieClickListener { void onPieClick(int index); } private Paint cirPaint; private Paint whiteLinePaint; private Point pieCenterPoint; private Paint textPaint; private RectF cirRect; private RectF cirSelectedRect; private int mViewWidth; private int mViewHeight; private int margin; private int pieRadius; private OnPieClickListener onPieClickListener; private ArrayList<PieHelper> pieHelperList; private int selectedIndex = NO_SELECTED_INDEX; private boolean showPercentLabel = true; public static final int NO_SELECTED_INDEX = -999; private final int[] DEFAULT_COLOR_LIST = {Color.parseColor("#33B5E5"), Color.parseColor("#AA66CC"), Color.parseColor("#99CC00"), Color.parseColor("#FFBB33"), Color.parseColor("#FF4444")}; private Runnable animator = new Runnable() { @Override public void run() { boolean needNewFrame = false; for (PieHelper pie : pieHelperList) { pie.update(); if (!pie.isAtRest()) { needNewFrame = true; } } if (needNewFrame) { postDelayed(this, 10); } invalidate(); } }; public PieView(Context context) { this(context, null); } public PieView(Context context, AttributeSet attrs) { super(context, attrs); pieHelperList = new ArrayList<PieHelper>(); cirPaint = new Paint(); cirPaint.setAntiAlias(true); cirPaint.setColor(Color.GRAY); whiteLinePaint = new Paint(cirPaint); whiteLinePaint.setColor(Color.WHITE); whiteLinePaint.setStrokeWidth(2f); textPaint = new Paint(); textPaint.setAntiAlias(true); textPaint.setColor(Color.WHITE); textPaint.setTextSize(ChartUtils.sp2px(getContext(), 13)); textPaint.setStrokeWidth(5); textPaint.setTextAlign(Paint.Align.CENTER); pieCenterPoint = new Point(); cirRect = new RectF(); cirSelectedRect = new RectF(); } public void showPercentLabel(boolean show) { showPercentLabel = show; postInvalidate(); } public void setOnPieClickListener(OnPieClickListener listener) { onPieClickListener = listener; } public void setDate(ArrayList<PieHelper> helperList) { initPies(helperList); pieHelperList.clear(); removeSelectedPie(); if (helperList != null && !helperList.isEmpty()) { for (PieHelper pieHelper : helperList) { pieHelperList.add(new PieHelper(pieHelper.getStartDegree(), pieHelper.getStartDegree(), pieHelper)); } } else { pieHelperList.clear(); } removeCallbacks(animator); post(animator); // pieHelperList = helperList; // postInvalidate(); } /** * Set startDegree and endDegree for each PieHelper * * @param helperList */ private void initPies(ArrayList<PieHelper> helperList) { float totalAngel = 270; for (PieHelper pie : helperList) { pie.setDegree(totalAngel, totalAngel + pie.getSweep()); totalAngel += pie.getSweep(); } } public void selectedPie(int index) { selectedIndex = index; if (onPieClickListener != null) onPieClickListener.onPieClick(index); postInvalidate(); } public void removeSelectedPie() { selectedIndex = NO_SELECTED_INDEX; if (onPieClickListener != null) onPieClickListener.onPieClick(NO_SELECTED_INDEX); postInvalidate(); } @Override protected void onDraw(Canvas canvas) { if (pieHelperList.isEmpty()) { return; } int index = 0; for (PieHelper pieHelper : pieHelperList) { boolean selected = (selectedIndex == index); RectF rect = selected ? cirSelectedRect : cirRect; if (pieHelper.isColorSetted()) { cirPaint.setColor(pieHelper.getColor()); } else { cirPaint.setColor(DEFAULT_COLOR_LIST[index % 5]); } canvas.drawArc(rect, pieHelper.getStartDegree(), pieHelper.getSweep(), true, cirPaint); drawPercentText(canvas, pieHelper); drawLineBesideCir(canvas, pieHelper.getStartDegree(), selected); drawLineBesideCir(canvas, pieHelper.getEndDegree(), selected); index++; } } private void drawLineBesideCir(Canvas canvas, float angel, boolean selectedCir) { int sth2 = selectedCir ? mViewHeight / 2 : pieRadius; // Sorry I'm really don't know how to name the variable.. int sth = 1; // And it's if (angel % 360 > 180 && angel % 360 < 360) { sth = -1; } float lineToX = (float) (mViewHeight / 2 + Math.cos(Math.toRadians(-angel)) * sth2); float lineToY = (float) (mViewHeight / 2 + sth * Math.abs(Math.sin(Math.toRadians(-angel))) * sth2); canvas.drawLine(pieCenterPoint.x, pieCenterPoint.y, lineToX, lineToY, whiteLinePaint); } private void drawPercentText(Canvas canvas, PieHelper pieHelper) { if (!showPercentLabel) return; float angel = (pieHelper.getStartDegree() + pieHelper.getEndDegree()) / 2; int sth = 1; if (angel % 360 > 180 && angel % 360 < 360) { sth = -1; } float x = (float) (mViewHeight / 2 + Math.cos(Math.toRadians(-angel)) * pieRadius / 2); float y = (float) (mViewHeight / 2 + sth * Math.abs(Math.sin(Math.toRadians(-angel))) * pieRadius / 2); canvas.drawText(pieHelper.getPercentStr(), x, y, textPaint); } private void drawText(Canvas canvas, PieHelper pieHelper) { if (pieHelper.getTitle() == null) return; float angel = (pieHelper.getStartDegree() + pieHelper.getEndDegree()) / 2; int sth = 1; if (angel % 360 > 180 && angel % 360 < 360) { sth = -1; } float x = (float) (mViewHeight / 2 + Math.cos(Math.toRadians(-angel)) * pieRadius / 2); float y = (float) (mViewHeight / 2 + sth * Math.abs(Math.sin(Math.toRadians(-angel))) * pieRadius / 2); canvas.drawText(pieHelper.getTitle(), x, y, textPaint); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { selectedIndex = findPointAt((int) event.getX(), (int) event.getY()); if (onPieClickListener != null) { onPieClickListener.onPieClick(selectedIndex); } postInvalidate(); } return true; } /** * find pie index where point is * * @param x * @param y * @return */ private int findPointAt(int x, int y) { double degree = Math.atan2(x - pieCenterPoint.x, y - pieCenterPoint.y) * 180 / Math.PI; degree = -(degree - 180) + 270; int index = 0; for (PieHelper pieHelper : pieHelperList) { if (degree >= pieHelper.getStartDegree() && degree <= pieHelper.getEndDegree()) { return index; } index++; } return NO_SELECTED_INDEX; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mViewWidth = measureWidth(widthMeasureSpec); mViewHeight = measureHeight(heightMeasureSpec); margin = mViewWidth / 16; pieRadius = (mViewWidth) / 2 - margin; pieCenterPoint.set(pieRadius + margin, pieRadius + margin); cirRect.set(pieCenterPoint.x - pieRadius, pieCenterPoint.y - pieRadius, pieCenterPoint.x + pieRadius, pieCenterPoint.y + pieRadius); cirSelectedRect.set(2, //minor margin for bigger circle 2, mViewWidth - 2, mViewHeight - 2); setMeasuredDimension(mViewWidth, mViewHeight); } private int measureWidth(int measureSpec) { int preferred = 3; return getMeasurement(measureSpec, preferred); } private int measureHeight(int measureSpec) { int preferred = mViewWidth; return getMeasurement(measureSpec, preferred); } private int getMeasurement(int measureSpec, int preferred) { int specSize = View.MeasureSpec.getSize(measureSpec); int measurement; switch (View.MeasureSpec.getMode(measureSpec)) { case View.MeasureSpec.EXACTLY: measurement = specSize; break; case View.MeasureSpec.AT_MOST: measurement = Math.min(preferred, specSize); break; default: measurement = preferred; break; } return measurement; } }