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