/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.ui; import java.util.Calendar; import java.util.Date; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Display; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import com.timsu.astrid.R; public class CalendarView extends View { private static final int PADDING = 0; private static final int TEXT_PADDING = 2; private final static int CURVE_RADIUS = 0; private final static int TEXT_SIZE = 16; private static final float MONTH_TEXT_SIZE = 22; private static final float GESTURE_DELTAX_THRESHOLD = 100; private float deltaX; private boolean ignoreNextTouch; private Paint borderPaint; private Paint calendarNumberRightAlignPaint; private Paint calendarSelectedNumberRightAlignPaint; private Paint backgroundColorPaint; private Paint monthCenterAlignLargePaint; private Paint centerAlignPaint; private Paint rightAlignPaint; private Paint todayCalendarPaint; private Paint selectedCalendarPaint; private Paint dayPaint; private float density; private int leftArrowHeight; private int leftArrowWidth; private int rightArrowHeight; private int rightArrowWidth; private int leftArrowX = 0; private int leftArrowY = 0; private int rightArrowX = 0; private int rightArrowY = 0; private int[] dayLeftArr; private int[] dayTopArr; private float boxWidth; private float boxHeight; private Date calendarDate = new Date(); private int currentHighlightDay = -1; public interface OnSelectedDateListener { public void onSelectedDate(Date date); } private OnSelectedDateListener onSelectedDateListener; public void setOnSelectedDateListener( OnSelectedDateListener onSelectedDateListener) { this.onSelectedDateListener = onSelectedDateListener; } /** * Constructor. This version is only needed if you will be instantiating * the object manually (not from a layout XML file). * @param context */ public CalendarView(Context context) { super(context); initCalendarView(context); } public CalendarView(Context context, AttributeSet attrs) { super(context, attrs); initCalendarView(context); } private final void initCalendarView(Context context) { Display display = ((WindowManager) context.getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); density = metrics.density; Resources r = context.getResources(); borderPaint = new Paint(); borderPaint.setAntiAlias(true); borderPaint.setColor(r.getColor(R.color.task_edit_deadline_gray)); borderPaint.setStyle(Style.STROKE); calendarNumberRightAlignPaint = new Paint(); calendarNumberRightAlignPaint.setAntiAlias(true); calendarNumberRightAlignPaint.setColor(r.getColor(R.color.task_edit_deadline_gray)); calendarNumberRightAlignPaint.setTextSize(TEXT_SIZE * density); calendarNumberRightAlignPaint.setTextAlign(Paint.Align.RIGHT); calendarSelectedNumberRightAlignPaint = new Paint(); calendarSelectedNumberRightAlignPaint.setAntiAlias(true); calendarSelectedNumberRightAlignPaint.setColor(Color.WHITE); calendarSelectedNumberRightAlignPaint.setTextSize(TEXT_SIZE * density); calendarSelectedNumberRightAlignPaint.setTextAlign(Paint.Align.RIGHT); dayPaint = new Paint(); dayPaint.setAntiAlias(true); dayPaint.setColor(Color.rgb(137, 135, 132)); monthCenterAlignLargePaint = new Paint(); monthCenterAlignLargePaint.setAntiAlias(true); monthCenterAlignLargePaint.setColor(r.getColor(R.color.task_edit_deadline_gray)); monthCenterAlignLargePaint.setTextAlign(Paint.Align.CENTER); monthCenterAlignLargePaint.setTextSize(MONTH_TEXT_SIZE * density); centerAlignPaint = new Paint(); centerAlignPaint.setAntiAlias(true); centerAlignPaint.setColor(r.getColor(R.color.task_edit_deadline_gray)); centerAlignPaint.setTextAlign(Paint.Align.CENTER); centerAlignPaint.setTextSize(TEXT_SIZE * density); rightAlignPaint = new Paint(); rightAlignPaint.setAntiAlias(true); rightAlignPaint.setColor(r.getColor(R.color.task_edit_deadline_gray)); rightAlignPaint.setTextAlign(Paint.Align.RIGHT); rightAlignPaint.setTextSize(TEXT_SIZE * density); todayCalendarPaint = new Paint(); todayCalendarPaint.setAntiAlias(true); todayCalendarPaint.setColor(r.getColor(R.color.task_edit_deadline_gray)); TypedValue selectedColor = new TypedValue(); context.getTheme().resolveAttribute(R.attr.asThemeTextColor, selectedColor, false); selectedCalendarPaint = new Paint(); selectedCalendarPaint.setAntiAlias(true); selectedCalendarPaint.setColor(r.getColor(selectedColor.data)); backgroundColorPaint = new Paint(); backgroundColorPaint.setAntiAlias(true); backgroundColorPaint.setColor(Color.TRANSPARENT); setPadding(PADDING, PADDING, PADDING, PADDING); final GestureDetector swipeCalendarListener = new GestureDetector(new SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (distanceX < 0 && deltaX > 0 || distanceX > 0 && deltaX < 0) { // Reset if direction changed deltaX = 0; } if (Math.abs(deltaX) > GESTURE_DELTAX_THRESHOLD) { if (deltaX > 0) { performClick(rightArrowX + rightArrowWidth / 2, rightArrowY + rightArrowHeight / 2); } else { performClick(leftArrowX + leftArrowWidth / 2, leftArrowY + leftArrowHeight / 2); } ignoreNextTouch = true; deltaX = 0; } else { deltaX += distanceX; } return true; } }); final OnTouchListener swipeTouchListener = new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { return swipeCalendarListener.onTouchEvent(event); } }; this.setOnTouchListener(swipeTouchListener); } /** * @see android.view.View#measure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } /** * Determines the width of this view * @param measureSpec A measureSpec packed into an int * @return The width of the view, honoring constraints from measureSpec */ private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } return result; } /** * Determines the height of this view * @param measureSpec A measureSpec packed into an int * @return The height of the view, honoring constraints from measureSpec */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } return result; } /** * @see android.view.View#onDraw(android.graphics.Canvas) */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Background canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), backgroundColorPaint); // Outermost border -- Start // RectF outerMostBorder = new RectF(); // // outerMostBorder.set(5, 5, getMeasuredWidth() - 5, getMeasuredHeight() - 5); // canvas.drawRoundRect(outerMostBorder, OUTER_BORDER_RADIUS, OUTER_BORDER_RADIUS, borderPaint); // // outerMostBorder.set(6, 6, getMeasuredWidth() - 6, getMeasuredHeight() - 6); // canvas.drawRoundRect(outerMostBorder, CURVE_RADIUS, CURVE_RADIUS, backColorPaint); // Outermost border -- end // Month border -- Start RectF rectF = new RectF(); float monthTitleHeight = (MONTH_TEXT_SIZE + 10) * density; rectF.set(15, 15, getMeasuredWidth() - 15, monthTitleHeight); canvas.drawRoundRect(rectF, CURVE_RADIUS, CURVE_RADIUS, backgroundColorPaint); rectF.set(16, 16, getMeasuredWidth() - 16, monthTitleHeight - 1); // canvas.drawRoundRect(rectF, CURVE_RADIUS, CURVE_RADIUS, backColorPaint); // Month border -- end // Month left arrow -- Start Bitmap leftArrow = ((BitmapDrawable)getResources().getDrawable(R.drawable.icn_arrow_left)).getBitmap(); leftArrowHeight = (int)(leftArrow.getHeight()*density / 2); leftArrowWidth = (int)(leftArrow.getWidth()*density / 2); leftArrowX = 5; leftArrowY = 4 + (int)((monthTitleHeight / 2 - leftArrowHeight/2)); canvas.drawBitmap(leftArrow, new Rect(0,0,leftArrow.getWidth(),leftArrow.getHeight()), new Rect(leftArrowX, leftArrowY, leftArrowX + leftArrowWidth, leftArrowY + leftArrowHeight), null); // Month left arrow -- End // Month right arrow -- Start Bitmap rightArrow = ((BitmapDrawable)getResources().getDrawable(R.drawable.icn_arrow_right)).getBitmap(); rightArrowHeight = (int)(rightArrow.getHeight()*density / 2); rightArrowWidth = (int)(rightArrow.getWidth()*density / 2); rightArrowX = (int) (getMeasuredWidth() - (2 * density) - (PADDING*3) - rightArrow.getWidth()); rightArrowY = 4 + (int)((monthTitleHeight / 2 - rightArrowHeight/2)); canvas.drawBitmap(rightArrow, new Rect(0,0,rightArrow.getWidth(),rightArrow.getHeight()), new Rect(rightArrowX, rightArrowY, rightArrowX + rightArrowWidth, rightArrowY + rightArrowHeight), null); // Month right arrow -- End Calendar calendar = Calendar.getInstance(); // Month text -- Start int monthX = getMeasuredWidth() / 2; int monthY = (int) (monthTitleHeight / 2 + 15); String monthYear = (String) DateFormat.format("MMMM yyyy", getCoercedDate(calendar.getTime(), calendarDate)); //$NON-NLS-1$ canvas.drawText(monthYear, monthX, monthY, monthCenterAlignLargePaint); // Month text -- End // Day heading -- Start int dayLeft = 3; int dayTop = (int)(monthTitleHeight + PADDING * 2); boxWidth = (getMeasuredWidth() - (PADDING*2)) / 7.0f; boxHeight = (int) (((getMeasuredHeight() - (monthTitleHeight) - 16) - (PADDING * 8)) / 7); float textX = 0; float textY = 0; int firstDayOfWeek = calendar.getFirstDayOfWeek(); calendar.set(Calendar.DAY_OF_WEEK, firstDayOfWeek); for(int i = 0; i < 7; i++) { int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); String day = DateUtils.getDayOfWeekString(dayOfWeek, DateUtils.LENGTH_SHORT); calendar.add(Calendar.DATE, 1); textX = dayLeft + boxWidth - TEXT_PADDING * 3; textY = dayTop + (boxHeight - boxHeight/8) - TEXT_PADDING * 2; canvas.drawText(day, textX, textY, rightAlignPaint); dayLeft += boxWidth; } // Day heading -- End // Calendar -- Start calendar.setTime(getCoercedDate(new Date(), calendarDate)); // Reset the calendar to either today or the specified date if (currentHighlightDay == -1) { currentHighlightDay = calendarDate.getTime() == 0 ? 0 : calendar.get(Calendar.DATE); } int today = -1; Calendar todayCalendar = Calendar.getInstance(); if(calendar.get(Calendar.MONTH) == todayCalendar.get(Calendar.MONTH) && calendar.get(Calendar.YEAR) == todayCalendar.get(Calendar.YEAR)) today = todayCalendar.get(Calendar.DATE); int lastDateOfThisMonth = calendar.getActualMaximum(Calendar.DATE); calendar.set(Calendar.DATE, 1); // offset for day of week int firstDayOfMonth = calendar.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek + Calendar.SUNDAY; if(firstDayOfMonth == 0) firstDayOfMonth = 7; boolean firstTime = true; int dayOfMonth = 1; Paint colorPaint; Paint textPaint; dayLeftArr = new int[lastDateOfThisMonth]; dayTopArr = new int[lastDateOfThisMonth]; for (int i = 1; i <= 6; i++) { dayLeft = 1; dayTop += boxHeight + PADDING; for (int j = 1; j <= 7; j++) { if (firstTime && j != firstDayOfMonth) { dayLeft += boxWidth + PADDING; continue; } firstTime = false; if (dayOfMonth <= lastDateOfThisMonth) { if (currentHighlightDay == dayOfMonth) { colorPaint = selectedCalendarPaint; textPaint = calendarSelectedNumberRightAlignPaint; } else if(today == dayOfMonth) { colorPaint = todayCalendarPaint; textPaint = calendarSelectedNumberRightAlignPaint; } else { colorPaint = backgroundColorPaint; textPaint = calendarNumberRightAlignPaint; } dayLeftArr[dayOfMonth-1] = dayLeft; dayTopArr[dayOfMonth-1] = dayTop; rectF.set(dayLeft, dayTop, dayLeft + boxWidth, dayTop + boxHeight); // if (j != 1) // canvas.drawLine(rectF.left, rectF.top, rectF.left, rectF.bottom, borderPaint); rectF.set(dayLeft+1, dayTop, dayLeft + boxWidth - 1, dayTop + boxHeight); canvas.drawRoundRect(rectF, CURVE_RADIUS, CURVE_RADIUS, colorPaint); textX = dayLeft + boxWidth - TEXT_PADDING * 3; textY = dayTop + calendarNumberRightAlignPaint.getTextSize() + TEXT_PADDING; canvas.drawText(String.valueOf(dayOfMonth), textX, textY, textPaint); dayLeft += boxWidth + PADDING; dayOfMonth++; } } } // Calendar -- End } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: int x = (int) event.getX(); int y = (int) event.getY(); performClick(x, y); break; } return true; } private void performClick(int x, int y) { // System.out.println("---------------------Current x, y : " + x + ", " + y); // Handle left-right arrow click -- start if ((x > leftArrowX && x < (leftArrowX + leftArrowWidth * 2)) && (y > leftArrowY - leftArrowHeight / 2 && y < (leftArrowY + 3 * leftArrowHeight / 2))) { Calendar calendar = Calendar.getInstance(); calendar.setTime(getCoercedDate(getToday(calendar), calendarDate)); int currentDay = calendar.get(Calendar.DATE); calendar.set(Calendar.DATE, 1); calendar.add(Calendar.MONTH, -1); int lastDay = calendar.getActualMaximum(Calendar.DATE); calendar.set(Calendar.DATE, Math.min(currentDay, lastDay)); calendarDate = calendar.getTime(); currentHighlightDay = calendar.get(Calendar.DATE); this.invalidate(); if(onSelectedDateListener != null) onSelectedDateListener.onSelectedDate(calendarDate); } else if ((x > rightArrowX - rightArrowWidth && x < (rightArrowX + rightArrowWidth)) && (y > rightArrowY - rightArrowHeight / 2 && y < (rightArrowY + 3 * rightArrowHeight / 2))) { Calendar calendar = Calendar.getInstance(); calendar.setTime(getCoercedDate(getToday(calendar), calendarDate)); int currentDay = calendar.get(Calendar.DATE); calendar.set(Calendar.DATE, 1); calendar.add(Calendar.MONTH, 1); int lastDay = calendar.getActualMaximum(Calendar.DATE); calendar.set(Calendar.DATE, Math.min(currentDay, lastDay)); calendarDate = calendar.getTime(); currentHighlightDay = calendar.get(Calendar.DATE); this.invalidate(); if(onSelectedDateListener != null) onSelectedDateListener.onSelectedDate(calendarDate); // Handle left-right arrow click -- end } else if(dayLeftArr != null) { // Check if clicked on date if (ignoreNextTouch) { ignoreNextTouch = false; return; } for (int i=0; i<dayLeftArr.length; i++) { if ((x > dayLeftArr[i] && x < dayLeftArr[i]+boxWidth) && (y > dayTopArr[i] && y < dayTopArr[i] + boxHeight)) { currentHighlightDay = i+1; Calendar calendar = Calendar.getInstance(); Date today = getToday(calendar); calendar.setTime(getCoercedDate(today, calendarDate)); calendar.set(Calendar.DATE, currentHighlightDay); calendarDate = calendar.getTime(); this.invalidate(); if(onSelectedDateListener != null) onSelectedDateListener.onSelectedDate(calendarDate); } } } } private Date getToday(Calendar calendar) { Date today = calendar.getTime(); today.setTime(today.getTime() / 1000L * 1000L); today.setHours(12); today.setMinutes(0); today.setSeconds(0); return today; } public Date getCoercedDate(Date ifZero, Date ifNotZero) { return (calendarDate.getTime() == 0 ? ifZero : ifNotZero); } public Date getCalendarDate() { return calendarDate; } public void setCalendarDate(Date calendarDate) { this.calendarDate = calendarDate; currentHighlightDay = -1; } }