package com.yuyh.sprintnba.widget.calendar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import java.util.Calendar; public class CalendarView extends View implements View.OnTouchListener, ICalendarView { private int selectedYear; private int selectedMonth; /** * correspond to xxxx(year)-xx(month)-1(day) */ private Calendar calendar; /** * the calendar needs a 6*7 matrix to store * date[i] represents the day of position i */ private int[] date = new int[42]; /** * the width of the telephone screen */ private int screenWidth; /** * the index in date[] of the first day of current month */ private int curStartIndex; /** * the index in date[] of the last day of current month */ private int curEndIndex; /** * the index in date[] of today */ private int todayIndex = -1; /** * [only used for MODE_SHOW_DATA_OF_THIS_MONTH] * data[i] indicates whether completing the plan on the ith day of current month (such as running in JOY RUN) */ private boolean[] data = new boolean[32]; /** * record the index in date[] of the last ACTION_DOWN event */ private int actionDownIndex = -1; /** * record the selected index in date[] */ private int selectedIndex = -1; private OnItemClickListener onItemClickListener; private OnRefreshListener onRefreshListener; /** * following are some parameters for rendering the widget */ private float cellWidth; private float cellHeight; private String[] weekText; private int textColor = CalConstant.TEXT_COLOR; private int backgroundColor = CalConstant.BACKGROUND_COLOR; private Paint textPaint; private Paint weekTextPaint; private Paint todayBgPaint; private Paint selectedDayBgPaint; private Paint selectedDayTextPaint; public CalendarView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); /** * default choose the current month of real life */ calendar = Calendar.getInstance(); selectedYear = calendar.get(Calendar.YEAR); selectedMonth = calendar.get(Calendar.MONTH) + 1; calendar.set(Calendar.DAY_OF_MONTH, 1); /** * get the width of the screen of the phone */ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); screenWidth = dm.widthPixels; /** * set the width and height of a cell of the calendar */ cellWidth = screenWidth / 7f; cellHeight = cellWidth * 0.7f; setBackgroundColor(backgroundColor); weekText = getResources().getStringArray(CalConstant.WEEK_TEXT[0]); setOnTouchListener(this); textPaint = RenderUtil.getPaint(textColor); textPaint.setTextSize(cellHeight * 0.4f); /** * paint for head of the calendar */ weekTextPaint = RenderUtil.getPaint(textColor); weekTextPaint.setTextSize(cellHeight * 0.4f); weekTextPaint.setTypeface(Typeface.DEFAULT_BOLD); todayBgPaint = RenderUtil.getPaint(Color.parseColor("#FF5055")); todayBgPaint.setStrokeWidth(3); todayBgPaint.setStyle(Paint.Style.STROKE); selectedDayBgPaint = RenderUtil.getPaint(Color.parseColor("#FF5055")); selectedDayTextPaint = RenderUtil.getPaint(Color.WHITE); selectedDayTextPaint.setTextSize(cellHeight * 0.4f); initial(); } public CalendarView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CalendarView(Context context) { this(context, null); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY); heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight(), MeasureSpec.EXACTLY); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * calculate the total height of the widget */ private int measureHeight() { /** * the weekday of the first day of the month, Sunday's result is 1 and Monday 2 and Saturday 7, etc. */ int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); /** * the number of days of current month */ int daysOfMonth = daysOfCurrentMonth(); /** * calculate the total lines, which equals to 1 (head of the calendar) + 1 (the first line) + n/7 + (n%7==0?0:1) * and n means number of days except first line of the calendar */ int n = -1; if (dayOfWeek >= 2 && dayOfWeek <= 7) { n = daysOfMonth - (8 - dayOfWeek + 1); } else if (dayOfWeek == 1) { n = daysOfMonth - 1; } int lines = 2 + n / 7 + (n % 7 == 0 ? 0 : 1); return (int) (cellHeight * lines); } /** * calculate the values of date[] and the legal range of index of date[] */ private void initial() { int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); int monthStart = -1; if (dayOfWeek >= 2 && dayOfWeek <= 7) { monthStart = dayOfWeek - 2; } else if (dayOfWeek == 1) { monthStart = 6; } curStartIndex = monthStart; date[monthStart] = 1; int daysOfMonth = daysOfCurrentMonth(); for (int i = 1; i < daysOfMonth; i++) { date[monthStart + i] = i + 1; } curEndIndex = monthStart + daysOfMonth; //the year and month selected is the current year and month if (calendar.get(Calendar.YEAR) == Calendar.getInstance().get(Calendar.YEAR) && calendar.get(Calendar.MONTH) == Calendar.getInstance().get(Calendar.MONTH)) { todayIndex = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + monthStart - 1; } else { todayIndex = -1; } } /** * y is bigger than the head of the calendar, meaning that the coordination may represent a day * of the calendar */ private boolean coordIsCalendarCell(float y) { return y > cellHeight; } /** * calculate the index of date[] according to the coordination */ private int getIndexByCoordinate(float x, float y) { int m = (int) (Math.floor(x / cellWidth) + 1); int n = (int) (Math.floor((y - cellHeight) / cellHeight) + 1); return (n - 1) * 7 + m - 1; } /** * whether the index is legal * * @param i the index in date[] * @return */ private boolean isLegalIndex(int i) { return !isIllegalIndex(i); } /** * whether the index is illegal * * @param i the index in date[] * @return */ private boolean isIllegalIndex(int i) { return i < curStartIndex || i >= curEndIndex; } /** * calculate the x position according to the index in date[] * * @param i the index in date[] * @return */ private int getXByIndex(int i) { return i % 7 + 1; } /** * calculate the y position according to the index in date[] * * @param i the index in date[] * @return */ private int getYByIndex(int i) { return i / 7 + 1; } /** * render */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /** * render the head */ float baseline = RenderUtil.getBaseline(0, cellHeight, weekTextPaint); for (int i = 0; i < 7; i++) { float weekTextX = RenderUtil.getStartX(cellWidth * i + cellWidth * 0.5f, weekTextPaint, weekText[i]); canvas.drawText(weekText[i], weekTextX, baseline, weekTextPaint); } for (int i = curStartIndex; i < curEndIndex; i++) { if (i == todayIndex && i == selectedIndex) { drawCircle(canvas, i, selectedDayBgPaint, cellHeight * 0.48f); drawText(canvas, i, selectedDayTextPaint, "" + date[i]); } else if (i == todayIndex) { drawCircle(canvas, i, todayBgPaint, cellHeight * 0.48f); drawText(canvas, i, textPaint, "" + date[i]); } else if (i == selectedIndex) { drawCircle(canvas, i, selectedDayBgPaint, cellHeight * 0.48f); drawText(canvas, i, selectedDayTextPaint, "" + date[i]); } else { drawText(canvas, i, textPaint, "" + date[i]); } } } /** * draw text, around the middle of the cell decided by the index */ private void drawText(Canvas canvas, int index, Paint paint, String text) { if (isIllegalIndex(index)) { return; } int x = getXByIndex(index); int y = getYByIndex(index); float top = cellHeight + (y - 1) * cellHeight; float bottom = top + cellHeight; float baseline = RenderUtil.getBaseline(top, bottom, paint); float startX = RenderUtil.getStartX(cellWidth * (x - 1) + cellWidth * 0.5f, paint, text); canvas.drawText(text, startX, baseline, paint); } /** * draw circle, around the middle of the cell decided by the index */ private void drawCircle(Canvas canvas, int index, Paint paint, float radius) { if (isIllegalIndex(index)) { return; } int x = getXByIndex(index); int y = getYByIndex(index); float centreY = cellHeight + (y - 1) * cellHeight + cellHeight * 0.5f; float centreX = cellWidth * (x - 1) + cellWidth * 0.5f; canvas.drawCircle(centreX, centreY, radius, paint); } @Override public boolean onTouch(View v, MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (coordIsCalendarCell(y)) { int index = getIndexByCoordinate(x, y); if (isLegalIndex(index)) { actionDownIndex = index; selectedIndex = -1; } } break; case MotionEvent.ACTION_UP: if (coordIsCalendarCell(y)) { int actionUpIndex = getIndexByCoordinate(x, y); if (isLegalIndex(actionUpIndex)) { if (actionDownIndex == actionUpIndex) { selectedIndex = actionUpIndex; actionDownIndex = -1; int day = date[actionUpIndex]; if (onItemClickListener != null) { onItemClickListener.onItemClick(day); } invalidate(); } } } break; } return true; } private static int leap(int year) { return (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 1 : 0; } @Override public int daysOfCurrentMonth() { return CalConstant.DAYS_OF_MONTH[leap(selectedYear)][selectedMonth]; } @Override public int getYear() { return selectedYear; } /** * legal values : 1-12 */ @Override public int getMonth() { return selectedMonth; } @Override public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } /** * used for MODE_CALENDAR * legal values of month: 1-12 */ @Override public void refresh(int year, int month) { selectedYear = year; selectedMonth = month; selectedIndex = -1; calendar.set(Calendar.YEAR, selectedYear); calendar.set(Calendar.MONTH, selectedMonth - 1); calendar.set(Calendar.DAY_OF_MONTH, 1); initial(); invalidate(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } /** * legal values : 0-3 */ @Override public void setWeekTextStyle(int style) { if (style >= 0 && style <= 3) { weekText = getResources().getStringArray(CalConstant.WEEK_TEXT[style]); } } @Override public void setWeekTextColor(int color) { weekTextPaint.setColor(color); } @Override public void setCalendarTextColor(int color) { textPaint.setColor(color); } /** * legal values : 0-1 */ @Override public void setWeekTextSizeScale(float scale) { if (scale >= 0 && scale <= 1) { weekTextPaint.setTextSize(cellHeight * 0.5f * scale); } } /** * legal values : 0-1 */ @Override public void setTextSizeScale(float scale) { if (scale >= 0 && scale <= 1) { textPaint.setTextSize(cellHeight * 0.5f * scale); selectedDayTextPaint.setTextSize(cellHeight * 0.5f * scale); } } @Override public void setSelectedDayTextColor(int color) { selectedDayTextPaint.setColor(color); } @Override public void setSelectedDayBgColor(int color) { selectedDayBgPaint.setColor(color); } @Override public void setTodayBgColor(int color) { todayBgPaint.setColor(color); } @Override public Calendar getCalendar() { return calendar; } @Override public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.onRefreshListener = onRefreshListener; } /** * used for MODE_SHOW_DATA_OF_THIS_MONTH */ @Override public int daysCompleteTheTask() { int k = 0; for (int i = 1; i <= daysOfCurrentMonth(); i++) { k += data[i] ? 1 : 0; } return k; } }