package com.spacemangames.biomatcher.view; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.widget.Scroller; import com.spacemangames.biomatcher.util.DateUtils; public class Graph extends View { private static final int FLING_FRAMERATE = 30; protected GraphAttributeHandler attributes; private float monthTextPaddingBottom; private float dayTextOffsetBottom; protected int graphBottomOffset; private float pixelsPerDay; protected long timePerPixel; protected final Calendar originDate = new GregorianCalendar(); protected final Calendar leftDate = new GregorianCalendar(); @SuppressLint("SimpleDateFormat") private final DateFormat format = new SimpleDateFormat("MMMM"); private TouchEventHandler touchEventHandler; private GestureDetector gestureDetector; private float offsetX; private Scroller scroller; public Graph(Context context) { super(context); } public Graph(Context context, AttributeSet attrs) { super(context, attrs); } public Graph(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } protected void init(AttributeSet attrs, int defStyle) { attributes = new GraphAttributeHandler(getContext(), attrs, defStyle); touchEventHandler = new TouchEventHandler(this); gestureDetector = new GestureDetector(getContext(), touchEventHandler); scroller = new Scroller(getContext()); invalidateTextPaintAndMeasurements(); } private void invalidateTextPaintAndMeasurements() { Paint.FontMetrics fontMetrics = attributes.getMonthTextPaint().getFontMetrics(); monthTextPaddingBottom = fontMetrics.bottom; dayTextOffsetBottom = fontMetrics.bottom - fontMetrics.top; graphBottomOffset = (int) dayTextOffsetBottom * 2; } @Override public boolean onTouchEvent(MotionEvent event) { gestureDetector.onTouchEvent(event); getParent().requestDisallowInterceptTouchEvent(true); return true; } protected void onDrawGraphBackground(Canvas canvas, int width, int height) { canvas.drawRect(0, 0, width, height - graphBottomOffset, attributes.getGraphBackgroundPaint()); canvas.drawRect(calculateTodayRect(leftDate), attributes.getGraphTodayPaint()); } private RectF calculateTodayRect(Calendar centerDate) { Calendar today = new GregorianCalendar(); float xoffset = calculatePosition(DateUtils.roundToFullDay(today.getTimeInMillis())); return new RectF(xoffset, 0, xoffset + pixelsPerDay, getHeight() - graphBottomOffset); } protected void onDrawDaysText(Canvas canvas, int width, int height) { Calendar tempCal = new GregorianCalendar(); long currentDay = DateUtils.roundToFullDay(leftDate.getTimeInMillis()) + DateUtils.DAY_MILLIS / 2; for (int i = 0; i < attributes.getDaysInView() + 1; ++i) { float center = calculatePosition(currentDay); tempCal.setTimeInMillis(currentDay); int day = tempCal.get(Calendar.DAY_OF_MONTH); canvas.drawText(String.valueOf(day), center, height - dayTextOffsetBottom, attributes.getDayNumbersTextPaint()); currentDay += DateUtils.DAY_MILLIS; } } protected float calculatePosition(long time) { return (time - leftDate.getTimeInMillis()) / timePerPixel; } protected void onDrawMonthText(Canvas canvas, int width, int height) { Calendar center = new GregorianCalendar(); center.setTimeInMillis(leftDate.getTimeInMillis() + (attributes.getDaysInView() / 2) * DateUtils.DAY_MILLIS); String month = format.format(center.getTime()); canvas.drawText(month, width / 2, height - monthTextPaddingBottom, attributes.getMonthTextPaint()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (scroller.computeScrollOffset()) { offsetX = scroller.getCurrX(); postInvalidateDelayed(FLING_FRAMERATE); } int width = getWidth(); int height = getHeight(); canvas.drawColor(attributes.getBackgroundColor()); onDrawMonthText(canvas, width, height); onDrawDaysText(canvas, width, height); onDrawGraphBackground(canvas, width, height); onDrawDayLines(canvas, width, height); onDrawBaseLine(canvas, width, height); } private void onDrawBaseLine(Canvas canvas, int width, int height) { int y = (height - graphBottomOffset) / 2; canvas.drawLine(0, y, width, y, attributes.getBaseLinePaint()); } private void onDrawDayLines(Canvas canvas, int width, int height) { long currentDay = DateUtils.roundToFullDay(leftDate.getTimeInMillis()); for (int i = 0; i < attributes.getDaysInView() + 1; ++i) { float center = calculatePosition(currentDay); canvas.drawLine(center, 0, center, height - graphBottomOffset, attributes.getDayLinesPaint()); currentDay += DateUtils.DAY_MILLIS; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (width < height) { setMeasuredDimension(width, width); } else { setMeasuredDimension(width, height); } pixelsPerDay = getMeasuredWidth() / (float) attributes.getDaysInView(); timePerPixel = (long) (DateUtils.DAY_MILLIS / pixelsPerDay); } public void scroll(float distanceX) { scroller.forceFinished(true); this.offsetX -= distanceX; invalidate(); } protected float getOffsetX() { return offsetX; } public void fling(float velocityX) { scroller.forceFinished(true); scroller.fling((int) offsetX, 0, (int) velocityX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0); invalidate(); } }