SimpleWeekView.java revision d6c480146a3dbf2a18e1680d9078db5261ff9fe1
1/* 2 * Copyright (C) 2010 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.calendar.month; 18 19import com.android.calendar.R; 20import com.android.calendar.Utils; 21 22import android.content.Context; 23import android.content.res.Resources; 24import android.graphics.Canvas; 25import android.graphics.Paint; 26import android.graphics.Paint.Align; 27import android.graphics.Paint.Style; 28import android.graphics.Rect; 29import android.graphics.drawable.Drawable; 30import android.text.format.Time; 31import android.view.View; 32 33import java.security.InvalidParameterException; 34import java.util.HashMap; 35 36/** 37 * <p> 38 * This is a dynamic view for drawing a single week. It can be configured to 39 * display the week number, start the week on a given day, or show a reduced 40 * number of days. It is intended for use as a single view within a ListView. 41 * See {@link SimpleWeeksAdapter} for usage. 42 * </p> 43 */ 44public class SimpleWeekView extends View { 45 private static final String TAG = "MonthView"; 46 47 /** 48 * These params can be passed into the view to control how it appears. 49 * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default 50 * values are unlikely to fit most layouts correctly. 51 */ 52 /** 53 * This sets the height of this week in pixels 54 */ 55 public static final String VIEW_PARAMS_HEIGHT = "height"; 56 /** 57 * This specifies the position (or weeks since the epoch) of this week, 58 * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay} 59 */ 60 public static final String VIEW_PARAMS_WEEK = "week"; 61 /** 62 * This sets one of the days in this view as selected {@link Time#SUNDAY} 63 * through {@link Time#SATURDAY}. 64 */ 65 public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day"; 66 /** 67 * Which day the week should start on. {@link Time#SUNDAY} through 68 * {@link Time#SATURDAY}. 69 */ 70 public static final String VIEW_PARAMS_WEEK_START = "week_start"; 71 /** 72 * How many days to display at a time. Days will be displayed starting with 73 * {@link #mWeekStart}. 74 */ 75 public static final String VIEW_PARAMS_NUM_DAYS = "num_days"; 76 /** 77 * Which month is currently in focus, as defined by {@link Time#month} 78 * [0-11]. 79 */ 80 public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month"; 81 /** 82 * If this month should display week numbers. false if 0, true otherwise. 83 */ 84 public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num"; 85 86 protected static int DEFAULT_HEIGHT = 32; 87 protected static int MIN_HEIGHT = 10; 88 protected static final int DEFAULT_SELECTED_DAY = -1; 89 protected static final int DEFAULT_WEEK_START = Time.SUNDAY; 90 protected static final int DEFAULT_NUM_DAYS = 7; 91 protected static final int DEFAULT_SHOW_WK_NUM = 0; 92 protected static final int DEFAULT_FOCUS_MONTH = -1; 93 94 protected static int DAY_SEPARATOR_WIDTH = 1; 95 96 protected static int MINI_DAY_NUMBER_TEXT_SIZE = 14; 97 protected static int MINI_WK_NUMBER_TEXT_SIZE = 12; 98 protected static int MINI_TODAY_NUMBER_TEXT_SIZE = 18; 99 protected static int MINI_TODAY_OUTLINE_WIDTH = 2; 100 protected static int WEEK_NUM_MARGIN_BOTTOM = 4; 101 102 // used for scaling to the device density 103 protected static float mScale = 0; 104 105 // affects the padding on the sides of this view 106 protected int mPadding = 0; 107 108 protected Rect r = new Rect(); 109 protected Paint p = new Paint(); 110 protected Paint mMonthNumPaint; 111 protected Drawable mSelectedDayLine; 112 113 // Cache the number strings so we don't have to recompute them each time 114 protected String[] mDayNumbers; 115 // Quick lookup for checking which days are in the focus month 116 protected boolean[] mFocusDay; 117 // The Julian day of the first day displayed by this item 118 protected int mFirstJulianDay = -1; 119 // The month of the first day in this week 120 protected int mFirstMonth = -1; 121 // The month of the last day in this week 122 protected int mLastMonth = -1; 123 // The position of this week, equivalent to weeks since the week of Jan 1st, 124 // 1970 125 protected int mWeek = -1; 126 // Quick reference to the width of this view, matches parent 127 protected int mWidth; 128 // The height this view should draw at in pixels, set by height param 129 protected int mHeight = DEFAULT_HEIGHT; 130 // Whether the week number should be shown 131 protected boolean mShowWeekNum = false; 132 // If this view contains the selected day 133 protected boolean mHasSelectedDay = false; 134 // If this view contains the today 135 protected boolean mHasToday = false; 136 // Which day is selected [0-6] or -1 if no day is selected 137 protected int mSelectedDay = DEFAULT_SELECTED_DAY; 138 // Which day is today [0-6] or -1 if no day is today 139 protected int mToday = DEFAULT_SELECTED_DAY; 140 // Which day of the week to start on [0-6] 141 protected int mWeekStart = DEFAULT_WEEK_START; 142 // How many days to display 143 protected int mNumDays = DEFAULT_NUM_DAYS; 144 // The number of days + a spot for week number if it is displayed 145 protected int mNumCells = mNumDays; 146 // The left edge of the selected day 147 protected int mSelectedLeft = -1; 148 // The right edge of the selected day 149 protected int mSelectedRight = -1; 150 // The timezone to display times/dates in (used for determining when Today 151 // is) 152 protected String mTimeZone = Time.getCurrentTimezone(); 153 154 protected int mBGColor; 155 protected int mSelectedWeekBGColor; 156 protected int mFocusMonthColor; 157 protected int mOtherMonthColor; 158 protected int mDaySeparatorColor; 159 protected int mTodayOutlineColor; 160 protected int mWeekNumColor; 161 162 public SimpleWeekView(Context context) { 163 super(context); 164 165 Resources res = context.getResources(); 166 167 mBGColor = res.getColor(R.color.month_bgcolor); 168 mSelectedWeekBGColor = res.getColor(R.color.month_selected_week_bgcolor); 169 mFocusMonthColor = res.getColor(R.color.month_mini_day_number); 170 mOtherMonthColor = res.getColor(R.color.month_other_month_day_number); 171 mDaySeparatorColor = res.getColor(R.color.month_grid_lines); 172 mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color); 173 mWeekNumColor = res.getColor(R.color.month_week_num_color); 174 mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light); 175 176 if (mScale == 0) { 177 mScale = context.getResources().getDisplayMetrics().density; 178 if (mScale != 1) { 179 DEFAULT_HEIGHT *= mScale; 180 MIN_HEIGHT *= mScale; 181 MINI_DAY_NUMBER_TEXT_SIZE *= mScale; 182 MINI_TODAY_NUMBER_TEXT_SIZE *= mScale; 183 MINI_TODAY_OUTLINE_WIDTH *= mScale; 184 WEEK_NUM_MARGIN_BOTTOM *= mScale; 185 DAY_SEPARATOR_WIDTH *= mScale; 186 MINI_WK_NUMBER_TEXT_SIZE *= mScale; 187 } 188 } 189 190 // Sets up any standard paints that will be used 191 initView(); 192 } 193 194 /** 195 * Sets all the parameters for displaying this week. The only required 196 * parameter is the week number. Other parameters have a default value and 197 * will only update if a new value is included, except for focus month, 198 * which will always default to no focus month if no value is passed in. See 199 * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters. 200 * 201 * @param params A map of the new parameters, see 202 * {@link #VIEW_PARAMS_HEIGHT} 203 * @param tz The time zone this view should reference times in 204 */ 205 public void setWeekParams(HashMap<String, Integer> params, String tz) { 206 if (!params.containsKey(VIEW_PARAMS_WEEK)) { 207 throw new InvalidParameterException("You must specify the week number for this view"); 208 } 209 setTag(params); 210 mTimeZone = tz; 211 // We keep the current value for any params not present 212 if (params.containsKey(VIEW_PARAMS_HEIGHT)) { 213 mHeight = params.get(VIEW_PARAMS_HEIGHT); 214 if (mHeight < MIN_HEIGHT) { 215 mHeight = MIN_HEIGHT; 216 } 217 } 218 if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) { 219 mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY); 220 } 221 mHasSelectedDay = mSelectedDay != -1; 222 if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) { 223 mNumDays = params.get(VIEW_PARAMS_NUM_DAYS); 224 } 225 if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) { 226 if (params.get(VIEW_PARAMS_SHOW_WK_NUM) != 0) { 227 mShowWeekNum = true; 228 } else { 229 mShowWeekNum = false; 230 } 231 } 232 mNumCells = mShowWeekNum ? mNumDays + 1 : mNumDays; 233 234 // Allocate space for caching the day numbers and focus values 235 mDayNumbers = new String[mNumCells]; 236 mFocusDay = new boolean[mNumCells]; 237 mWeek = params.get(VIEW_PARAMS_WEEK); 238 int julianMonday = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek); 239 Time time = new Time(tz); 240 time.setJulianDay(julianMonday); 241 242 // If we're showing the week number calculate it based on Monday 243 int i = 0; 244 if (mShowWeekNum) { 245 mDayNumbers[0] = Integer.toString(time.getWeekNumber()); 246 i++; 247 } 248 249 if (params.containsKey(VIEW_PARAMS_WEEK_START)) { 250 mWeekStart = params.get(VIEW_PARAMS_WEEK_START); 251 } 252 253 // Now adjust our starting day based on the start day of the week 254 // If the week is set to start on a Saturday the first week will be 255 // Dec 27th 1969 -Jan 2nd, 1970 256 if (time.weekDay != mWeekStart) { 257 int diff = time.weekDay - mWeekStart; 258 if (diff < 0) { 259 diff += 7; 260 } 261 time.monthDay -= diff; 262 time.normalize(true); 263 } 264 265 mFirstJulianDay = Time.getJulianDay(time.toMillis(true), time.gmtoff); 266 mFirstMonth = time.month; 267 268 // Figure out what day today is 269 Time today = new Time(tz); 270 today.setToNow(); 271 mHasToday = false; 272 mToday = -1; 273 274 int focusMonth = params.containsKey(VIEW_PARAMS_FOCUS_MONTH) ? params.get( 275 VIEW_PARAMS_FOCUS_MONTH) 276 : DEFAULT_FOCUS_MONTH; 277 278 for (; i < mNumCells; i++) { 279 if (time.monthDay == 1) { 280 mFirstMonth = time.month; 281 } 282 if (time.month == focusMonth) { 283 mFocusDay[i] = true; 284 } else { 285 mFocusDay[i] = false; 286 } 287 if (time.year == today.year && time.yearDay == today.yearDay) { 288 mHasToday = true; 289 mToday = i; 290 } 291 mDayNumbers[i] = Integer.toString(time.monthDay++); 292 time.normalize(true); 293 } 294 // We do one extra add at the end of the loop, if that pushed us to a 295 // new month undo it 296 if (time.monthDay == 1) { 297 time.monthDay--; 298 time.normalize(true); 299 } 300 mLastMonth = time.month; 301 302 updateSelectionPositions(); 303 } 304 305 /** 306 * Sets up the text and style properties for painting. Override this if you 307 * want to use a different paint. 308 */ 309 protected void initView() { 310 p.setFakeBoldText(false); 311 p.setAntiAlias(true); 312 p.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE); 313 p.setStyle(Style.FILL); 314 315 mMonthNumPaint = new Paint(); 316 mMonthNumPaint.setFakeBoldText(true); 317 mMonthNumPaint.setAntiAlias(true); 318 mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE); 319 mMonthNumPaint.setColor(mFocusMonthColor); 320 mMonthNumPaint.setStyle(Style.FILL); 321 mMonthNumPaint.setTextAlign(Align.CENTER); 322 } 323 324 /** 325 * Returns the month of the first day in this week 326 * 327 * @return The month the first day of this view is in 328 */ 329 public int getFirstMonth() { 330 return mFirstMonth; 331 } 332 333 /** 334 * Returns the month of the last day in this week 335 * 336 * @return The month the last day of this view is in 337 */ 338 public int getLastMonth() { 339 return mLastMonth; 340 } 341 342 /** 343 * Returns the julian day of the first day in this view. 344 * 345 * @return The julian day of the first day in the view. 346 */ 347 public int getFirstJulianDay() { 348 return mFirstJulianDay; 349 } 350 351 /** 352 * Calculates the day that the given x position is in, accounting for week 353 * number. Returns a Time referencing that day or null if 354 * 355 * @param x The x position of the touch event 356 * @return A time object for the tapped day or null if the position wasn't 357 * in a day 358 */ 359 public Time getDayFromLocation(float x) { 360 int dayStart = mShowWeekNum ? (mWidth - mPadding * 2) / mNumCells + mPadding : mPadding; 361 if (x < dayStart || x > mWidth - mPadding) { 362 return null; 363 } 364 // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels 365 int dayPosition = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); 366 int day = mFirstJulianDay + dayPosition; 367 368 Time time = new Time(mTimeZone); 369 if (mWeek == 0) { 370 // This week is weird... 371 if (day < Time.EPOCH_JULIAN_DAY) { 372 day++; 373 } else if (day == Time.EPOCH_JULIAN_DAY) { 374 time.set(1, 0, 1970); 375 time.normalize(true); 376 return time; 377 } 378 } 379 380 time.setJulianDay(day); 381 return time; 382 } 383 384 @Override 385 protected void onDraw(Canvas canvas) { 386 drawBackground(canvas); 387 drawWeekNums(canvas); 388 drawDaySeparators(canvas); 389 } 390 391 /** 392 * This draws the selection highlight if a day is selected in this week. 393 * Override this method if you wish to have a different background drawn. 394 * 395 * @param canvas The canvas to draw on 396 */ 397 protected void drawBackground(Canvas canvas) { 398 if (mHasSelectedDay) { 399 p.setColor(mSelectedWeekBGColor); 400 p.setStyle(Style.FILL); 401 } else { 402 return; 403 } 404 r.top = 1; 405 r.bottom = mHeight - 1; 406 r.left = mPadding; 407 r.right = mSelectedLeft; 408 canvas.drawRect(r, p); 409 r.left = mSelectedRight; 410 r.right = mWidth - mPadding; 411 canvas.drawRect(r, p); 412 } 413 414 /** 415 * Draws the week and month day numbers for this week. Override this method 416 * if you need different placement. 417 * 418 * @param canvas The canvas to draw on 419 */ 420 protected void drawWeekNums(Canvas canvas) { 421 int y = ((mHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH; 422 int nDays = mNumCells; 423 424 int i = 0; 425 int divisor = 2 * nDays; 426 if (mShowWeekNum) { 427 p.setTextSize(MINI_WK_NUMBER_TEXT_SIZE); 428 p.setStyle(Style.FILL); 429 p.setTextAlign(Align.CENTER); 430 p.setAntiAlias(true); 431 p.setColor(mWeekNumColor); 432 int x = (mWidth - mPadding * 2) / divisor + mPadding; 433 canvas.drawText(mDayNumbers[0], x, y, p); 434 i++; 435 } 436 437 boolean isFocusMonth = mFocusDay[i]; 438 mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor); 439 mMonthNumPaint.setFakeBoldText(false); 440 for (; i < nDays; i++) { 441 if (mFocusDay[i] != isFocusMonth) { 442 isFocusMonth = mFocusDay[i]; 443 mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor); 444 } 445 if (mHasToday && mToday == i) { 446 mMonthNumPaint.setTextSize(MINI_TODAY_NUMBER_TEXT_SIZE); 447 mMonthNumPaint.setFakeBoldText(true); 448 } 449 int x = (2 * i + 1) * (mWidth - mPadding * 2) / (divisor) + mPadding; 450 canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint); 451 if (mHasToday && mToday == i) { 452 mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE); 453 mMonthNumPaint.setFakeBoldText(false); 454 } 455 } 456 } 457 458 /** 459 * Draws a horizontal line for separating the weeks. Override this method if 460 * you want custom separators. 461 * 462 * @param canvas The canvas to draw on 463 */ 464 protected void drawDaySeparators(Canvas canvas) { 465 if (mHasSelectedDay) { 466 r.top = 1; 467 r.bottom = mHeight - 1; 468 r.left = mSelectedLeft + 1; 469 r.right = mSelectedRight - 1; 470 p.setStrokeWidth(MINI_TODAY_OUTLINE_WIDTH); 471 p.setStyle(Style.STROKE); 472 p.setColor(mTodayOutlineColor); 473 canvas.drawRect(r, p); 474 } 475 if (mShowWeekNum) { 476 p.setColor(mDaySeparatorColor); 477 p.setStrokeWidth(DAY_SEPARATOR_WIDTH); 478 479 int x = (mWidth - mPadding * 2) / mNumCells + mPadding; 480 canvas.drawLine(x, 0, x, mHeight, p); 481 } 482 } 483 484 @Override 485 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 486 mWidth = w; 487 updateSelectionPositions(); 488 } 489 490 /** 491 * This calculates the positions for the selected day lines. 492 */ 493 protected void updateSelectionPositions() { 494 if (mHasSelectedDay) { 495 int selectedPosition = mSelectedDay - mWeekStart; 496 if (selectedPosition < 0) { 497 selectedPosition += 7; 498 } 499 if (mShowWeekNum) { 500 selectedPosition++; 501 } 502 mSelectedLeft = selectedPosition * (mWidth - mPadding * 2) / mNumCells 503 + mPadding; 504 mSelectedRight = (selectedPosition + 1) * (mWidth - mPadding * 2) / mNumCells 505 + mPadding; 506 } 507 } 508 509 @Override 510 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 511 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); 512 } 513}