SimpleMonthView.java revision 51da77ac265fc6e46403bc6f8d3cca57e57427d7
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.datetimepicker.date; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.graphics.Canvas; 22import android.graphics.Paint; 23import android.graphics.Paint.Align; 24import android.graphics.Paint.Style; 25import android.graphics.Typeface; 26import android.text.format.Time; 27import android.view.View; 28 29import com.android.datetimepicker.R; 30import com.android.datetimepicker.Utils; 31import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay; 32 33import java.security.InvalidParameterException; 34import java.util.Calendar; 35import java.util.HashMap; 36import java.util.Locale; 37 38/** 39 * A calendar-like view displaying a specified month and the appropriate selectable day numbers 40 * within the specified month. 41 */ 42public class SimpleMonthView extends View { 43 44 /** 45 * These params can be passed into the view to control how it appears. 46 * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default 47 * values are unlikely to fit most layouts correctly. 48 */ 49 /** 50 * This sets the height of this week in pixels 51 */ 52 public static final String VIEW_PARAMS_HEIGHT = "height"; 53 /** 54 * This specifies the position (or weeks since the epoch) of this week, 55 * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay} 56 */ 57 public static final String VIEW_PARAMS_MONTH = "month"; 58 /** 59 * This specifies the position (or weeks since the epoch) of this week, 60 * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay} 61 */ 62 public static final String VIEW_PARAMS_YEAR = "year"; 63 /** 64 * This sets one of the days in this view as selected {@link Time#SUNDAY} 65 * through {@link Time#SATURDAY}. 66 */ 67 public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day"; 68 /** 69 * Which day the week should start on. {@link Time#SUNDAY} through 70 * {@link Time#SATURDAY}. 71 */ 72 public static final String VIEW_PARAMS_WEEK_START = "week_start"; 73 /** 74 * How many days to display at a time. Days will be displayed starting with 75 * {@link #mWeekStart}. 76 */ 77 public static final String VIEW_PARAMS_NUM_DAYS = "num_days"; 78 /** 79 * Which month is currently in focus, as defined by {@link Time#month} 80 * [0-11]. 81 */ 82 public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month"; 83 /** 84 * If this month should display week numbers. false if 0, true otherwise. 85 */ 86 public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num"; 87 88 protected static int DEFAULT_HEIGHT = 32; 89 protected static int MIN_HEIGHT = 10; 90 protected static final int DEFAULT_SELECTED_DAY = -1; 91 protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY; 92 protected static final int DEFAULT_NUM_DAYS = 7; 93 protected static final int DEFAULT_SHOW_WK_NUM = 0; 94 protected static final int DEFAULT_FOCUS_MONTH = -1; 95 protected static final int DEFAULT_NUM_ROWS = 6; 96 protected static final int MAX_NUM_ROWS = 6; 97 98 private static final int SELECTED_CIRCLE_ALPHA = 60; 99 100 protected static int DAY_SEPARATOR_WIDTH = 1; 101 protected static int MINI_DAY_NUMBER_TEXT_SIZE; 102 protected static int MONTH_LABEL_TEXT_SIZE; 103 protected static int MONTH_DAY_LABEL_TEXT_SIZE; 104 protected static int MONTH_HEADER_SIZE; 105 protected static int DAY_SELECTED_CIRCLE_SIZE; 106 107 // used for scaling to the device density 108 protected static float mScale = 0; 109 110 // affects the padding on the sides of this view 111 protected int mPadding = 0; 112 113 private String mDayOfWeekTypeface; 114 private String mMonthTitleTypeface; 115 116 protected Paint mMonthNumPaint; 117 protected Paint mMonthTitlePaint; 118 protected Paint mMonthTitleBGPaint; 119 protected Paint mSelectedCirclePaint; 120 protected Paint mMonthDayLabelPaint; 121 122 // The Julian day of the first day displayed by this item 123 protected int mFirstJulianDay = -1; 124 // The month of the first day in this week 125 protected int mFirstMonth = -1; 126 // The month of the last day in this week 127 protected int mLastMonth = -1; 128 129 protected int mMonth; 130 131 protected int mYear; 132 // Quick reference to the width of this view, matches parent 133 protected int mWidth; 134 // The height this view should draw at in pixels, set by height param 135 protected int mRowHeight = DEFAULT_HEIGHT; 136 // If this view contains the today 137 protected boolean mHasToday = false; 138 // Which day is selected [0-6] or -1 if no day is selected 139 protected int mSelectedDay = -1; 140 // Which day is today [0-6] or -1 if no day is today 141 protected int mToday = DEFAULT_SELECTED_DAY; 142 // Which day of the week to start on [0-6] 143 protected int mWeekStart = DEFAULT_WEEK_START; 144 // How many days to display 145 protected int mNumDays = DEFAULT_NUM_DAYS; 146 // The number of days + a spot for week number if it is displayed 147 protected int mNumCells = mNumDays; 148 // The left edge of the selected day 149 protected int mSelectedLeft = -1; 150 // The right edge of the selected day 151 protected int mSelectedRight = -1; 152 153 private final Calendar mCalendar; 154 private final Calendar mDayLabelCalendar; 155 156 private int mNumRows = DEFAULT_NUM_ROWS; 157 158 protected int mDayTextColor; 159 protected int mTodayNumberColor; 160 protected int mMonthTitleColor; 161 protected int mMonthTitleBGColor; 162 163 public SimpleMonthView(Context context) { 164 super(context); 165 166 Resources res = context.getResources(); 167 168 mDayLabelCalendar = Calendar.getInstance(); 169 mCalendar = Calendar.getInstance(); 170 171 mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface); 172 mMonthTitleTypeface = res.getString(R.string.sans_serif); 173 174 mDayTextColor = res.getColor(R.color.date_picker_text_normal); 175 mTodayNumberColor = res.getColor(R.color.blue); 176 mMonthTitleColor = res.getColor(R.color.white); 177 mMonthTitleBGColor = res.getColor(R.color.circle_background); 178 179 MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.day_number_size); 180 MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_label_size); 181 MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_day_label_text_size); 182 MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.month_list_item_header_height); 183 DAY_SELECTED_CIRCLE_SIZE = res 184 .getDimensionPixelSize(R.dimen.day_number_select_circle_radius); 185 186 mRowHeight = (res.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height) 187 - MONTH_HEADER_SIZE) / MAX_NUM_ROWS; 188 // Sets up any standard paints that will be used 189 initView(); 190 } 191 192 /** 193 * Sets up the text and style properties for painting. Override this if you 194 * want to use a different paint. 195 */ 196 protected void initView() { 197 mMonthTitlePaint = new Paint(); 198 mMonthTitlePaint.setFakeBoldText(true); 199 mMonthTitlePaint.setAntiAlias(true); 200 mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE); 201 mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD)); 202 mMonthTitlePaint.setColor(mDayTextColor); 203 mMonthTitlePaint.setTextAlign(Align.CENTER); 204 mMonthTitlePaint.setStyle(Style.FILL); 205 206 mMonthTitleBGPaint = new Paint(); 207 mMonthTitleBGPaint.setFakeBoldText(true); 208 mMonthTitleBGPaint.setAntiAlias(true); 209 mMonthTitleBGPaint.setColor(mMonthTitleBGColor); 210 mMonthTitleBGPaint.setTextAlign(Align.CENTER); 211 mMonthTitleBGPaint.setStyle(Style.FILL); 212 213 mSelectedCirclePaint = new Paint(); 214 mSelectedCirclePaint.setFakeBoldText(true); 215 mSelectedCirclePaint.setAntiAlias(true); 216 mSelectedCirclePaint.setColor(mTodayNumberColor); 217 mSelectedCirclePaint.setTextAlign(Align.CENTER); 218 mSelectedCirclePaint.setStyle(Style.FILL); 219 mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA); 220 221 mMonthDayLabelPaint = new Paint(); 222 mMonthDayLabelPaint.setAntiAlias(true); 223 mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE); 224 mMonthDayLabelPaint.setColor(mDayTextColor); 225 mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL)); 226 mMonthDayLabelPaint.setStyle(Style.FILL); 227 mMonthDayLabelPaint.setTextAlign(Align.CENTER); 228 mMonthDayLabelPaint.setFakeBoldText(true); 229 230 mMonthNumPaint = new Paint(); 231 mMonthNumPaint.setAntiAlias(true); 232 mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE); 233 mMonthNumPaint.setStyle(Style.FILL); 234 mMonthNumPaint.setTextAlign(Align.CENTER); 235 mMonthNumPaint.setFakeBoldText(false); 236 } 237 238 @Override 239 protected void onDraw(Canvas canvas) { 240 drawMonthTitle(canvas); 241 drawMonthDayLabels(canvas); 242 drawMonthNums(canvas); 243 } 244 245 private int mDayOfWeekStart = 0; 246 247 /** 248 * Sets all the parameters for displaying this week. The only required 249 * parameter is the week number. Other parameters have a default value and 250 * will only update if a new value is included, except for focus month, 251 * which will always default to no focus month if no value is passed in. See 252 * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters. 253 * 254 * @param params A map of the new parameters, see 255 * {@link #VIEW_PARAMS_HEIGHT} 256 * @param tz The time zone this view should reference times in 257 */ 258 public void setMonthParams(HashMap<String, Integer> params) { 259 if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) { 260 throw new InvalidParameterException("You must specify the month and year for this view"); 261 } 262 setTag(params); 263 // We keep the current value for any params not present 264 if (params.containsKey(VIEW_PARAMS_HEIGHT)) { 265 mRowHeight = params.get(VIEW_PARAMS_HEIGHT); 266 if (mRowHeight < MIN_HEIGHT) { 267 mRowHeight = MIN_HEIGHT; 268 } 269 } 270 if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) { 271 mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY); 272 } 273 274 // Allocate space for caching the day numbers and focus values 275 mMonth = params.get(VIEW_PARAMS_MONTH); 276 mYear = params.get(VIEW_PARAMS_YEAR); 277 278 // Figure out what day today is 279 final Time today = new Time(Time.getCurrentTimezone()); 280 today.setToNow(); 281 mHasToday = false; 282 mToday = -1; 283 284 mCalendar.set(Calendar.MONTH, mMonth); 285 mCalendar.set(Calendar.YEAR, mYear); 286 mCalendar.set(Calendar.DAY_OF_MONTH, 1); 287 mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK); 288 289 if (params.containsKey(VIEW_PARAMS_WEEK_START)) { 290 mWeekStart = params.get(VIEW_PARAMS_WEEK_START); 291 } else { 292 mWeekStart = mCalendar.getFirstDayOfWeek(); 293 } 294 295 mNumCells = Utils.getDaysInMonth(mMonth, mYear); 296 for (int i = 0; i < mNumCells; i++) { 297 final int day = i + 1; 298 if (sameDay(day, today)) { 299 mHasToday = true; 300 mToday = day; 301 } 302 } 303 mNumRows = calculateNumRows(); 304 } 305 306 public void reuse() { 307 mNumRows = DEFAULT_NUM_ROWS; 308 requestLayout(); 309 } 310 311 private int calculateNumRows() { 312 int offset = findDayOffset(); 313 int dividend = (offset + mNumCells) / mNumDays; 314 int remainder = (offset + mNumCells) % mNumDays; 315 return (dividend + (remainder > 0 ? 1 : 0)); 316 } 317 318 private boolean sameDay(int day, Time today) { 319 return mYear == today.year && 320 mMonth == today.month && 321 day == today.monthDay; 322 } 323 324 @Override 325 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 326 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows 327 + MONTH_HEADER_SIZE); 328 } 329 330 @Override 331 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 332 mWidth = w; 333 } 334 335 private void drawMonthTitle(Canvas canvas) { 336 int x = (mWidth + 2 * mPadding) / 2; 337 int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2; 338 StringBuffer sbuf = new StringBuffer(); 339 sbuf.append(mCalendar.getDisplayName(Calendar.MONTH, Calendar.LONG, 340 Locale.getDefault())); 341 sbuf.append(" "); 342 sbuf.append(mYear); 343 canvas.drawText(sbuf.toString(), x, y, mMonthTitlePaint); 344 } 345 346 private void drawMonthDayLabels(Canvas canvas) { 347 int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2); 348 int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); 349 350 for (int i = 0; i < mNumDays; i++) { 351 int calendarDay = (i + mWeekStart) % mNumDays; 352 int x = (2 * i + 1) * dayWidthHalf + mPadding; 353 mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); 354 canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, 355 Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y, 356 mMonthDayLabelPaint); 357 } 358 } 359 360 /** 361 * Draws the week and month day numbers for this week. Override this method 362 * if you need different placement. 363 * 364 * @param canvas The canvas to draw on 365 */ 366 protected void drawMonthNums(Canvas canvas) { 367 int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH) 368 + MONTH_HEADER_SIZE; 369 int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); 370 int j = findDayOffset(); 371 for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) { 372 int x = (2 * j + 1) * dayWidthHalf + mPadding; 373 if (mSelectedDay == dayNumber) { 374 canvas.drawCircle(x, y - (MINI_DAY_NUMBER_TEXT_SIZE / 3), DAY_SELECTED_CIRCLE_SIZE, 375 mSelectedCirclePaint); 376 } 377 378 if (mHasToday && mToday == dayNumber) { 379 mMonthNumPaint.setColor(mTodayNumberColor); 380 } else { 381 mMonthNumPaint.setColor(mDayTextColor); 382 } 383 canvas.drawText(Integer.valueOf(dayNumber).toString(), x, y, mMonthNumPaint); 384 385 j++; 386 if (j == mNumDays) { 387 j = 0; 388 y += mRowHeight; 389 } 390 } 391 } 392 393 private int findDayOffset() { 394 return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart) 395 - mWeekStart; 396 } 397 398 399 /** 400 * Calculates the day that the given x position is in, accounting for week 401 * number. Returns a Time referencing that day or null if 402 * 403 * @param x The x position of the touch event 404 * @return A time object for the tapped day or null if the position wasn't 405 * in a day 406 */ 407 public CalendarDay getDayFromLocation(float x, float y) { 408 int dayStart = mPadding; 409 if (x < dayStart || x > mWidth - mPadding) { 410 return null; 411 } 412 // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels 413 int row = (int) (y - MONTH_HEADER_SIZE) / mRowHeight; 414 int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); 415 416 int day = column - findDayOffset() + 1; 417 day += row * mNumDays; 418 if (day < 1 || day > mNumCells) { 419 return null; 420 } 421 return new CalendarDay(mYear, mMonth, day); 422 } 423 424} 425