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