CalendarView.java revision f5926962cc665d4a2e6464f9ba9e3e9788496a6f
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 android.widget; 18 19import com.android.internal.R; 20 21import android.annotation.Widget; 22import android.app.Service; 23import android.content.Context; 24import android.content.res.Configuration; 25import android.content.res.TypedArray; 26import android.database.DataSetObserver; 27import android.graphics.Canvas; 28import android.graphics.Paint; 29import android.graphics.Paint.Align; 30import android.graphics.Paint.Style; 31import android.graphics.Rect; 32import android.graphics.drawable.Drawable; 33import android.text.TextUtils; 34import android.text.format.DateFormat; 35import android.text.format.DateUtils; 36import android.util.AttributeSet; 37import android.util.DisplayMetrics; 38import android.util.Log; 39import android.util.TypedValue; 40import android.view.GestureDetector; 41import android.view.LayoutInflater; 42import android.view.MotionEvent; 43import android.view.View; 44import android.view.ViewGroup; 45import android.widget.AbsListView.OnScrollListener; 46 47import java.text.ParseException; 48import java.text.SimpleDateFormat; 49import java.util.Calendar; 50import java.util.Locale; 51import java.util.TimeZone; 52 53import libcore.icu.LocaleData; 54 55/** 56 * This class is a calendar widget for displaying and selecting dates. The range 57 * of dates supported by this calendar is configurable. A user can select a date 58 * by taping on it and can scroll and fling the calendar to a desired date. 59 * 60 * @attr ref android.R.styleable#CalendarView_showWeekNumber 61 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek 62 * @attr ref android.R.styleable#CalendarView_minDate 63 * @attr ref android.R.styleable#CalendarView_maxDate 64 * @attr ref android.R.styleable#CalendarView_shownWeekCount 65 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor 66 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor 67 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor 68 * @attr ref android.R.styleable#CalendarView_weekNumberColor 69 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor 70 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar 71 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance 72 * @attr ref android.R.styleable#CalendarView_dateTextAppearance 73 */ 74@Widget 75public class CalendarView extends FrameLayout { 76 77 /** 78 * Tag for logging. 79 */ 80 private static final String LOG_TAG = CalendarView.class.getSimpleName(); 81 82 /** 83 * Default value whether to show week number. 84 */ 85 private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; 86 87 /** 88 * The number of milliseconds in a day.e 89 */ 90 private static final long MILLIS_IN_DAY = 86400000L; 91 92 /** 93 * The number of day in a week. 94 */ 95 private static final int DAYS_PER_WEEK = 7; 96 97 /** 98 * The number of milliseconds in a week. 99 */ 100 private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; 101 102 /** 103 * Affects when the month selection will change while scrolling upe 104 */ 105 private static final int SCROLL_HYST_WEEKS = 2; 106 107 /** 108 * How long the GoTo fling animation should last. 109 */ 110 private static final int GOTO_SCROLL_DURATION = 1000; 111 112 /** 113 * The duration of the adjustment upon a user scroll in milliseconds. 114 */ 115 private static final int ADJUSTMENT_SCROLL_DURATION = 500; 116 117 /** 118 * How long to wait after receiving an onScrollStateChanged notification 119 * before acting on it. 120 */ 121 private static final int SCROLL_CHANGE_DELAY = 40; 122 123 /** 124 * String for formatting the month name in the title text view. 125 */ 126 private static final String FORMAT_MONTH_NAME = "MMMM, yyyy"; 127 128 /** 129 * String for parsing dates. 130 */ 131 private static final String DATE_FORMAT = "MM/dd/yyyy"; 132 133 /** 134 * The default minimal date. 135 */ 136 private static final String DEFAULT_MIN_DATE = "01/01/1900"; 137 138 /** 139 * The default maximal date. 140 */ 141 private static final String DEFAULT_MAX_DATE = "01/01/2100"; 142 143 private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; 144 145 private static final int DEFAULT_DATE_TEXT_SIZE = 14; 146 147 private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; 148 149 private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; 150 151 private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; 152 153 private static final int UNSCALED_BOTTOM_BUFFER = 20; 154 155 private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; 156 157 private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; 158 159 private final int mWeekSeperatorLineWidth; 160 161 private final int mDateTextSize; 162 163 private final Drawable mSelectedDateVerticalBar; 164 165 private final int mSelectedDateVerticalBarWidth; 166 167 private final int mSelectedWeekBackgroundColor; 168 169 private final int mFocusedMonthDateColor; 170 171 private final int mUnfocusedMonthDateColor; 172 173 private final int mWeekSeparatorLineColor; 174 175 private final int mWeekNumberColor; 176 177 /** 178 * The top offset of the weeks list. 179 */ 180 private int mListScrollTopOffset = 2; 181 182 /** 183 * The visible height of a week view. 184 */ 185 private int mWeekMinVisibleHeight = 12; 186 187 /** 188 * The visible height of a week view. 189 */ 190 private int mBottomBuffer = 20; 191 192 /** 193 * The number of shown weeks. 194 */ 195 private int mShownWeekCount; 196 197 /** 198 * Flag whether to show the week number. 199 */ 200 private boolean mShowWeekNumber; 201 202 /** 203 * The number of day per week to be shown. 204 */ 205 private int mDaysPerWeek = 7; 206 207 /** 208 * The friction of the week list while flinging. 209 */ 210 private float mFriction = .05f; 211 212 /** 213 * Scale for adjusting velocity of the week list while flinging. 214 */ 215 private float mVelocityScale = 0.333f; 216 217 /** 218 * The adapter for the weeks list. 219 */ 220 private WeeksAdapter mAdapter; 221 222 /** 223 * The weeks list. 224 */ 225 private ListView mListView; 226 227 /** 228 * The name of the month to display. 229 */ 230 private TextView mMonthName; 231 232 /** 233 * The header with week day names. 234 */ 235 private ViewGroup mDayNamesHeader; 236 237 /** 238 * Cached labels for the week names header. 239 */ 240 private String[] mDayLabels; 241 242 /** 243 * The first day of the week. 244 */ 245 private int mFirstDayOfWeek; 246 247 /** 248 * Which month should be displayed/highlighted [0-11]. 249 */ 250 private int mCurrentMonthDisplayed; 251 252 /** 253 * Used for tracking during a scroll. 254 */ 255 private long mPreviousScrollPosition; 256 257 /** 258 * Used for tracking which direction the view is scrolling. 259 */ 260 private boolean mIsScrollingUp = false; 261 262 /** 263 * The previous scroll state of the weeks ListView. 264 */ 265 private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; 266 267 /** 268 * The current scroll state of the weeks ListView. 269 */ 270 private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; 271 272 /** 273 * Listener for changes in the selected day. 274 */ 275 private OnDateChangeListener mOnDateChangeListener; 276 277 /** 278 * Command for adjusting the position after a scroll/fling. 279 */ 280 private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); 281 282 /** 283 * Temporary instance to avoid multiple instantiations. 284 */ 285 private Calendar mTempDate; 286 287 /** 288 * The first day of the focused month. 289 */ 290 private Calendar mFirstDayOfMonth; 291 292 /** 293 * The start date of the range supported by this picker. 294 */ 295 private Calendar mMinDate; 296 297 /** 298 * The end date of the range supported by this picker. 299 */ 300 private Calendar mMaxDate; 301 302 /** 303 * Date format for parsing dates. 304 */ 305 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); 306 307 /** 308 * The current locale. 309 */ 310 private Locale mCurrentLocale; 311 312 /** 313 * The callback used to indicate the user changes the date. 314 */ 315 public interface OnDateChangeListener { 316 317 /** 318 * Called upon change of the selected day. 319 * 320 * @param view The view associated with this listener. 321 * @param year The year that was set. 322 * @param month The month that was set [0-11]. 323 * @param dayOfMonth The day of the month that was set. 324 */ 325 public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth); 326 } 327 328 public CalendarView(Context context) { 329 this(context, null); 330 } 331 332 public CalendarView(Context context, AttributeSet attrs) { 333 this(context, attrs, 0); 334 } 335 336 public CalendarView(Context context, AttributeSet attrs, int defStyle) { 337 super(context, attrs, 0); 338 339 // initialization based on locale 340 setCurrentLocale(Locale.getDefault()); 341 342 TypedValue calendarViewStyle = new TypedValue(); 343 context.getTheme().resolveAttribute(R.attr.calendarViewStyle, calendarViewStyle, true); 344 TypedArray attributesArray = context.obtainStyledAttributes(calendarViewStyle.resourceId, 345 R.styleable.CalendarView); 346 mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, 347 DEFAULT_SHOW_WEEK_NUMBER); 348 mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, 349 LocaleData.get(Locale.getDefault()).firstDayOfWeek); 350 String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); 351 if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { 352 parseDate(DEFAULT_MIN_DATE, mMinDate); 353 } 354 String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); 355 if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { 356 parseDate(DEFAULT_MAX_DATE, mMaxDate); 357 } 358 mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, 359 DEFAULT_SHOWN_WEEK_COUNT); 360 mSelectedWeekBackgroundColor = attributesArray.getColor( 361 R.styleable.CalendarView_selectedWeekBackgroundColor, 0); 362 mFocusedMonthDateColor = attributesArray.getColor( 363 R.styleable.CalendarView_focusedMonthDateColor, 0); 364 mUnfocusedMonthDateColor = attributesArray.getColor( 365 R.styleable.CalendarView_unfocusedMonthDateColor, 0); 366 mWeekSeparatorLineColor = attributesArray.getColor( 367 R.styleable.CalendarView_weekSeparatorLineColor, 0); 368 mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); 369 mSelectedDateVerticalBar = attributesArray.getDrawable( 370 R.styleable.CalendarView_selectedDateVerticalBar); 371 372 int dateTextAppearanceResId= attributesArray.getResourceId( 373 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); 374 TypedArray dateTextAppearance = context.obtainStyledAttributes(dateTextAppearanceResId, 375 com.android.internal.R.styleable.TextAppearance); 376 mDateTextSize = dateTextAppearance.getDimensionPixelSize( 377 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); 378 379 int weekDayTextAppearanceResId = attributesArray.getResourceId( 380 R.styleable.CalendarView_weekDayTextAppearance, 381 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); 382 attributesArray.recycle(); 383 384 DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 385 mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 386 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); 387 mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 388 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); 389 mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 390 UNSCALED_BOTTOM_BUFFER, displayMetrics); 391 mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 392 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); 393 mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 394 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); 395 396 LayoutInflater layoutInflater = (LayoutInflater) mContext 397 .getSystemService(Service.LAYOUT_INFLATER_SERVICE); 398 View content = layoutInflater.inflate(R.layout.calendar_view, null, false); 399 addView(content); 400 401 mListView = (ListView) findViewById(R.id.list); 402 mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); 403 mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); 404 405 setUpHeader(weekDayTextAppearanceResId); 406 setUpListView(); 407 setUpAdapter(); 408 409 // go to today now 410 mTempDate.setTimeInMillis(System.currentTimeMillis()); 411 goTo(mTempDate, false, true, true); 412 invalidate(); 413 } 414 415 @Override 416 public void setEnabled(boolean enabled) { 417 mListView.setEnabled(enabled); 418 } 419 420 @Override 421 public boolean isEnabled() { 422 return mListView.isEnabled(); 423 } 424 425 @Override 426 protected void onConfigurationChanged(Configuration newConfig) { 427 super.onConfigurationChanged(newConfig); 428 setCurrentLocale(newConfig.locale); 429 } 430 431 /** 432 * Gets the minimal date supported by this {@link CalendarView} in milliseconds 433 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 434 * zone. 435 * <p> 436 * Note: The default minimal date is 01/01/1900. 437 * <p> 438 * 439 * @return The minimal supported date. 440 */ 441 public long getMinDate() { 442 return mMinDate.getTimeInMillis(); 443 } 444 445 /** 446 * Sets the minimal date supported by this {@link CalendarView} in milliseconds 447 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 448 * zone. 449 * 450 * @param minDate The minimal supported date. 451 */ 452 public void setMinDate(long minDate) { 453 mTempDate.setTimeInMillis(minDate); 454 if (isSameDate(mTempDate, mMinDate)) { 455 return; 456 } 457 mMinDate.setTimeInMillis(minDate); 458 // make sure the current date is not earlier than 459 // the new min date since the latter is used for 460 // calculating the indices in the adapter thus 461 // avoiding out of bounds error 462 Calendar date = mAdapter.mSelectedDate; 463 if (date.before(mMinDate)) { 464 mAdapter.setSelectedDay(mMinDate); 465 } 466 // reinitialize the adapter since its range depends on min date 467 mAdapter.init(); 468 if (date.before(mMinDate)) { 469 setDate(mTempDate.getTimeInMillis()); 470 } else { 471 // we go to the current date to force the ListView to query its 472 // adapter for the shown views since we have changed the adapter 473 // range and the base from which the later calculates item indices 474 // note that calling setDate will not work since the date is the same 475 goTo(date, false, true, false); 476 } 477 } 478 479 /** 480 * Gets the maximal date supported by this {@link CalendarView} in milliseconds 481 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 482 * zone. 483 * <p> 484 * Note: The default maximal date is 01/01/2100. 485 * <p> 486 * 487 * @return The maximal supported date. 488 */ 489 public long getMaxDate() { 490 return mMaxDate.getTimeInMillis(); 491 } 492 493 /** 494 * Sets the maximal date supported by this {@link CalendarView} in milliseconds 495 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 496 * zone. 497 * 498 * @param maxDate The maximal supported date. 499 */ 500 public void setMaxDate(long maxDate) { 501 mTempDate.setTimeInMillis(maxDate); 502 if (isSameDate(mTempDate, mMaxDate)) { 503 return; 504 } 505 mMaxDate.setTimeInMillis(maxDate); 506 // reinitialize the adapter since its range depends on max date 507 mAdapter.init(); 508 Calendar date = mAdapter.mSelectedDate; 509 if (date.after(mMaxDate)) { 510 setDate(mMaxDate.getTimeInMillis()); 511 } else { 512 // we go to the current date to force the ListView to query its 513 // adapter for the shown views since we have changed the adapter 514 // range and the base from which the later calculates item indices 515 // note that calling setDate will not work since the date is the same 516 goTo(date, false, true, false); 517 } 518 } 519 520 /** 521 * Sets whether to show the week number. 522 * 523 * @param showWeekNumber True to show the week number. 524 */ 525 public void setShowWeekNumber(boolean showWeekNumber) { 526 if (mShowWeekNumber == showWeekNumber) { 527 return; 528 } 529 mShowWeekNumber = showWeekNumber; 530 mAdapter.notifyDataSetChanged(); 531 setUpHeader(DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); 532 } 533 534 /** 535 * Gets whether to show the week number. 536 * 537 * @return True if showing the week number. 538 */ 539 public boolean getShowWeekNumber() { 540 return mShowWeekNumber; 541 } 542 543 /** 544 * Gets the first day of week. 545 * 546 * @return The first day of the week conforming to the {@link CalendarView} 547 * APIs. 548 * @see Calendar#MONDAY 549 * @see Calendar#TUESDAY 550 * @see Calendar#WEDNESDAY 551 * @see Calendar#THURSDAY 552 * @see Calendar#FRIDAY 553 * @see Calendar#SATURDAY 554 * @see Calendar#SUNDAY 555 */ 556 public int getFirstDayOfWeek() { 557 return mFirstDayOfWeek; 558 } 559 560 /** 561 * Sets the first day of week. 562 * 563 * @param firstDayOfWeek The first day of the week conforming to the 564 * {@link CalendarView} APIs. 565 * @see Calendar#MONDAY 566 * @see Calendar#TUESDAY 567 * @see Calendar#WEDNESDAY 568 * @see Calendar#THURSDAY 569 * @see Calendar#FRIDAY 570 * @see Calendar#SATURDAY 571 * @see Calendar#SUNDAY 572 */ 573 public void setFirstDayOfWeek(int firstDayOfWeek) { 574 if (mFirstDayOfWeek == firstDayOfWeek) { 575 return; 576 } 577 mFirstDayOfWeek = firstDayOfWeek; 578 mAdapter.init(); 579 mAdapter.notifyDataSetChanged(); 580 setUpHeader(DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); 581 } 582 583 /** 584 * Sets the listener to be notified upon selected date change. 585 * 586 * @param listener The listener to be notified. 587 */ 588 public void setOnDateChangeListener(OnDateChangeListener listener) { 589 mOnDateChangeListener = listener; 590 } 591 592 /** 593 * Gets the selected date in milliseconds since January 1, 1970 00:00:00 in 594 * {@link TimeZone#getDefault()} time zone. 595 * 596 * @return The selected date. 597 */ 598 public long getDate() { 599 return mAdapter.mSelectedDate.getTimeInMillis(); 600 } 601 602 /** 603 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in 604 * {@link TimeZone#getDefault()} time zone. 605 * 606 * @param date The selected date. 607 * 608 * @throws IllegalArgumentException of the provided date is before the 609 * minimal or after the maximal date. 610 * 611 * @see #setDate(long, boolean, boolean) 612 * @see #setMinDate(long) 613 * @see #setMaxDate(long) 614 */ 615 public void setDate(long date) { 616 setDate(date, false, false); 617 } 618 619 /** 620 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in 621 * {@link TimeZone#getDefault()} time zone. 622 * 623 * @param date The date. 624 * @param animate Whether to animate the scroll to the current date. 625 * @param center Whether to center the current date even if it is already visible. 626 * 627 * @throws IllegalArgumentException of the provided date is before the 628 * minimal or after the maximal date. 629 * 630 * @see #setMinDate(long) 631 * @see #setMaxDate(long) 632 */ 633 public void setDate(long date, boolean animate, boolean center) { 634 mTempDate.setTimeInMillis(date); 635 if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { 636 return; 637 } 638 goTo(mTempDate, animate, true, center); 639 } 640 641 /** 642 * Sets the current locale. 643 * 644 * @param locale The current locale. 645 */ 646 private void setCurrentLocale(Locale locale) { 647 if (locale.equals(mCurrentLocale)) { 648 return; 649 } 650 651 mCurrentLocale = locale; 652 653 mTempDate = getCalendarForLocale(mTempDate, locale); 654 mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); 655 mMinDate = getCalendarForLocale(mMinDate, locale); 656 mMaxDate = getCalendarForLocale(mMaxDate, locale); 657 } 658 659 /** 660 * Gets a calendar for locale bootstrapped with the value of a given calendar. 661 * 662 * @param oldCalendar The old calendar. 663 * @param locale The locale. 664 */ 665 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 666 if (oldCalendar == null) { 667 return Calendar.getInstance(locale); 668 } else { 669 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 670 Calendar newCalendar = Calendar.getInstance(locale); 671 newCalendar.setTimeInMillis(currentTimeMillis); 672 return newCalendar; 673 } 674 } 675 676 /** 677 * @return True if the <code>firstDate</code> is the same as the <code> 678 * secondDate</code>. 679 */ 680 private boolean isSameDate(Calendar firstDate, Calendar secondDate) { 681 return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) 682 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); 683 } 684 685 /** 686 * Creates a new adapter if necessary and sets up its parameters. 687 */ 688 private void setUpAdapter() { 689 if (mAdapter == null) { 690 mAdapter = new WeeksAdapter(getContext()); 691 mAdapter.registerDataSetObserver(new DataSetObserver() { 692 @Override 693 public void onChanged() { 694 if (mOnDateChangeListener != null) { 695 Calendar selectedDay = mAdapter.getSelectedDay(); 696 mOnDateChangeListener.onSelectedDayChange(CalendarView.this, 697 selectedDay.get(Calendar.YEAR), 698 selectedDay.get(Calendar.MONTH), 699 selectedDay.get(Calendar.DAY_OF_MONTH)); 700 } 701 } 702 }); 703 mListView.setAdapter(mAdapter); 704 } 705 706 // refresh the view with the new parameters 707 mAdapter.notifyDataSetChanged(); 708 } 709 710 /** 711 * Sets up the strings to be used by the header. 712 */ 713 private void setUpHeader(int weekDayTextAppearanceResId) { 714 mDayLabels = new String[mDaysPerWeek]; 715 for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { 716 int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; 717 mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, 718 DateUtils.LENGTH_SHORTEST); 719 } 720 721 TextView label = (TextView) mDayNamesHeader.getChildAt(0); 722 if (mShowWeekNumber) { 723 label.setVisibility(View.VISIBLE); 724 } else { 725 label.setVisibility(View.GONE); 726 } 727 for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { 728 label = (TextView) mDayNamesHeader.getChildAt(i); 729 if (weekDayTextAppearanceResId > -1) { 730 label.setTextAppearance(mContext, weekDayTextAppearanceResId); 731 } 732 if (i < mDaysPerWeek + 1) { 733 label.setText(mDayLabels[i - 1]); 734 label.setVisibility(View.VISIBLE); 735 } else { 736 label.setVisibility(View.GONE); 737 } 738 } 739 mDayNamesHeader.invalidate(); 740 } 741 742 /** 743 * Sets all the required fields for the list view. 744 */ 745 private void setUpListView() { 746 // Configure the listview 747 mListView.setDivider(null); 748 mListView.setItemsCanFocus(true); 749 mListView.setVerticalScrollBarEnabled(false); 750 mListView.setOnScrollListener(new OnScrollListener() { 751 public void onScrollStateChanged(AbsListView view, int scrollState) { 752 CalendarView.this.onScrollStateChanged(view, scrollState); 753 } 754 755 public void onScroll( 756 AbsListView view, int firstVisibleItem, int visibleItemCount, 757 int totalItemCount) { 758 CalendarView.this.onScroll(view, firstVisibleItem, visibleItemCount, 759 totalItemCount); 760 } 761 }); 762 // Make the scrolling behavior nicer 763 mListView.setFriction(mFriction); 764 mListView.setVelocityScale(mVelocityScale); 765 } 766 767 /** 768 * This moves to the specified time in the view. If the time is not already 769 * in range it will move the list so that the first of the month containing 770 * the time is at the top of the view. If the new time is already in view 771 * the list will not be scrolled unless forceScroll is true. This time may 772 * optionally be highlighted as selected as well. 773 * 774 * @param date The time to move to. 775 * @param animate Whether to scroll to the given time or just redraw at the 776 * new location. 777 * @param setSelected Whether to set the given time as selected. 778 * @param forceScroll Whether to recenter even if the time is already 779 * visible. 780 * 781 * @throws IllegalArgumentException of the provided date is before the 782 * range start of after the range end. 783 */ 784 private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) { 785 if (date.before(mMinDate) || date.after(mMaxDate)) { 786 throw new IllegalArgumentException("Time not between " + mMinDate.getTime() 787 + " and " + mMaxDate.getTime()); 788 } 789 // Find the first and last entirely visible weeks 790 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); 791 View firstChild = mListView.getChildAt(0); 792 if (firstChild != null && firstChild.getTop() < 0) { 793 firstFullyVisiblePosition++; 794 } 795 int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; 796 if (firstChild != null && firstChild.getTop() > mBottomBuffer) { 797 lastFullyVisiblePosition--; 798 } 799 if (setSelected) { 800 mAdapter.setSelectedDay(date); 801 } 802 // Get the week we're going to 803 int position = getWeeksSinceMinDate(date); 804 805 // Check if the selected day is now outside of our visible range 806 // and if so scroll to the month that contains it 807 if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition 808 || forceScroll) { 809 mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); 810 mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); 811 812 setMonthDisplayed(mFirstDayOfMonth); 813 814 // the earliest time we can scroll to is the min date 815 if (mFirstDayOfMonth.before(mMinDate)) { 816 position = 0; 817 } else { 818 position = getWeeksSinceMinDate(mFirstDayOfMonth); 819 } 820 821 mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; 822 if (animate) { 823 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, 824 GOTO_SCROLL_DURATION); 825 } else { 826 mListView.setSelectionFromTop(position, mListScrollTopOffset); 827 // Perform any after scroll operations that are needed 828 onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); 829 } 830 } else if (setSelected) { 831 // Otherwise just set the selection 832 setMonthDisplayed(date); 833 } 834 } 835 836 /** 837 * Parses the given <code>date</code> and in case of success sets 838 * the result to the <code>outDate</code>. 839 * 840 * @return True if the date was parsed. 841 */ 842 private boolean parseDate(String date, Calendar outDate) { 843 try { 844 outDate.setTime(mDateFormat.parse(date)); 845 return true; 846 } catch (ParseException e) { 847 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); 848 return false; 849 } 850 } 851 852 /** 853 * Called when a <code>view</code> transitions to a new <code>scrollState 854 * </code>. 855 */ 856 private void onScrollStateChanged(AbsListView view, int scrollState) { 857 mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); 858 } 859 860 /** 861 * Updates the title and selected month if the <code>view</code> has moved to a new 862 * month. 863 */ 864 private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 865 int totalItemCount) { 866 WeekView child = (WeekView) view.getChildAt(0); 867 if (child == null) { 868 return; 869 } 870 871 // Figure out where we are 872 long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); 873 874 // If we have moved since our last call update the direction 875 if (currScroll < mPreviousScrollPosition) { 876 mIsScrollingUp = true; 877 } else if (currScroll > mPreviousScrollPosition) { 878 mIsScrollingUp = false; 879 } else { 880 return; 881 } 882 883 // Use some hysteresis for checking which month to highlight. This 884 // causes the month to transition when two full weeks of a month are 885 // visible when scrolling up, and when the first day in a month reaches 886 // the top of the screen when scrolling down. 887 int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; 888 if (mIsScrollingUp) { 889 child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); 890 } else if (offset != 0) { 891 child = (WeekView) view.getChildAt(offset); 892 } 893 894 // Find out which month we're moving into 895 int month; 896 if (mIsScrollingUp) { 897 month = child.getMonthOfFirstWeekDay(); 898 } else { 899 month = child.getMonthOfLastWeekDay(); 900 } 901 902 // And how it relates to our current highlighted month 903 int monthDiff; 904 if (mCurrentMonthDisplayed == 11 && month == 0) { 905 monthDiff = 1; 906 } else if (mCurrentMonthDisplayed == 0 && month == 11) { 907 monthDiff = -1; 908 } else { 909 monthDiff = month - mCurrentMonthDisplayed; 910 } 911 912 // Only switch months if we're scrolling away from the currently 913 // selected month 914 if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { 915 Calendar firstDay = child.getFirstDay(); 916 if (mIsScrollingUp) { 917 firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); 918 } else { 919 firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); 920 } 921 setMonthDisplayed(firstDay); 922 } 923 mPreviousScrollPosition = currScroll; 924 mPreviousScrollState = mCurrentScrollState; 925 } 926 927 /** 928 * Sets the month displayed at the top of this view based on time. Override 929 * to add custom events when the title is changed. 930 * 931 * @param calendar A day in the new focus month. 932 */ 933 private void setMonthDisplayed(Calendar calendar) { 934 mMonthName.setText(DateFormat.format(FORMAT_MONTH_NAME, calendar)); 935 mMonthName.invalidate(); 936 mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); 937 mAdapter.setFocusMonth(mCurrentMonthDisplayed); 938 // TODO Send Accessibility Event 939 } 940 941 /** 942 * @return Returns the number of weeks between the current <code>date</code> 943 * and the <code>mMinDate</code>. 944 */ 945 private int getWeeksSinceMinDate(Calendar date) { 946 if (date.before(mMinDate)) { 947 throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() 948 + " does not precede toDate: " + date.getTime()); 949 } 950 long endTimeMillis = date.getTimeInMillis() 951 + date.getTimeZone().getOffset(date.getTimeInMillis()); 952 long startTimeMillis = mMinDate.getTimeInMillis() 953 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); 954 long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) 955 * MILLIS_IN_DAY; 956 return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); 957 } 958 959 /** 960 * Command responsible for acting upon scroll state changes. 961 */ 962 private class ScrollStateRunnable implements Runnable { 963 private AbsListView mView; 964 965 private int mNewState; 966 967 /** 968 * Sets up the runnable with a short delay in case the scroll state 969 * immediately changes again. 970 * 971 * @param view The list view that changed state 972 * @param scrollState The new state it changed to 973 */ 974 public void doScrollStateChange(AbsListView view, int scrollState) { 975 mView = view; 976 mNewState = scrollState; 977 removeCallbacks(this); 978 postDelayed(this, SCROLL_CHANGE_DELAY); 979 } 980 981 public void run() { 982 mCurrentScrollState = mNewState; 983 // Fix the position after a scroll or a fling ends 984 if (mNewState == OnScrollListener.SCROLL_STATE_IDLE 985 && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { 986 View child = mView.getChildAt(0); 987 if (child == null) { 988 // The view is no longer visible, just return 989 return; 990 } 991 int dist = child.getBottom() - mListScrollTopOffset; 992 if (dist > mListScrollTopOffset) { 993 if (mIsScrollingUp) { 994 mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION); 995 } else { 996 mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); 997 } 998 } 999 } 1000 mPreviousScrollState = mNewState; 1001 } 1002 } 1003 1004 /** 1005 * <p> 1006 * This is a specialized adapter for creating a list of weeks with 1007 * selectable days. It can be configured to display the week number, start 1008 * the week on a given day, show a reduced number of days, or display an 1009 * arbitrary number of weeks at a time. 1010 * </p> 1011 */ 1012 private class WeeksAdapter extends BaseAdapter implements OnTouchListener { 1013 1014 private int mSelectedWeek; 1015 1016 private GestureDetector mGestureDetector; 1017 1018 private int mFocusedMonth; 1019 1020 private final Calendar mSelectedDate = Calendar.getInstance(); 1021 1022 private int mTotalWeekCount; 1023 1024 public WeeksAdapter(Context context) { 1025 mContext = context; 1026 mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); 1027 init(); 1028 } 1029 1030 /** 1031 * Set up the gesture detector and selected time 1032 */ 1033 private void init() { 1034 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); 1035 mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); 1036 if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek 1037 || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { 1038 mTotalWeekCount++; 1039 } 1040 } 1041 1042 /** 1043 * Updates the selected day and related parameters. 1044 * 1045 * @param selectedDay The time to highlight 1046 */ 1047 public void setSelectedDay(Calendar selectedDay) { 1048 if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) 1049 && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { 1050 return; 1051 } 1052 mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); 1053 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); 1054 mFocusedMonth = mSelectedDate.get(Calendar.MONTH); 1055 notifyDataSetChanged(); 1056 } 1057 1058 /** 1059 * @return The selected day of month. 1060 */ 1061 public Calendar getSelectedDay() { 1062 return mSelectedDate; 1063 } 1064 1065 @Override 1066 public int getCount() { 1067 return mTotalWeekCount; 1068 } 1069 1070 @Override 1071 public Object getItem(int position) { 1072 return null; 1073 } 1074 1075 @Override 1076 public long getItemId(int position) { 1077 return position; 1078 } 1079 1080 @Override 1081 public View getView(int position, View convertView, ViewGroup parent) { 1082 WeekView weekView = null; 1083 if (convertView != null) { 1084 weekView = (WeekView) convertView; 1085 } else { 1086 weekView = new WeekView(mContext); 1087 android.widget.AbsListView.LayoutParams params = 1088 new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, 1089 LayoutParams.WRAP_CONTENT); 1090 weekView.setLayoutParams(params); 1091 weekView.setClickable(true); 1092 weekView.setOnTouchListener(this); 1093 } 1094 1095 int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( 1096 Calendar.DAY_OF_WEEK) : -1; 1097 weekView.init(position, selectedWeekDay, mFocusedMonth); 1098 1099 return weekView; 1100 } 1101 1102 /** 1103 * Changes which month is in focus and updates the view. 1104 * 1105 * @param month The month to show as in focus [0-11] 1106 */ 1107 public void setFocusMonth(int month) { 1108 if (mFocusedMonth == month) { 1109 return; 1110 } 1111 mFocusedMonth = month; 1112 notifyDataSetChanged(); 1113 } 1114 1115 @Override 1116 public boolean onTouch(View v, MotionEvent event) { 1117 if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { 1118 WeekView weekView = (WeekView) v; 1119 // if we cannot find a day for the given location we are done 1120 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { 1121 return true; 1122 } 1123 // it is possible that the touched day is outside the valid range 1124 // we draw whole weeks but range end can fall not on the week end 1125 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { 1126 return true; 1127 } 1128 onDateTapped(mTempDate); 1129 return true; 1130 } 1131 return false; 1132 } 1133 1134 /** 1135 * Maintains the same hour/min/sec but moves the day to the tapped day. 1136 * 1137 * @param day The day that was tapped 1138 */ 1139 private void onDateTapped(Calendar day) { 1140 setSelectedDay(day); 1141 setMonthDisplayed(day); 1142 } 1143 1144 /** 1145 * This is here so we can identify single tap events and set the 1146 * selected day correctly 1147 */ 1148 class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { 1149 @Override 1150 public boolean onSingleTapUp(MotionEvent e) { 1151 return true; 1152 } 1153 } 1154 } 1155 1156 /** 1157 * <p> 1158 * This is a dynamic view for drawing a single week. It can be configured to 1159 * display the week number, start the week on a given day, or show a reduced 1160 * number of days. It is intended for use as a single view within a 1161 * ListView. See {@link WeeksAdapter} for usage. 1162 * </p> 1163 */ 1164 private class WeekView extends View { 1165 1166 private final Rect mTempRect = new Rect(); 1167 1168 private final Paint mDrawPaint = new Paint(); 1169 1170 private final Paint mMonthNumDrawPaint = new Paint(); 1171 1172 // Cache the number strings so we don't have to recompute them each time 1173 private String[] mDayNumbers; 1174 1175 // Quick lookup for checking which days are in the focus month 1176 private boolean[] mFocusDay; 1177 1178 // The first day displayed by this item 1179 private Calendar mFirstDay; 1180 1181 // The month of the first day in this week 1182 private int mMonthOfFirstWeekDay = -1; 1183 1184 // The month of the last day in this week 1185 private int mLastWeekDayMonth = -1; 1186 1187 // The position of this week, equivalent to weeks since the week of Jan 1188 // 1st, 1900 1189 private int mWeek = -1; 1190 1191 // Quick reference to the width of this view, matches parent 1192 private int mWidth; 1193 1194 // The height this view should draw at in pixels, set by height param 1195 private int mHeight; 1196 1197 // If this view contains the selected day 1198 private boolean mHasSelectedDay = false; 1199 1200 // Which day is selected [0-6] or -1 if no day is selected 1201 private int mSelectedDay = -1; 1202 1203 // The number of days + a spot for week number if it is displayed 1204 private int mNumCells; 1205 1206 // The left edge of the selected day 1207 private int mSelectedLeft = -1; 1208 1209 // The right edge of the selected day 1210 private int mSelectedRight = -1; 1211 1212 public WeekView(Context context) { 1213 super(context); 1214 1215 mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView 1216 .getPaddingBottom()) / mShownWeekCount; 1217 1218 // Sets up any standard paints that will be used 1219 setPaintProperties(); 1220 } 1221 1222 /** 1223 * Initializes this week view. 1224 * 1225 * @param weekNumber The number of the week this view represents. The 1226 * week number is a zero based index of the weeks since 1227 * {@link CalendarView#getMinDate()}. 1228 * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no 1229 * selected day. 1230 * @param focusedMonth The month that is currently in focus i.e. 1231 * highlighted. 1232 */ 1233 public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { 1234 mSelectedDay = selectedWeekDay; 1235 mHasSelectedDay = mSelectedDay != -1; 1236 mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; 1237 mWeek = weekNumber; 1238 mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); 1239 1240 mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); 1241 mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); 1242 1243 // Allocate space for caching the day numbers and focus values 1244 mDayNumbers = new String[mNumCells]; 1245 mFocusDay = new boolean[mNumCells]; 1246 1247 // If we're showing the week number calculate it based on Monday 1248 int i = 0; 1249 if (mShowWeekNumber) { 1250 mDayNumbers[0] = Integer.toString(mTempDate.get(Calendar.WEEK_OF_YEAR)); 1251 i++; 1252 } 1253 1254 // Now adjust our starting day based on the start day of the week 1255 int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); 1256 mTempDate.add(Calendar.DAY_OF_MONTH, diff); 1257 1258 mFirstDay = (Calendar) mTempDate.clone(); 1259 mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); 1260 1261 for (; i < mNumCells; i++) { 1262 mFocusDay[i] = (mTempDate.get(Calendar.MONTH) == focusedMonth); 1263 // do not draw dates outside the valid range to avoid user confusion 1264 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { 1265 mDayNumbers[i] = ""; 1266 } else { 1267 mDayNumbers[i] = Integer.toString(mTempDate.get(Calendar.DAY_OF_MONTH)); 1268 } 1269 mTempDate.add(Calendar.DAY_OF_MONTH, 1); 1270 } 1271 // We do one extra add at the end of the loop, if that pushed us to 1272 // new month undo it 1273 if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { 1274 mTempDate.add(Calendar.DAY_OF_MONTH, -1); 1275 } 1276 mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); 1277 1278 updateSelectionPositions(); 1279 } 1280 1281 /** 1282 * Sets up the text and style properties for painting. 1283 */ 1284 private void setPaintProperties() { 1285 mDrawPaint.setFakeBoldText(false); 1286 mDrawPaint.setAntiAlias(true); 1287 mDrawPaint.setTextSize(mDateTextSize); 1288 mDrawPaint.setStyle(Style.FILL); 1289 1290 mMonthNumDrawPaint.setFakeBoldText(true); 1291 mMonthNumDrawPaint.setAntiAlias(true); 1292 mMonthNumDrawPaint.setTextSize(mDateTextSize); 1293 mMonthNumDrawPaint.setColor(mFocusedMonthDateColor); 1294 mMonthNumDrawPaint.setStyle(Style.FILL); 1295 mMonthNumDrawPaint.setTextAlign(Align.CENTER); 1296 } 1297 1298 /** 1299 * Returns the month of the first day in this week. 1300 * 1301 * @return The month the first day of this view is in. 1302 */ 1303 public int getMonthOfFirstWeekDay() { 1304 return mMonthOfFirstWeekDay; 1305 } 1306 1307 /** 1308 * Returns the month of the last day in this week 1309 * 1310 * @return The month the last day of this view is in 1311 */ 1312 public int getMonthOfLastWeekDay() { 1313 return mLastWeekDayMonth; 1314 } 1315 1316 /** 1317 * Returns the first day in this view. 1318 * 1319 * @return The first day in the view. 1320 */ 1321 public Calendar getFirstDay() { 1322 return mFirstDay; 1323 } 1324 1325 /** 1326 * Calculates the day that the given x position is in, accounting for 1327 * week number. 1328 * 1329 * @param x The x position of the touch event. 1330 * @return True if a day was found for the given location. 1331 */ 1332 public boolean getDayFromLocation(float x, Calendar outCalendar) { 1333 int dayStart = mShowWeekNumber ? mWidth / mNumCells : 0; 1334 if (x < dayStart || x > mWidth) { 1335 outCalendar.clear(); 1336 return false; 1337 } 1338 // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels 1339 int dayPosition = (int) ((x - dayStart) * mDaysPerWeek 1340 / (mWidth - dayStart)); 1341 outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); 1342 outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); 1343 return true; 1344 } 1345 1346 @Override 1347 protected void onDraw(Canvas canvas) { 1348 drawBackground(canvas); 1349 drawWeekNumbers(canvas); 1350 drawWeekSeparators(canvas); 1351 drawSelectedDateVerticalBars(canvas); 1352 } 1353 1354 /** 1355 * This draws the selection highlight if a day is selected in this week. 1356 * 1357 * @param canvas The canvas to draw on 1358 */ 1359 private void drawBackground(Canvas canvas) { 1360 if (!mHasSelectedDay) { 1361 return; 1362 } 1363 mDrawPaint.setColor(mSelectedWeekBackgroundColor); 1364 1365 mTempRect.top = mWeekSeperatorLineWidth; 1366 mTempRect.bottom = mHeight; 1367 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; 1368 mTempRect.right = mSelectedLeft - 2; 1369 canvas.drawRect(mTempRect, mDrawPaint); 1370 1371 mTempRect.left = mSelectedRight + 3; 1372 mTempRect.right = mWidth; 1373 canvas.drawRect(mTempRect, mDrawPaint); 1374 } 1375 1376 /** 1377 * Draws the week and month day numbers for this week. 1378 * 1379 * @param canvas The canvas to draw on 1380 */ 1381 private void drawWeekNumbers(Canvas canvas) { 1382 float textHeight = mDrawPaint.getTextSize(); 1383 int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; 1384 int nDays = mNumCells; 1385 1386 mDrawPaint.setTextAlign(Align.CENTER); 1387 int i = 0; 1388 int divisor = 2 * nDays; 1389 if (mShowWeekNumber) { 1390 mDrawPaint.setColor(mWeekNumberColor); 1391 int x = mWidth / divisor; 1392 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); 1393 i++; 1394 } 1395 for (; i < nDays; i++) { 1396 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor 1397 : mUnfocusedMonthDateColor); 1398 int x = (2 * i + 1) * mWidth / divisor; 1399 canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); 1400 } 1401 } 1402 1403 /** 1404 * Draws a horizontal line for separating the weeks. 1405 * 1406 * @param canvas The canvas to draw on. 1407 */ 1408 private void drawWeekSeparators(Canvas canvas) { 1409 // If it is the topmost fully visible child do not draw separator line 1410 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); 1411 if (mListView.getChildAt(0).getTop() < 0) { 1412 firstFullyVisiblePosition++; 1413 } 1414 if (firstFullyVisiblePosition == mWeek) { 1415 return; 1416 } 1417 mDrawPaint.setColor(mWeekSeparatorLineColor); 1418 mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); 1419 float x = mShowWeekNumber ? mWidth / mNumCells : 0; 1420 canvas.drawLine(x, 0, mWidth, 0, mDrawPaint); 1421 } 1422 1423 /** 1424 * Draws the selected date bars if this week has a selected day. 1425 * 1426 * @param canvas The canvas to draw on 1427 */ 1428 private void drawSelectedDateVerticalBars(Canvas canvas) { 1429 if (!mHasSelectedDay) { 1430 return; 1431 } 1432 mSelectedDateVerticalBar.setBounds(mSelectedLeft - mSelectedDateVerticalBarWidth / 2, 1433 mWeekSeperatorLineWidth, 1434 mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight); 1435 mSelectedDateVerticalBar.draw(canvas); 1436 mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2, 1437 mWeekSeperatorLineWidth, 1438 mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight); 1439 mSelectedDateVerticalBar.draw(canvas); 1440 } 1441 1442 @Override 1443 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1444 mWidth = w; 1445 updateSelectionPositions(); 1446 } 1447 1448 /** 1449 * This calculates the positions for the selected day lines. 1450 */ 1451 private void updateSelectionPositions() { 1452 if (mHasSelectedDay) { 1453 int selectedPosition = mSelectedDay - mFirstDayOfWeek; 1454 if (selectedPosition < 0) { 1455 selectedPosition += 7; 1456 } 1457 if (mShowWeekNumber) { 1458 selectedPosition++; 1459 } 1460 mSelectedLeft = selectedPosition * mWidth / mNumCells; 1461 mSelectedRight = (selectedPosition + 1) * mWidth / mNumCells; 1462 } 1463 } 1464 1465 @Override 1466 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1467 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); 1468 } 1469 } 1470} 1471