package com.fourmob.datetimepicker.date; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.Typeface; import android.text.format.DateUtils; import android.text.format.Time; import android.view.MotionEvent; import android.view.View; import com.fourmob.datetimepicker.R; import com.fourmob.datetimepicker.Utils; import java.security.InvalidParameterException; import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Formatter; import java.util.HashMap; import java.util.Locale; public class SimpleMonthView extends View { public static final String VIEW_PARAMS_HEIGHT = "height"; public static final String VIEW_PARAMS_MONTH = "month"; public static final String VIEW_PARAMS_YEAR = "year"; public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day"; public static final String VIEW_PARAMS_WEEK_START = "week_start"; public static final String VIEW_PARAMS_NUM_DAYS = "num_days"; public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month"; public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num"; private static final int SELECTED_CIRCLE_ALPHA = 60; protected static int DEFAULT_HEIGHT = 32; protected static final int DEFAULT_NUM_ROWS = 6; protected static int DAY_SELECTED_CIRCLE_SIZE; protected static int DAY_SEPARATOR_WIDTH = 1; protected static int MINI_DAY_NUMBER_TEXT_SIZE; protected static int MIN_HEIGHT = 10; protected static int MONTH_DAY_LABEL_TEXT_SIZE; protected static int MONTH_HEADER_SIZE; protected static int MONTH_LABEL_TEXT_SIZE; protected static float mScale = 0.0F; protected int mPadding = 0; private String mDayOfWeekTypeface; private String mMonthTitleTypeface; protected Paint mMonthDayLabelPaint; protected Paint mMonthNumPaint; protected Paint mMonthTitleBGPaint; protected Paint mMonthTitlePaint; protected Paint mSelectedCirclePaint; protected int mDayTextColor; protected int mMonthTitleBGColor; protected int mMonthTitleColor; protected int mTodayNumberColor; private final StringBuilder mStringBuilder; private final Formatter mFormatter; protected int mFirstJulianDay = -1; protected int mFirstMonth = -1; protected int mLastMonth = -1; protected boolean mHasToday = false; protected int mSelectedDay = -1; protected int mToday = -1; protected int mWeekStart = 1; protected int mNumDays = 7; protected int mNumCells = mNumDays; protected int mSelectedLeft = -1; protected int mSelectedRight = -1; private int mDayOfWeekStart = 0; protected int mMonth; protected int mRowHeight = DEFAULT_HEIGHT; protected int mWidth; protected int mYear; private final Calendar mCalendar; private final Calendar mDayLabelCalendar; private int mNumRows = DEFAULT_NUM_ROWS; private DateFormatSymbols mDateFormatSymbols = new DateFormatSymbols(); private OnDayClickListener mOnDayClickListener; public SimpleMonthView(Context context) { super(context); Resources resources = context.getResources(); mDayLabelCalendar = Calendar.getInstance(); mCalendar = Calendar.getInstance(); mDayOfWeekTypeface = resources.getString(R.string.day_of_week_label_typeface); mMonthTitleTypeface = resources.getString(R.string.sans_serif); mDayTextColor = resources.getColor(R.color.date_picker_text_normal); mTodayNumberColor = Utils.resolveColor(getContext(), R.attr.themeColor, resources.getColor(R.color.comm_blue)); mMonthTitleColor = resources.getColor(R.color.comm_white); mMonthTitleBGColor = resources.getColor(R.color.circle_background); mStringBuilder = new StringBuilder(50); mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); MINI_DAY_NUMBER_TEXT_SIZE = resources.getDimensionPixelSize(R.dimen.day_number_size); MONTH_LABEL_TEXT_SIZE = resources.getDimensionPixelSize(R.dimen.month_label_size); MONTH_DAY_LABEL_TEXT_SIZE = resources.getDimensionPixelSize(R.dimen.month_day_label_text_size); MONTH_HEADER_SIZE = resources.getDimensionPixelOffset(R.dimen.month_list_item_header_height); DAY_SELECTED_CIRCLE_SIZE = resources.getDimensionPixelSize(R.dimen.day_number_select_circle_radius); mRowHeight = ((resources.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height) - MONTH_HEADER_SIZE) / 6); initView(); } private int calculateNumRows() { int offset = findDayOffset(); int dividend = (offset + mNumCells) / mNumDays; int remainder = (offset + mNumCells) % mNumDays; return (dividend + (remainder > 0 ? 1 : 0)); } private void drawMonthDayLabels(Canvas canvas) { int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2); int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); for (int i = 0; i < mNumDays; i++) { int calendarDay = (i + mWeekStart) % mNumDays; int x = (2 * i + 1) * dayWidthHalf + mPadding; mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); canvas.drawText(mDateFormatSymbols.getShortWeekdays()[mDayLabelCalendar.get(Calendar.DAY_OF_WEEK)].toUpperCase(Locale.getDefault()), x, y, mMonthDayLabelPaint); } } private void drawMonthTitle(Canvas canvas) { int x = (mWidth + 2 * mPadding) / 2; int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3); canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint); } private int findDayOffset() { return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart) - mWeekStart; } private String getMonthAndYearString() { int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY; mStringBuilder.setLength(0); long millis = mCalendar.getTimeInMillis(); return DateUtils.formatDateRange(getContext(), millis, millis, flags); } private void onDayClick(SimpleMonthAdapter.CalendarDay calendarDay) { if (mOnDayClickListener != null) { mOnDayClickListener.onDayClick(this, calendarDay); } } private boolean sameDay(int monthDay, Time time) { return (mYear == time.year) && (mMonth == time.month) && (monthDay == time.monthDay); } protected void drawMonthNums(Canvas canvas) { int y = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH + MONTH_HEADER_SIZE; int paddingDay = (mWidth - 2 * mPadding) / (2 * mNumDays); int dayOffset = findDayOffset(); int day = 1; while (day <= mNumCells) { int x = paddingDay * (1 + dayOffset * 2) + mPadding; if (mSelectedDay == day) { canvas.drawCircle(x, y - MINI_DAY_NUMBER_TEXT_SIZE / 3, DAY_SELECTED_CIRCLE_SIZE, mSelectedCirclePaint); } if (mHasToday && (mToday == day)) { mMonthNumPaint.setColor(mTodayNumberColor); } else { mMonthNumPaint.setColor(mDayTextColor); } canvas.drawText(String.format("%d", day), x, y, mMonthNumPaint); dayOffset++; if (dayOffset == mNumDays) { dayOffset = 0; y += mRowHeight; } day++; } } public SimpleMonthAdapter.CalendarDay getDayFromLocation(float x, float y) { int padding = mPadding; if ((x < padding) || (x > mWidth - mPadding)) { return null; } int yDay = (int) (y - MONTH_HEADER_SIZE) / mRowHeight; int day = 1 + ((int) ((x - padding) * mNumDays / (mWidth - padding - mPadding)) - findDayOffset()) + yDay * mNumDays; return new SimpleMonthAdapter.CalendarDay(mYear, mMonth, day); } protected void initView() { mMonthTitlePaint = new Paint(); mMonthTitlePaint.setFakeBoldText(true); mMonthTitlePaint.setAntiAlias(true); mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE); mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD)); mMonthTitlePaint.setColor(mDayTextColor); mMonthTitlePaint.setTextAlign(Align.CENTER); mMonthTitlePaint.setStyle(Style.FILL); mMonthTitleBGPaint = new Paint(); mMonthTitleBGPaint.setFakeBoldText(true); mMonthTitleBGPaint.setAntiAlias(true); mMonthTitleBGPaint.setColor(mMonthTitleBGColor); mMonthTitleBGPaint.setTextAlign(Align.CENTER); mMonthTitleBGPaint.setStyle(Style.FILL); mSelectedCirclePaint = new Paint(); mSelectedCirclePaint.setFakeBoldText(true); mSelectedCirclePaint.setAntiAlias(true); mSelectedCirclePaint.setColor(mTodayNumberColor); mSelectedCirclePaint.setTextAlign(Align.CENTER); mSelectedCirclePaint.setStyle(Style.FILL); mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA); mMonthDayLabelPaint = new Paint(); mMonthDayLabelPaint.setAntiAlias(true); mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE); mMonthDayLabelPaint.setColor(mDayTextColor); mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL)); mMonthDayLabelPaint.setStyle(Style.FILL); mMonthDayLabelPaint.setTextAlign(Align.CENTER); mMonthDayLabelPaint.setFakeBoldText(true); mMonthNumPaint = new Paint(); mMonthNumPaint.setAntiAlias(true); mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE); mMonthNumPaint.setStyle(Style.FILL); mMonthNumPaint.setTextAlign(Align.CENTER); mMonthNumPaint.setFakeBoldText(false); } protected void onDraw(Canvas canvas) { drawMonthTitle(canvas); drawMonthDayLabels(canvas); drawMonthNums(canvas); } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows + MONTH_HEADER_SIZE); } protected void onSizeChanged(int w, int h, int oldw, int oldh) { mWidth = w; } public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { SimpleMonthAdapter.CalendarDay calendarDay = getDayFromLocation(event.getX(), event.getY()); if (calendarDay != null) { onDayClick(calendarDay); } } return true; } public void reuse() { mNumRows = DEFAULT_NUM_ROWS; requestLayout(); } public void setMonthParams(HashMap<String, Integer> params) { if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) { throw new InvalidParameterException("You must specify month and year for this view"); } setTag(params); if (params.containsKey(VIEW_PARAMS_HEIGHT)) { mRowHeight = params.get(VIEW_PARAMS_HEIGHT); if (mRowHeight < MIN_HEIGHT) { mRowHeight = MIN_HEIGHT; } } if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) { mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY); } mMonth = params.get(VIEW_PARAMS_MONTH); mYear = params.get(VIEW_PARAMS_YEAR); final Time today = new Time(Time.getCurrentTimezone()); today.setToNow(); mHasToday = false; mToday = -1; mCalendar.set(Calendar.MONTH, mMonth); mCalendar.set(Calendar.YEAR, mYear); mCalendar.set(Calendar.DAY_OF_MONTH, 1); mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK); if (params.containsKey(VIEW_PARAMS_WEEK_START)) { mWeekStart = params.get(VIEW_PARAMS_WEEK_START); } else { mWeekStart = mCalendar.getFirstDayOfWeek(); } mNumCells = Utils.getDaysInMonth(mMonth, mYear); for (int i = 0; i < mNumCells; i++) { final int day = i + 1; if (sameDay(day, today)) { mHasToday = true; mToday = day; } } mNumRows = calculateNumRows(); } public void setOnDayClickListener(OnDayClickListener onDayClickListener) { mOnDayClickListener = onDayClickListener; } public static abstract interface OnDayClickListener { public abstract void onDayClick(SimpleMonthView simpleMonthView, SimpleMonthAdapter.CalendarDay calendarDay); } }