CalendarView.java revision ef71947fd38f7918a628a238fee2ae000bb6bb45
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 android.annotation.Widget; 20import android.app.Service; 21import android.content.Context; 22import android.content.res.Configuration; 23import android.content.res.TypedArray; 24import android.database.DataSetObserver; 25import android.graphics.Canvas; 26import android.graphics.Paint; 27import android.graphics.Paint.Align; 28import android.graphics.Paint.Style; 29import android.graphics.Rect; 30import android.graphics.drawable.Drawable; 31import android.text.TextUtils; 32import android.text.format.DateUtils; 33import android.util.AttributeSet; 34import android.util.DisplayMetrics; 35import android.util.Log; 36import android.util.TypedValue; 37import android.view.GestureDetector; 38import android.view.LayoutInflater; 39import android.view.MotionEvent; 40import android.view.View; 41import android.view.ViewGroup; 42import android.view.accessibility.AccessibilityEvent; 43import android.view.accessibility.AccessibilityNodeInfo; 44import android.widget.AbsListView.OnScrollListener; 45 46import com.android.internal.R; 47 48import java.text.ParseException; 49import java.text.SimpleDateFormat; 50import java.util.Calendar; 51import java.util.Locale; 52import java.util.TimeZone; 53 54import libcore.icu.LocaleData; 55 56/** 57 * This class is a calendar widget for displaying and selecting dates. The range 58 * of dates supported by this calendar is configurable. A user can select a date 59 * by taping on it and can scroll and fling the calendar to a desired date. 60 * 61 * @attr ref android.R.styleable#CalendarView_showWeekNumber 62 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek 63 * @attr ref android.R.styleable#CalendarView_minDate 64 * @attr ref android.R.styleable#CalendarView_maxDate 65 * @attr ref android.R.styleable#CalendarView_shownWeekCount 66 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor 67 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor 68 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor 69 * @attr ref android.R.styleable#CalendarView_weekNumberColor 70 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor 71 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar 72 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance 73 * @attr ref android.R.styleable#CalendarView_dateTextAppearance 74 */ 75@Widget 76public class CalendarView extends FrameLayout { 77 78 /** 79 * Tag for logging. 80 */ 81 private static final String LOG_TAG = CalendarView.class.getSimpleName(); 82 83 private CalendarViewDelegate mDelegate; 84 85 /** 86 * The callback used to indicate the user changes the date. 87 */ 88 public interface OnDateChangeListener { 89 90 /** 91 * Called upon change of the selected day. 92 * 93 * @param view The view associated with this listener. 94 * @param year The year that was set. 95 * @param month The month that was set [0-11]. 96 * @param dayOfMonth The day of the month that was set. 97 */ 98 public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth); 99 } 100 101 public CalendarView(Context context) { 102 this(context, null); 103 } 104 105 public CalendarView(Context context, AttributeSet attrs) { 106 this(context, attrs, R.attr.calendarViewStyle); 107 } 108 109 public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) { 110 this(context, attrs, defStyleAttr, 0); 111 } 112 113 public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 114 super(context, attrs, defStyleAttr, defStyleRes); 115 116 mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes); 117 } 118 119 /** 120 * Sets the number of weeks to be shown. 121 * 122 * @param count The shown week count. 123 * 124 * @attr ref android.R.styleable#CalendarView_shownWeekCount 125 */ 126 public void setShownWeekCount(int count) { 127 mDelegate.setShownWeekCount(count); 128 } 129 130 /** 131 * Gets the number of weeks to be shown. 132 * 133 * @return The shown week count. 134 * 135 * @attr ref android.R.styleable#CalendarView_shownWeekCount 136 */ 137 public int getShownWeekCount() { 138 return mDelegate.getShownWeekCount(); 139 } 140 141 /** 142 * Sets the background color for the selected week. 143 * 144 * @param color The week background color. 145 * 146 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor 147 */ 148 public void setSelectedWeekBackgroundColor(int color) { 149 mDelegate.setSelectedWeekBackgroundColor(color); 150 } 151 152 /** 153 * Gets the background color for the selected week. 154 * 155 * @return The week background color. 156 * 157 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor 158 */ 159 public int getSelectedWeekBackgroundColor() { 160 return mDelegate.getSelectedWeekBackgroundColor(); 161 } 162 163 /** 164 * Sets the color for the dates of the focused month. 165 * 166 * @param color The focused month date color. 167 * 168 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor 169 */ 170 public void setFocusedMonthDateColor(int color) { 171 mDelegate.setFocusedMonthDateColor(color); 172 } 173 174 /** 175 * Gets the color for the dates in the focused month. 176 * 177 * @return The focused month date color. 178 * 179 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor 180 */ 181 public int getFocusedMonthDateColor() { 182 return mDelegate.getFocusedMonthDateColor(); 183 } 184 185 /** 186 * Sets the color for the dates of a not focused month. 187 * 188 * @param color A not focused month date color. 189 * 190 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor 191 */ 192 public void setUnfocusedMonthDateColor(int color) { 193 mDelegate.setUnfocusedMonthDateColor(color); 194 } 195 196 /** 197 * Gets the color for the dates in a not focused month. 198 * 199 * @return A not focused month date color. 200 * 201 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor 202 */ 203 public int getUnfocusedMonthDateColor() { 204 return mDelegate.getUnfocusedMonthDateColor(); 205 } 206 207 /** 208 * Sets the color for the week numbers. 209 * 210 * @param color The week number color. 211 * 212 * @attr ref android.R.styleable#CalendarView_weekNumberColor 213 */ 214 public void setWeekNumberColor(int color) { 215 mDelegate.setWeekNumberColor(color); 216 } 217 218 /** 219 * Gets the color for the week numbers. 220 * 221 * @return The week number color. 222 * 223 * @attr ref android.R.styleable#CalendarView_weekNumberColor 224 */ 225 public int getWeekNumberColor() { 226 return mDelegate.getWeekNumberColor(); 227 } 228 229 /** 230 * Sets the color for the separator line between weeks. 231 * 232 * @param color The week separator color. 233 * 234 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor 235 */ 236 public void setWeekSeparatorLineColor(int color) { 237 mDelegate.setWeekSeparatorLineColor(color); 238 } 239 240 /** 241 * Gets the color for the separator line between weeks. 242 * 243 * @return The week separator color. 244 * 245 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor 246 */ 247 public int getWeekSeparatorLineColor() { 248 return mDelegate.getWeekSeparatorLineColor(); 249 } 250 251 /** 252 * Sets the drawable for the vertical bar shown at the beginning and at 253 * the end of the selected date. 254 * 255 * @param resourceId The vertical bar drawable resource id. 256 * 257 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar 258 */ 259 public void setSelectedDateVerticalBar(int resourceId) { 260 mDelegate.setSelectedDateVerticalBar(resourceId); 261 } 262 263 /** 264 * Sets the drawable for the vertical bar shown at the beginning and at 265 * the end of the selected date. 266 * 267 * @param drawable The vertical bar drawable. 268 * 269 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar 270 */ 271 public void setSelectedDateVerticalBar(Drawable drawable) { 272 mDelegate.setSelectedDateVerticalBar(drawable); 273 } 274 275 /** 276 * Gets the drawable for the vertical bar shown at the beginning and at 277 * the end of the selected date. 278 * 279 * @return The vertical bar drawable. 280 */ 281 public Drawable getSelectedDateVerticalBar() { 282 return mDelegate.getSelectedDateVerticalBar(); 283 } 284 285 /** 286 * Sets the text appearance for the week day abbreviation of the calendar header. 287 * 288 * @param resourceId The text appearance resource id. 289 * 290 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance 291 */ 292 public void setWeekDayTextAppearance(int resourceId) { 293 mDelegate.setWeekDayTextAppearance(resourceId); 294 } 295 296 /** 297 * Gets the text appearance for the week day abbreviation of the calendar header. 298 * 299 * @return The text appearance resource id. 300 * 301 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance 302 */ 303 public int getWeekDayTextAppearance() { 304 return mDelegate.getWeekDayTextAppearance(); 305 } 306 307 /** 308 * Sets the text appearance for the calendar dates. 309 * 310 * @param resourceId The text appearance resource id. 311 * 312 * @attr ref android.R.styleable#CalendarView_dateTextAppearance 313 */ 314 public void setDateTextAppearance(int resourceId) { 315 mDelegate.setDateTextAppearance(resourceId); 316 } 317 318 /** 319 * Gets the text appearance for the calendar dates. 320 * 321 * @return The text appearance resource id. 322 * 323 * @attr ref android.R.styleable#CalendarView_dateTextAppearance 324 */ 325 public int getDateTextAppearance() { 326 return mDelegate.getDateTextAppearance(); 327 } 328 329 @Override 330 public void setEnabled(boolean enabled) { 331 mDelegate.setEnabled(enabled); 332 } 333 334 @Override 335 public boolean isEnabled() { 336 return mDelegate.isEnabled(); 337 } 338 339 /** 340 * Gets the minimal date supported by this {@link CalendarView} in milliseconds 341 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 342 * zone. 343 * <p> 344 * Note: The default minimal date is 01/01/1900. 345 * <p> 346 * 347 * @return The minimal supported date. 348 * 349 * @attr ref android.R.styleable#CalendarView_minDate 350 */ 351 public long getMinDate() { 352 return mDelegate.getMinDate(); 353 } 354 355 /** 356 * Sets the minimal date supported by this {@link CalendarView} in milliseconds 357 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 358 * zone. 359 * 360 * @param minDate The minimal supported date. 361 * 362 * @attr ref android.R.styleable#CalendarView_minDate 363 */ 364 public void setMinDate(long minDate) { 365 mDelegate.setMinDate(minDate); 366 } 367 368 /** 369 * Gets the maximal date supported by this {@link CalendarView} in milliseconds 370 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 371 * zone. 372 * <p> 373 * Note: The default maximal date is 01/01/2100. 374 * <p> 375 * 376 * @return The maximal supported date. 377 * 378 * @attr ref android.R.styleable#CalendarView_maxDate 379 */ 380 public long getMaxDate() { 381 return mDelegate.getMaxDate(); 382 } 383 384 /** 385 * Sets the maximal date supported by this {@link CalendarView} in milliseconds 386 * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time 387 * zone. 388 * 389 * @param maxDate The maximal supported date. 390 * 391 * @attr ref android.R.styleable#CalendarView_maxDate 392 */ 393 public void setMaxDate(long maxDate) { 394 mDelegate.setMaxDate(maxDate); 395 } 396 397 /** 398 * Sets whether to show the week number. 399 * 400 * @param showWeekNumber True to show the week number. 401 * 402 * @attr ref android.R.styleable#CalendarView_showWeekNumber 403 */ 404 public void setShowWeekNumber(boolean showWeekNumber) { 405 mDelegate.setShowWeekNumber(showWeekNumber); 406 } 407 408 /** 409 * Gets whether to show the week number. 410 * 411 * @return True if showing the week number. 412 * 413 * @attr ref android.R.styleable#CalendarView_showWeekNumber 414 */ 415 public boolean getShowWeekNumber() { 416 return mDelegate.getShowWeekNumber(); 417 } 418 419 /** 420 * Gets the first day of week. 421 * 422 * @return The first day of the week conforming to the {@link CalendarView} 423 * APIs. 424 * @see Calendar#MONDAY 425 * @see Calendar#TUESDAY 426 * @see Calendar#WEDNESDAY 427 * @see Calendar#THURSDAY 428 * @see Calendar#FRIDAY 429 * @see Calendar#SATURDAY 430 * @see Calendar#SUNDAY 431 * 432 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek 433 */ 434 public int getFirstDayOfWeek() { 435 return mDelegate.getFirstDayOfWeek(); 436 } 437 438 /** 439 * Sets the first day of week. 440 * 441 * @param firstDayOfWeek The first day of the week conforming to the 442 * {@link CalendarView} APIs. 443 * @see Calendar#MONDAY 444 * @see Calendar#TUESDAY 445 * @see Calendar#WEDNESDAY 446 * @see Calendar#THURSDAY 447 * @see Calendar#FRIDAY 448 * @see Calendar#SATURDAY 449 * @see Calendar#SUNDAY 450 * 451 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek 452 */ 453 public void setFirstDayOfWeek(int firstDayOfWeek) { 454 mDelegate.setFirstDayOfWeek(firstDayOfWeek); 455 } 456 457 /** 458 * Sets the listener to be notified upon selected date change. 459 * 460 * @param listener The listener to be notified. 461 */ 462 public void setOnDateChangeListener(OnDateChangeListener listener) { 463 mDelegate.setOnDateChangeListener(listener); 464 } 465 466 /** 467 * Gets the selected date in milliseconds since January 1, 1970 00:00:00 in 468 * {@link TimeZone#getDefault()} time zone. 469 * 470 * @return The selected date. 471 */ 472 public long getDate() { 473 return mDelegate.getDate(); 474 } 475 476 /** 477 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in 478 * {@link TimeZone#getDefault()} time zone. 479 * 480 * @param date The selected date. 481 * 482 * @throws IllegalArgumentException of the provided date is before the 483 * minimal or after the maximal date. 484 * 485 * @see #setDate(long, boolean, boolean) 486 * @see #setMinDate(long) 487 * @see #setMaxDate(long) 488 */ 489 public void setDate(long date) { 490 mDelegate.setDate(date); 491 } 492 493 /** 494 * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in 495 * {@link TimeZone#getDefault()} time zone. 496 * 497 * @param date The date. 498 * @param animate Whether to animate the scroll to the current date. 499 * @param center Whether to center the current date even if it is already visible. 500 * 501 * @throws IllegalArgumentException of the provided date is before the 502 * minimal or after the maximal date. 503 * 504 * @see #setMinDate(long) 505 * @see #setMaxDate(long) 506 */ 507 public void setDate(long date, boolean animate, boolean center) { 508 mDelegate.setDate(date, animate, center); 509 } 510 511 @Override 512 protected void onConfigurationChanged(Configuration newConfig) { 513 super.onConfigurationChanged(newConfig); 514 mDelegate.onConfigurationChanged(newConfig); 515 } 516 517 @Override 518 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 519 super.onInitializeAccessibilityEvent(event); 520 mDelegate.onInitializeAccessibilityEvent(event); 521 } 522 523 @Override 524 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 525 super.onInitializeAccessibilityNodeInfo(info); 526 mDelegate.onInitializeAccessibilityNodeInfo(info); 527 } 528 529 /** 530 * A delegate interface that defined the public API of the CalendarView. Allows different 531 * CalendarView implementations. This would need to be implemented by the CalendarView delegates 532 * for the real behavior. 533 */ 534 private interface CalendarViewDelegate { 535 void setShownWeekCount(int count); 536 int getShownWeekCount(); 537 538 void setSelectedWeekBackgroundColor(int color); 539 int getSelectedWeekBackgroundColor(); 540 541 void setFocusedMonthDateColor(int color); 542 int getFocusedMonthDateColor(); 543 544 void setUnfocusedMonthDateColor(int color); 545 int getUnfocusedMonthDateColor(); 546 547 void setWeekNumberColor(int color); 548 int getWeekNumberColor(); 549 550 void setWeekSeparatorLineColor(int color); 551 int getWeekSeparatorLineColor(); 552 553 void setSelectedDateVerticalBar(int resourceId); 554 void setSelectedDateVerticalBar(Drawable drawable); 555 Drawable getSelectedDateVerticalBar(); 556 557 void setWeekDayTextAppearance(int resourceId); 558 int getWeekDayTextAppearance(); 559 560 void setDateTextAppearance(int resourceId); 561 int getDateTextAppearance(); 562 563 void setEnabled(boolean enabled); 564 boolean isEnabled(); 565 566 void setMinDate(long minDate); 567 long getMinDate(); 568 569 void setMaxDate(long maxDate); 570 long getMaxDate(); 571 572 void setShowWeekNumber(boolean showWeekNumber); 573 boolean getShowWeekNumber(); 574 575 void setFirstDayOfWeek(int firstDayOfWeek); 576 int getFirstDayOfWeek(); 577 578 void setDate(long date); 579 void setDate(long date, boolean animate, boolean center); 580 long getDate(); 581 582 void setOnDateChangeListener(OnDateChangeListener listener); 583 584 void onConfigurationChanged(Configuration newConfig); 585 void onInitializeAccessibilityEvent(AccessibilityEvent event); 586 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); 587 } 588 589 /** 590 * An abstract class which can be used as a start for CalendarView implementations 591 */ 592 abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate { 593 // The delegator 594 protected CalendarView mDelegator; 595 596 // The context 597 protected Context mContext; 598 599 // The current locale 600 protected Locale mCurrentLocale; 601 602 AbstractCalendarViewDelegate(CalendarView delegator, Context context) { 603 mDelegator = delegator; 604 mContext = context; 605 606 // Initialization based on locale 607 setCurrentLocale(Locale.getDefault()); 608 } 609 610 protected void setCurrentLocale(Locale locale) { 611 if (locale.equals(mCurrentLocale)) { 612 return; 613 } 614 mCurrentLocale = locale; 615 } 616 } 617 618 /** 619 * A delegate implementing the legacy CalendarView 620 */ 621 private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate { 622 623 /** 624 * Default value whether to show week number. 625 */ 626 private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; 627 628 /** 629 * The number of milliseconds in a day.e 630 */ 631 private static final long MILLIS_IN_DAY = 86400000L; 632 633 /** 634 * The number of day in a week. 635 */ 636 private static final int DAYS_PER_WEEK = 7; 637 638 /** 639 * The number of milliseconds in a week. 640 */ 641 private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; 642 643 /** 644 * Affects when the month selection will change while scrolling upe 645 */ 646 private static final int SCROLL_HYST_WEEKS = 2; 647 648 /** 649 * How long the GoTo fling animation should last. 650 */ 651 private static final int GOTO_SCROLL_DURATION = 1000; 652 653 /** 654 * The duration of the adjustment upon a user scroll in milliseconds. 655 */ 656 private static final int ADJUSTMENT_SCROLL_DURATION = 500; 657 658 /** 659 * How long to wait after receiving an onScrollStateChanged notification 660 * before acting on it. 661 */ 662 private static final int SCROLL_CHANGE_DELAY = 40; 663 664 /** 665 * String for parsing dates. 666 */ 667 private static final String DATE_FORMAT = "MM/dd/yyyy"; 668 669 /** 670 * The default minimal date. 671 */ 672 private static final String DEFAULT_MIN_DATE = "01/01/1900"; 673 674 /** 675 * The default maximal date. 676 */ 677 private static final String DEFAULT_MAX_DATE = "01/01/2100"; 678 679 private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; 680 681 private static final int DEFAULT_DATE_TEXT_SIZE = 14; 682 683 private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; 684 685 private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; 686 687 private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; 688 689 private static final int UNSCALED_BOTTOM_BUFFER = 20; 690 691 private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; 692 693 private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; 694 695 private final int mWeekSeperatorLineWidth; 696 697 private int mDateTextSize; 698 699 private Drawable mSelectedDateVerticalBar; 700 701 private final int mSelectedDateVerticalBarWidth; 702 703 private int mSelectedWeekBackgroundColor; 704 705 private int mFocusedMonthDateColor; 706 707 private int mUnfocusedMonthDateColor; 708 709 private int mWeekSeparatorLineColor; 710 711 private int mWeekNumberColor; 712 713 private int mWeekDayTextAppearanceResId; 714 715 private int mDateTextAppearanceResId; 716 717 /** 718 * The top offset of the weeks list. 719 */ 720 private int mListScrollTopOffset = 2; 721 722 /** 723 * The visible height of a week view. 724 */ 725 private int mWeekMinVisibleHeight = 12; 726 727 /** 728 * The visible height of a week view. 729 */ 730 private int mBottomBuffer = 20; 731 732 /** 733 * The number of shown weeks. 734 */ 735 private int mShownWeekCount; 736 737 /** 738 * Flag whether to show the week number. 739 */ 740 private boolean mShowWeekNumber; 741 742 /** 743 * The number of day per week to be shown. 744 */ 745 private int mDaysPerWeek = 7; 746 747 /** 748 * The friction of the week list while flinging. 749 */ 750 private float mFriction = .05f; 751 752 /** 753 * Scale for adjusting velocity of the week list while flinging. 754 */ 755 private float mVelocityScale = 0.333f; 756 757 /** 758 * The adapter for the weeks list. 759 */ 760 private WeeksAdapter mAdapter; 761 762 /** 763 * The weeks list. 764 */ 765 private ListView mListView; 766 767 /** 768 * The name of the month to display. 769 */ 770 private TextView mMonthName; 771 772 /** 773 * The header with week day names. 774 */ 775 private ViewGroup mDayNamesHeader; 776 777 /** 778 * Cached abbreviations for day of week names. 779 */ 780 private String[] mDayNamesShort; 781 782 /** 783 * Cached full-length day of week names. 784 */ 785 private String[] mDayNamesLong; 786 787 /** 788 * The first day of the week. 789 */ 790 private int mFirstDayOfWeek; 791 792 /** 793 * Which month should be displayed/highlighted [0-11]. 794 */ 795 private int mCurrentMonthDisplayed = -1; 796 797 /** 798 * Used for tracking during a scroll. 799 */ 800 private long mPreviousScrollPosition; 801 802 /** 803 * Used for tracking which direction the view is scrolling. 804 */ 805 private boolean mIsScrollingUp = false; 806 807 /** 808 * The previous scroll state of the weeks ListView. 809 */ 810 private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; 811 812 /** 813 * The current scroll state of the weeks ListView. 814 */ 815 private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; 816 817 /** 818 * Listener for changes in the selected day. 819 */ 820 private OnDateChangeListener mOnDateChangeListener; 821 822 /** 823 * Command for adjusting the position after a scroll/fling. 824 */ 825 private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); 826 827 /** 828 * Temporary instance to avoid multiple instantiations. 829 */ 830 private Calendar mTempDate; 831 832 /** 833 * The first day of the focused month. 834 */ 835 private Calendar mFirstDayOfMonth; 836 837 /** 838 * The start date of the range supported by this picker. 839 */ 840 private Calendar mMinDate; 841 842 /** 843 * The end date of the range supported by this picker. 844 */ 845 private Calendar mMaxDate; 846 847 /** 848 * Date format for parsing dates. 849 */ 850 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); 851 852 LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs, 853 int defStyleAttr, int defStyleRes) { 854 super(delegator, context); 855 856 // initialization based on locale 857 setCurrentLocale(Locale.getDefault()); 858 859 TypedArray attributesArray = context.obtainStyledAttributes(attrs, 860 R.styleable.CalendarView, defStyleAttr, defStyleRes); 861 mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, 862 DEFAULT_SHOW_WEEK_NUMBER); 863 mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, 864 LocaleData.get(Locale.getDefault()).firstDayOfWeek); 865 String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); 866 if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { 867 parseDate(DEFAULT_MIN_DATE, mMinDate); 868 } 869 String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); 870 if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { 871 parseDate(DEFAULT_MAX_DATE, mMaxDate); 872 } 873 if (mMaxDate.before(mMinDate)) { 874 throw new IllegalArgumentException("Max date cannot be before min date."); 875 } 876 mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, 877 DEFAULT_SHOWN_WEEK_COUNT); 878 mSelectedWeekBackgroundColor = attributesArray.getColor( 879 R.styleable.CalendarView_selectedWeekBackgroundColor, 0); 880 mFocusedMonthDateColor = attributesArray.getColor( 881 R.styleable.CalendarView_focusedMonthDateColor, 0); 882 mUnfocusedMonthDateColor = attributesArray.getColor( 883 R.styleable.CalendarView_unfocusedMonthDateColor, 0); 884 mWeekSeparatorLineColor = attributesArray.getColor( 885 R.styleable.CalendarView_weekSeparatorLineColor, 0); 886 mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); 887 mSelectedDateVerticalBar = attributesArray.getDrawable( 888 R.styleable.CalendarView_selectedDateVerticalBar); 889 890 mDateTextAppearanceResId = attributesArray.getResourceId( 891 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); 892 updateDateTextSize(); 893 894 mWeekDayTextAppearanceResId = attributesArray.getResourceId( 895 R.styleable.CalendarView_weekDayTextAppearance, 896 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); 897 attributesArray.recycle(); 898 899 DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics(); 900 mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 901 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); 902 mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 903 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); 904 mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 905 UNSCALED_BOTTOM_BUFFER, displayMetrics); 906 mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 907 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); 908 mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 909 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); 910 911 LayoutInflater layoutInflater = (LayoutInflater) mContext 912 .getSystemService(Service.LAYOUT_INFLATER_SERVICE); 913 View content = layoutInflater.inflate(R.layout.calendar_view, null, false); 914 mDelegator.addView(content); 915 916 mListView = (ListView) mDelegator.findViewById(R.id.list); 917 mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); 918 mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); 919 920 setUpHeader(); 921 setUpListView(); 922 setUpAdapter(); 923 924 // go to today or whichever is close to today min or max date 925 mTempDate.setTimeInMillis(System.currentTimeMillis()); 926 if (mTempDate.before(mMinDate)) { 927 goTo(mMinDate, false, true, true); 928 } else if (mMaxDate.before(mTempDate)) { 929 goTo(mMaxDate, false, true, true); 930 } else { 931 goTo(mTempDate, false, true, true); 932 } 933 934 mDelegator.invalidate(); 935 } 936 937 @Override 938 public void setShownWeekCount(int count) { 939 if (mShownWeekCount != count) { 940 mShownWeekCount = count; 941 mDelegator.invalidate(); 942 } 943 } 944 945 @Override 946 public int getShownWeekCount() { 947 return mShownWeekCount; 948 } 949 950 @Override 951 public void setSelectedWeekBackgroundColor(int color) { 952 if (mSelectedWeekBackgroundColor != color) { 953 mSelectedWeekBackgroundColor = color; 954 final int childCount = mListView.getChildCount(); 955 for (int i = 0; i < childCount; i++) { 956 WeekView weekView = (WeekView) mListView.getChildAt(i); 957 if (weekView.mHasSelectedDay) { 958 weekView.invalidate(); 959 } 960 } 961 } 962 } 963 964 @Override 965 public int getSelectedWeekBackgroundColor() { 966 return mSelectedWeekBackgroundColor; 967 } 968 969 @Override 970 public void setFocusedMonthDateColor(int color) { 971 if (mFocusedMonthDateColor != color) { 972 mFocusedMonthDateColor = color; 973 final int childCount = mListView.getChildCount(); 974 for (int i = 0; i < childCount; i++) { 975 WeekView weekView = (WeekView) mListView.getChildAt(i); 976 if (weekView.mHasFocusedDay) { 977 weekView.invalidate(); 978 } 979 } 980 } 981 } 982 983 @Override 984 public int getFocusedMonthDateColor() { 985 return mFocusedMonthDateColor; 986 } 987 988 @Override 989 public void setUnfocusedMonthDateColor(int color) { 990 if (mUnfocusedMonthDateColor != color) { 991 mUnfocusedMonthDateColor = color; 992 final int childCount = mListView.getChildCount(); 993 for (int i = 0; i < childCount; i++) { 994 WeekView weekView = (WeekView) mListView.getChildAt(i); 995 if (weekView.mHasUnfocusedDay) { 996 weekView.invalidate(); 997 } 998 } 999 } 1000 } 1001 1002 @Override 1003 public int getUnfocusedMonthDateColor() { 1004 return mFocusedMonthDateColor; 1005 } 1006 1007 @Override 1008 public void setWeekNumberColor(int color) { 1009 if (mWeekNumberColor != color) { 1010 mWeekNumberColor = color; 1011 if (mShowWeekNumber) { 1012 invalidateAllWeekViews(); 1013 } 1014 } 1015 } 1016 1017 @Override 1018 public int getWeekNumberColor() { 1019 return mWeekNumberColor; 1020 } 1021 1022 @Override 1023 public void setWeekSeparatorLineColor(int color) { 1024 if (mWeekSeparatorLineColor != color) { 1025 mWeekSeparatorLineColor = color; 1026 invalidateAllWeekViews(); 1027 } 1028 } 1029 1030 @Override 1031 public int getWeekSeparatorLineColor() { 1032 return mWeekSeparatorLineColor; 1033 } 1034 1035 @Override 1036 public void setSelectedDateVerticalBar(int resourceId) { 1037 Drawable drawable = mDelegator.getContext().getDrawable(resourceId); 1038 setSelectedDateVerticalBar(drawable); 1039 } 1040 1041 @Override 1042 public void setSelectedDateVerticalBar(Drawable drawable) { 1043 if (mSelectedDateVerticalBar != drawable) { 1044 mSelectedDateVerticalBar = drawable; 1045 final int childCount = mListView.getChildCount(); 1046 for (int i = 0; i < childCount; i++) { 1047 WeekView weekView = (WeekView) mListView.getChildAt(i); 1048 if (weekView.mHasSelectedDay) { 1049 weekView.invalidate(); 1050 } 1051 } 1052 } 1053 } 1054 1055 @Override 1056 public Drawable getSelectedDateVerticalBar() { 1057 return mSelectedDateVerticalBar; 1058 } 1059 1060 @Override 1061 public void setWeekDayTextAppearance(int resourceId) { 1062 if (mWeekDayTextAppearanceResId != resourceId) { 1063 mWeekDayTextAppearanceResId = resourceId; 1064 setUpHeader(); 1065 } 1066 } 1067 1068 @Override 1069 public int getWeekDayTextAppearance() { 1070 return mWeekDayTextAppearanceResId; 1071 } 1072 1073 @Override 1074 public void setDateTextAppearance(int resourceId) { 1075 if (mDateTextAppearanceResId != resourceId) { 1076 mDateTextAppearanceResId = resourceId; 1077 updateDateTextSize(); 1078 invalidateAllWeekViews(); 1079 } 1080 } 1081 1082 @Override 1083 public int getDateTextAppearance() { 1084 return mDateTextAppearanceResId; 1085 } 1086 1087 @Override 1088 public void setEnabled(boolean enabled) { 1089 mListView.setEnabled(enabled); 1090 } 1091 1092 @Override 1093 public boolean isEnabled() { 1094 return mListView.isEnabled(); 1095 } 1096 1097 @Override 1098 public void setMinDate(long minDate) { 1099 mTempDate.setTimeInMillis(minDate); 1100 if (isSameDate(mTempDate, mMinDate)) { 1101 return; 1102 } 1103 mMinDate.setTimeInMillis(minDate); 1104 // make sure the current date is not earlier than 1105 // the new min date since the latter is used for 1106 // calculating the indices in the adapter thus 1107 // avoiding out of bounds error 1108 Calendar date = mAdapter.mSelectedDate; 1109 if (date.before(mMinDate)) { 1110 mAdapter.setSelectedDay(mMinDate); 1111 } 1112 // reinitialize the adapter since its range depends on min date 1113 mAdapter.init(); 1114 if (date.before(mMinDate)) { 1115 setDate(mTempDate.getTimeInMillis()); 1116 } else { 1117 // we go to the current date to force the ListView to query its 1118 // adapter for the shown views since we have changed the adapter 1119 // range and the base from which the later calculates item indices 1120 // note that calling setDate will not work since the date is the same 1121 goTo(date, false, true, false); 1122 } 1123 } 1124 1125 @Override 1126 public long getMinDate() { 1127 return mMinDate.getTimeInMillis(); 1128 } 1129 1130 @Override 1131 public void setMaxDate(long maxDate) { 1132 mTempDate.setTimeInMillis(maxDate); 1133 if (isSameDate(mTempDate, mMaxDate)) { 1134 return; 1135 } 1136 mMaxDate.setTimeInMillis(maxDate); 1137 // reinitialize the adapter since its range depends on max date 1138 mAdapter.init(); 1139 Calendar date = mAdapter.mSelectedDate; 1140 if (date.after(mMaxDate)) { 1141 setDate(mMaxDate.getTimeInMillis()); 1142 } else { 1143 // we go to the current date to force the ListView to query its 1144 // adapter for the shown views since we have changed the adapter 1145 // range and the base from which the later calculates item indices 1146 // note that calling setDate will not work since the date is the same 1147 goTo(date, false, true, false); 1148 } 1149 } 1150 1151 @Override 1152 public long getMaxDate() { 1153 return mMaxDate.getTimeInMillis(); 1154 } 1155 1156 @Override 1157 public void setShowWeekNumber(boolean showWeekNumber) { 1158 if (mShowWeekNumber == showWeekNumber) { 1159 return; 1160 } 1161 mShowWeekNumber = showWeekNumber; 1162 mAdapter.notifyDataSetChanged(); 1163 setUpHeader(); 1164 } 1165 1166 @Override 1167 public boolean getShowWeekNumber() { 1168 return mShowWeekNumber; 1169 } 1170 1171 @Override 1172 public void setFirstDayOfWeek(int firstDayOfWeek) { 1173 if (mFirstDayOfWeek == firstDayOfWeek) { 1174 return; 1175 } 1176 mFirstDayOfWeek = firstDayOfWeek; 1177 mAdapter.init(); 1178 mAdapter.notifyDataSetChanged(); 1179 setUpHeader(); 1180 } 1181 1182 @Override 1183 public int getFirstDayOfWeek() { 1184 return mFirstDayOfWeek; 1185 } 1186 1187 @Override 1188 public void setDate(long date) { 1189 setDate(date, false, false); 1190 } 1191 1192 @Override 1193 public void setDate(long date, boolean animate, boolean center) { 1194 mTempDate.setTimeInMillis(date); 1195 if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { 1196 return; 1197 } 1198 goTo(mTempDate, animate, true, center); 1199 } 1200 1201 @Override 1202 public long getDate() { 1203 return mAdapter.mSelectedDate.getTimeInMillis(); 1204 } 1205 1206 @Override 1207 public void setOnDateChangeListener(OnDateChangeListener listener) { 1208 mOnDateChangeListener = listener; 1209 } 1210 1211 @Override 1212 public void onConfigurationChanged(Configuration newConfig) { 1213 setCurrentLocale(newConfig.locale); 1214 } 1215 1216 @Override 1217 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1218 event.setClassName(CalendarView.class.getName()); 1219 } 1220 1221 @Override 1222 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1223 info.setClassName(CalendarView.class.getName()); 1224 } 1225 1226 /** 1227 * Sets the current locale. 1228 * 1229 * @param locale The current locale. 1230 */ 1231 @Override 1232 protected void setCurrentLocale(Locale locale) { 1233 super.setCurrentLocale(locale); 1234 1235 mTempDate = getCalendarForLocale(mTempDate, locale); 1236 mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); 1237 mMinDate = getCalendarForLocale(mMinDate, locale); 1238 mMaxDate = getCalendarForLocale(mMaxDate, locale); 1239 } 1240 private void updateDateTextSize() { 1241 TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes( 1242 mDateTextAppearanceResId, R.styleable.TextAppearance); 1243 mDateTextSize = dateTextAppearance.getDimensionPixelSize( 1244 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); 1245 dateTextAppearance.recycle(); 1246 } 1247 1248 /** 1249 * Invalidates all week views. 1250 */ 1251 private void invalidateAllWeekViews() { 1252 final int childCount = mListView.getChildCount(); 1253 for (int i = 0; i < childCount; i++) { 1254 View view = mListView.getChildAt(i); 1255 view.invalidate(); 1256 } 1257 } 1258 1259 /** 1260 * Gets a calendar for locale bootstrapped with the value of a given calendar. 1261 * 1262 * @param oldCalendar The old calendar. 1263 * @param locale The locale. 1264 */ 1265 private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 1266 if (oldCalendar == null) { 1267 return Calendar.getInstance(locale); 1268 } else { 1269 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 1270 Calendar newCalendar = Calendar.getInstance(locale); 1271 newCalendar.setTimeInMillis(currentTimeMillis); 1272 return newCalendar; 1273 } 1274 } 1275 1276 /** 1277 * @return True if the <code>firstDate</code> is the same as the <code> 1278 * secondDate</code>. 1279 */ 1280 private static boolean isSameDate(Calendar firstDate, Calendar secondDate) { 1281 return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) 1282 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); 1283 } 1284 1285 /** 1286 * Creates a new adapter if necessary and sets up its parameters. 1287 */ 1288 private void setUpAdapter() { 1289 if (mAdapter == null) { 1290 mAdapter = new WeeksAdapter(mContext); 1291 mAdapter.registerDataSetObserver(new DataSetObserver() { 1292 @Override 1293 public void onChanged() { 1294 if (mOnDateChangeListener != null) { 1295 Calendar selectedDay = mAdapter.getSelectedDay(); 1296 mOnDateChangeListener.onSelectedDayChange(mDelegator, 1297 selectedDay.get(Calendar.YEAR), 1298 selectedDay.get(Calendar.MONTH), 1299 selectedDay.get(Calendar.DAY_OF_MONTH)); 1300 } 1301 } 1302 }); 1303 mListView.setAdapter(mAdapter); 1304 } 1305 1306 // refresh the view with the new parameters 1307 mAdapter.notifyDataSetChanged(); 1308 } 1309 1310 /** 1311 * Sets up the strings to be used by the header. 1312 */ 1313 private void setUpHeader() { 1314 mDayNamesShort = new String[mDaysPerWeek]; 1315 mDayNamesLong = new String[mDaysPerWeek]; 1316 for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { 1317 int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; 1318 mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, 1319 DateUtils.LENGTH_SHORTEST); 1320 mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, 1321 DateUtils.LENGTH_LONG); 1322 } 1323 1324 TextView label = (TextView) mDayNamesHeader.getChildAt(0); 1325 if (mShowWeekNumber) { 1326 label.setVisibility(View.VISIBLE); 1327 } else { 1328 label.setVisibility(View.GONE); 1329 } 1330 for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { 1331 label = (TextView) mDayNamesHeader.getChildAt(i); 1332 if (mWeekDayTextAppearanceResId > -1) { 1333 label.setTextAppearance(mContext, mWeekDayTextAppearanceResId); 1334 } 1335 if (i < mDaysPerWeek + 1) { 1336 label.setText(mDayNamesShort[i - 1]); 1337 label.setContentDescription(mDayNamesLong[i - 1]); 1338 label.setVisibility(View.VISIBLE); 1339 } else { 1340 label.setVisibility(View.GONE); 1341 } 1342 } 1343 mDayNamesHeader.invalidate(); 1344 } 1345 1346 /** 1347 * Sets all the required fields for the list view. 1348 */ 1349 private void setUpListView() { 1350 // Configure the listview 1351 mListView.setDivider(null); 1352 mListView.setItemsCanFocus(true); 1353 mListView.setVerticalScrollBarEnabled(false); 1354 mListView.setOnScrollListener(new OnScrollListener() { 1355 public void onScrollStateChanged(AbsListView view, int scrollState) { 1356 LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState); 1357 } 1358 1359 public void onScroll( 1360 AbsListView view, int firstVisibleItem, int visibleItemCount, 1361 int totalItemCount) { 1362 LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem, 1363 visibleItemCount, totalItemCount); 1364 } 1365 }); 1366 // Make the scrolling behavior nicer 1367 mListView.setFriction(mFriction); 1368 mListView.setVelocityScale(mVelocityScale); 1369 } 1370 1371 /** 1372 * This moves to the specified time in the view. If the time is not already 1373 * in range it will move the list so that the first of the month containing 1374 * the time is at the top of the view. If the new time is already in view 1375 * the list will not be scrolled unless forceScroll is true. This time may 1376 * optionally be highlighted as selected as well. 1377 * 1378 * @param date The time to move to. 1379 * @param animate Whether to scroll to the given time or just redraw at the 1380 * new location. 1381 * @param setSelected Whether to set the given time as selected. 1382 * @param forceScroll Whether to recenter even if the time is already 1383 * visible. 1384 * 1385 * @throws IllegalArgumentException of the provided date is before the 1386 * range start of after the range end. 1387 */ 1388 private void goTo(Calendar date, boolean animate, boolean setSelected, 1389 boolean forceScroll) { 1390 if (date.before(mMinDate) || date.after(mMaxDate)) { 1391 throw new IllegalArgumentException("Time not between " + mMinDate.getTime() 1392 + " and " + mMaxDate.getTime()); 1393 } 1394 // Find the first and last entirely visible weeks 1395 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); 1396 View firstChild = mListView.getChildAt(0); 1397 if (firstChild != null && firstChild.getTop() < 0) { 1398 firstFullyVisiblePosition++; 1399 } 1400 int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; 1401 if (firstChild != null && firstChild.getTop() > mBottomBuffer) { 1402 lastFullyVisiblePosition--; 1403 } 1404 if (setSelected) { 1405 mAdapter.setSelectedDay(date); 1406 } 1407 // Get the week we're going to 1408 int position = getWeeksSinceMinDate(date); 1409 1410 // Check if the selected day is now outside of our visible range 1411 // and if so scroll to the month that contains it 1412 if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition 1413 || forceScroll) { 1414 mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); 1415 mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); 1416 1417 setMonthDisplayed(mFirstDayOfMonth); 1418 1419 // the earliest time we can scroll to is the min date 1420 if (mFirstDayOfMonth.before(mMinDate)) { 1421 position = 0; 1422 } else { 1423 position = getWeeksSinceMinDate(mFirstDayOfMonth); 1424 } 1425 1426 mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; 1427 if (animate) { 1428 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, 1429 GOTO_SCROLL_DURATION); 1430 } else { 1431 mListView.setSelectionFromTop(position, mListScrollTopOffset); 1432 // Perform any after scroll operations that are needed 1433 onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); 1434 } 1435 } else if (setSelected) { 1436 // Otherwise just set the selection 1437 setMonthDisplayed(date); 1438 } 1439 } 1440 1441 /** 1442 * Parses the given <code>date</code> and in case of success sets 1443 * the result to the <code>outDate</code>. 1444 * 1445 * @return True if the date was parsed. 1446 */ 1447 private boolean parseDate(String date, Calendar outDate) { 1448 try { 1449 outDate.setTime(mDateFormat.parse(date)); 1450 return true; 1451 } catch (ParseException e) { 1452 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); 1453 return false; 1454 } 1455 } 1456 1457 /** 1458 * Called when a <code>view</code> transitions to a new <code>scrollState 1459 * </code>. 1460 */ 1461 private void onScrollStateChanged(AbsListView view, int scrollState) { 1462 mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); 1463 } 1464 1465 /** 1466 * Updates the title and selected month if the <code>view</code> has moved to a new 1467 * month. 1468 */ 1469 private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 1470 int totalItemCount) { 1471 WeekView child = (WeekView) view.getChildAt(0); 1472 if (child == null) { 1473 return; 1474 } 1475 1476 // Figure out where we are 1477 long currScroll = 1478 view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); 1479 1480 // If we have moved since our last call update the direction 1481 if (currScroll < mPreviousScrollPosition) { 1482 mIsScrollingUp = true; 1483 } else if (currScroll > mPreviousScrollPosition) { 1484 mIsScrollingUp = false; 1485 } else { 1486 return; 1487 } 1488 1489 // Use some hysteresis for checking which month to highlight. This 1490 // causes the month to transition when two full weeks of a month are 1491 // visible when scrolling up, and when the first day in a month reaches 1492 // the top of the screen when scrolling down. 1493 int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; 1494 if (mIsScrollingUp) { 1495 child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); 1496 } else if (offset != 0) { 1497 child = (WeekView) view.getChildAt(offset); 1498 } 1499 1500 if (child != null) { 1501 // Find out which month we're moving into 1502 int month; 1503 if (mIsScrollingUp) { 1504 month = child.getMonthOfFirstWeekDay(); 1505 } else { 1506 month = child.getMonthOfLastWeekDay(); 1507 } 1508 1509 // And how it relates to our current highlighted month 1510 int monthDiff; 1511 if (mCurrentMonthDisplayed == 11 && month == 0) { 1512 monthDiff = 1; 1513 } else if (mCurrentMonthDisplayed == 0 && month == 11) { 1514 monthDiff = -1; 1515 } else { 1516 monthDiff = month - mCurrentMonthDisplayed; 1517 } 1518 1519 // Only switch months if we're scrolling away from the currently 1520 // selected month 1521 if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { 1522 Calendar firstDay = child.getFirstDay(); 1523 if (mIsScrollingUp) { 1524 firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); 1525 } else { 1526 firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); 1527 } 1528 setMonthDisplayed(firstDay); 1529 } 1530 } 1531 mPreviousScrollPosition = currScroll; 1532 mPreviousScrollState = mCurrentScrollState; 1533 } 1534 1535 /** 1536 * Sets the month displayed at the top of this view based on time. Override 1537 * to add custom events when the title is changed. 1538 * 1539 * @param calendar A day in the new focus month. 1540 */ 1541 private void setMonthDisplayed(Calendar calendar) { 1542 mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); 1543 mAdapter.setFocusMonth(mCurrentMonthDisplayed); 1544 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY 1545 | DateUtils.FORMAT_SHOW_YEAR; 1546 final long millis = calendar.getTimeInMillis(); 1547 String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); 1548 mMonthName.setText(newMonthName); 1549 mMonthName.invalidate(); 1550 } 1551 1552 /** 1553 * @return Returns the number of weeks between the current <code>date</code> 1554 * and the <code>mMinDate</code>. 1555 */ 1556 private int getWeeksSinceMinDate(Calendar date) { 1557 if (date.before(mMinDate)) { 1558 throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() 1559 + " does not precede toDate: " + date.getTime()); 1560 } 1561 long endTimeMillis = date.getTimeInMillis() 1562 + date.getTimeZone().getOffset(date.getTimeInMillis()); 1563 long startTimeMillis = mMinDate.getTimeInMillis() 1564 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); 1565 long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) 1566 * MILLIS_IN_DAY; 1567 return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); 1568 } 1569 1570 /** 1571 * Command responsible for acting upon scroll state changes. 1572 */ 1573 private class ScrollStateRunnable implements Runnable { 1574 private AbsListView mView; 1575 1576 private int mNewState; 1577 1578 /** 1579 * Sets up the runnable with a short delay in case the scroll state 1580 * immediately changes again. 1581 * 1582 * @param view The list view that changed state 1583 * @param scrollState The new state it changed to 1584 */ 1585 public void doScrollStateChange(AbsListView view, int scrollState) { 1586 mView = view; 1587 mNewState = scrollState; 1588 mDelegator.removeCallbacks(this); 1589 mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY); 1590 } 1591 1592 public void run() { 1593 mCurrentScrollState = mNewState; 1594 // Fix the position after a scroll or a fling ends 1595 if (mNewState == OnScrollListener.SCROLL_STATE_IDLE 1596 && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { 1597 View child = mView.getChildAt(0); 1598 if (child == null) { 1599 // The view is no longer visible, just return 1600 return; 1601 } 1602 int dist = child.getBottom() - mListScrollTopOffset; 1603 if (dist > mListScrollTopOffset) { 1604 if (mIsScrollingUp) { 1605 mView.smoothScrollBy(dist - child.getHeight(), 1606 ADJUSTMENT_SCROLL_DURATION); 1607 } else { 1608 mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); 1609 } 1610 } 1611 } 1612 mPreviousScrollState = mNewState; 1613 } 1614 } 1615 1616 /** 1617 * <p> 1618 * This is a specialized adapter for creating a list of weeks with 1619 * selectable days. It can be configured to display the week number, start 1620 * the week on a given day, show a reduced number of days, or display an 1621 * arbitrary number of weeks at a time. 1622 * </p> 1623 */ 1624 private class WeeksAdapter extends BaseAdapter implements OnTouchListener { 1625 1626 private int mSelectedWeek; 1627 1628 private GestureDetector mGestureDetector; 1629 1630 private int mFocusedMonth; 1631 1632 private final Calendar mSelectedDate = Calendar.getInstance(); 1633 1634 private int mTotalWeekCount; 1635 1636 public WeeksAdapter(Context context) { 1637 mContext = context; 1638 mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); 1639 init(); 1640 } 1641 1642 /** 1643 * Set up the gesture detector and selected time 1644 */ 1645 private void init() { 1646 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); 1647 mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); 1648 if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek 1649 || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { 1650 mTotalWeekCount++; 1651 } 1652 notifyDataSetChanged(); 1653 } 1654 1655 /** 1656 * Updates the selected day and related parameters. 1657 * 1658 * @param selectedDay The time to highlight 1659 */ 1660 public void setSelectedDay(Calendar selectedDay) { 1661 if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) 1662 && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { 1663 return; 1664 } 1665 mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); 1666 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); 1667 mFocusedMonth = mSelectedDate.get(Calendar.MONTH); 1668 notifyDataSetChanged(); 1669 } 1670 1671 /** 1672 * @return The selected day of month. 1673 */ 1674 public Calendar getSelectedDay() { 1675 return mSelectedDate; 1676 } 1677 1678 @Override 1679 public int getCount() { 1680 return mTotalWeekCount; 1681 } 1682 1683 @Override 1684 public Object getItem(int position) { 1685 return null; 1686 } 1687 1688 @Override 1689 public long getItemId(int position) { 1690 return position; 1691 } 1692 1693 @Override 1694 public View getView(int position, View convertView, ViewGroup parent) { 1695 WeekView weekView = null; 1696 if (convertView != null) { 1697 weekView = (WeekView) convertView; 1698 } else { 1699 weekView = new WeekView(mContext); 1700 android.widget.AbsListView.LayoutParams params = 1701 new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, 1702 LayoutParams.WRAP_CONTENT); 1703 weekView.setLayoutParams(params); 1704 weekView.setClickable(true); 1705 weekView.setOnTouchListener(this); 1706 } 1707 1708 int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( 1709 Calendar.DAY_OF_WEEK) : -1; 1710 weekView.init(position, selectedWeekDay, mFocusedMonth); 1711 1712 return weekView; 1713 } 1714 1715 /** 1716 * Changes which month is in focus and updates the view. 1717 * 1718 * @param month The month to show as in focus [0-11] 1719 */ 1720 public void setFocusMonth(int month) { 1721 if (mFocusedMonth == month) { 1722 return; 1723 } 1724 mFocusedMonth = month; 1725 notifyDataSetChanged(); 1726 } 1727 1728 @Override 1729 public boolean onTouch(View v, MotionEvent event) { 1730 if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { 1731 WeekView weekView = (WeekView) v; 1732 // if we cannot find a day for the given location we are done 1733 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { 1734 return true; 1735 } 1736 // it is possible that the touched day is outside the valid range 1737 // we draw whole weeks but range end can fall not on the week end 1738 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { 1739 return true; 1740 } 1741 onDateTapped(mTempDate); 1742 return true; 1743 } 1744 return false; 1745 } 1746 1747 /** 1748 * Maintains the same hour/min/sec but moves the day to the tapped day. 1749 * 1750 * @param day The day that was tapped 1751 */ 1752 private void onDateTapped(Calendar day) { 1753 setSelectedDay(day); 1754 setMonthDisplayed(day); 1755 } 1756 1757 /** 1758 * This is here so we can identify single tap events and set the 1759 * selected day correctly 1760 */ 1761 class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { 1762 @Override 1763 public boolean onSingleTapUp(MotionEvent e) { 1764 return true; 1765 } 1766 } 1767 } 1768 1769 /** 1770 * <p> 1771 * This is a dynamic view for drawing a single week. It can be configured to 1772 * display the week number, start the week on a given day, or show a reduced 1773 * number of days. It is intended for use as a single view within a 1774 * ListView. See {@link WeeksAdapter} for usage. 1775 * </p> 1776 */ 1777 private class WeekView extends View { 1778 1779 private final Rect mTempRect = new Rect(); 1780 1781 private final Paint mDrawPaint = new Paint(); 1782 1783 private final Paint mMonthNumDrawPaint = new Paint(); 1784 1785 // Cache the number strings so we don't have to recompute them each time 1786 private String[] mDayNumbers; 1787 1788 // Quick lookup for checking which days are in the focus month 1789 private boolean[] mFocusDay; 1790 1791 // Whether this view has a focused day. 1792 private boolean mHasFocusedDay; 1793 1794 // Whether this view has only focused days. 1795 private boolean mHasUnfocusedDay; 1796 1797 // The first day displayed by this item 1798 private Calendar mFirstDay; 1799 1800 // The month of the first day in this week 1801 private int mMonthOfFirstWeekDay = -1; 1802 1803 // The month of the last day in this week 1804 private int mLastWeekDayMonth = -1; 1805 1806 // The position of this week, equivalent to weeks since the week of Jan 1807 // 1st, 1900 1808 private int mWeek = -1; 1809 1810 // Quick reference to the width of this view, matches parent 1811 private int mWidth; 1812 1813 // The height this view should draw at in pixels, set by height param 1814 private int mHeight; 1815 1816 // If this view contains the selected day 1817 private boolean mHasSelectedDay = false; 1818 1819 // Which day is selected [0-6] or -1 if no day is selected 1820 private int mSelectedDay = -1; 1821 1822 // The number of days + a spot for week number if it is displayed 1823 private int mNumCells; 1824 1825 // The left edge of the selected day 1826 private int mSelectedLeft = -1; 1827 1828 // The right edge of the selected day 1829 private int mSelectedRight = -1; 1830 1831 public WeekView(Context context) { 1832 super(context); 1833 1834 // Sets up any standard paints that will be used 1835 initilaizePaints(); 1836 } 1837 1838 /** 1839 * Initializes this week view. 1840 * 1841 * @param weekNumber The number of the week this view represents. The 1842 * week number is a zero based index of the weeks since 1843 * {@link CalendarView#getMinDate()}. 1844 * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no 1845 * selected day. 1846 * @param focusedMonth The month that is currently in focus i.e. 1847 * highlighted. 1848 */ 1849 public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { 1850 mSelectedDay = selectedWeekDay; 1851 mHasSelectedDay = mSelectedDay != -1; 1852 mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; 1853 mWeek = weekNumber; 1854 mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); 1855 1856 mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); 1857 mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); 1858 1859 // Allocate space for caching the day numbers and focus values 1860 mDayNumbers = new String[mNumCells]; 1861 mFocusDay = new boolean[mNumCells]; 1862 1863 // If we're showing the week number calculate it based on Monday 1864 int i = 0; 1865 if (mShowWeekNumber) { 1866 mDayNumbers[0] = String.format(Locale.getDefault(), "%d", 1867 mTempDate.get(Calendar.WEEK_OF_YEAR)); 1868 i++; 1869 } 1870 1871 // Now adjust our starting day based on the start day of the week 1872 int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); 1873 mTempDate.add(Calendar.DAY_OF_MONTH, diff); 1874 1875 mFirstDay = (Calendar) mTempDate.clone(); 1876 mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); 1877 1878 mHasUnfocusedDay = true; 1879 for (; i < mNumCells; i++) { 1880 final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); 1881 mFocusDay[i] = isFocusedDay; 1882 mHasFocusedDay |= isFocusedDay; 1883 mHasUnfocusedDay &= !isFocusedDay; 1884 // do not draw dates outside the valid range to avoid user confusion 1885 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { 1886 mDayNumbers[i] = ""; 1887 } else { 1888 mDayNumbers[i] = String.format(Locale.getDefault(), "%d", 1889 mTempDate.get(Calendar.DAY_OF_MONTH)); 1890 } 1891 mTempDate.add(Calendar.DAY_OF_MONTH, 1); 1892 } 1893 // We do one extra add at the end of the loop, if that pushed us to 1894 // new month undo it 1895 if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { 1896 mTempDate.add(Calendar.DAY_OF_MONTH, -1); 1897 } 1898 mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); 1899 1900 updateSelectionPositions(); 1901 } 1902 1903 /** 1904 * Initialize the paint instances. 1905 */ 1906 private void initilaizePaints() { 1907 mDrawPaint.setFakeBoldText(false); 1908 mDrawPaint.setAntiAlias(true); 1909 mDrawPaint.setStyle(Style.FILL); 1910 1911 mMonthNumDrawPaint.setFakeBoldText(true); 1912 mMonthNumDrawPaint.setAntiAlias(true); 1913 mMonthNumDrawPaint.setStyle(Style.FILL); 1914 mMonthNumDrawPaint.setTextAlign(Align.CENTER); 1915 mMonthNumDrawPaint.setTextSize(mDateTextSize); 1916 } 1917 1918 /** 1919 * Returns the month of the first day in this week. 1920 * 1921 * @return The month the first day of this view is in. 1922 */ 1923 public int getMonthOfFirstWeekDay() { 1924 return mMonthOfFirstWeekDay; 1925 } 1926 1927 /** 1928 * Returns the month of the last day in this week 1929 * 1930 * @return The month the last day of this view is in 1931 */ 1932 public int getMonthOfLastWeekDay() { 1933 return mLastWeekDayMonth; 1934 } 1935 1936 /** 1937 * Returns the first day in this view. 1938 * 1939 * @return The first day in the view. 1940 */ 1941 public Calendar getFirstDay() { 1942 return mFirstDay; 1943 } 1944 1945 /** 1946 * Calculates the day that the given x position is in, accounting for 1947 * week number. 1948 * 1949 * @param x The x position of the touch event. 1950 * @return True if a day was found for the given location. 1951 */ 1952 public boolean getDayFromLocation(float x, Calendar outCalendar) { 1953 final boolean isLayoutRtl = isLayoutRtl(); 1954 1955 int start; 1956 int end; 1957 1958 if (isLayoutRtl) { 1959 start = 0; 1960 end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; 1961 } else { 1962 start = mShowWeekNumber ? mWidth / mNumCells : 0; 1963 end = mWidth; 1964 } 1965 1966 if (x < start || x > end) { 1967 outCalendar.clear(); 1968 return false; 1969 } 1970 1971 // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels 1972 int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); 1973 1974 if (isLayoutRtl) { 1975 dayPosition = mDaysPerWeek - 1 - dayPosition; 1976 } 1977 1978 outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); 1979 outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); 1980 1981 return true; 1982 } 1983 1984 @Override 1985 protected void onDraw(Canvas canvas) { 1986 drawBackground(canvas); 1987 drawWeekNumbersAndDates(canvas); 1988 drawWeekSeparators(canvas); 1989 drawSelectedDateVerticalBars(canvas); 1990 } 1991 1992 /** 1993 * This draws the selection highlight if a day is selected in this week. 1994 * 1995 * @param canvas The canvas to draw on 1996 */ 1997 private void drawBackground(Canvas canvas) { 1998 if (!mHasSelectedDay) { 1999 return; 2000 } 2001 mDrawPaint.setColor(mSelectedWeekBackgroundColor); 2002 2003 mTempRect.top = mWeekSeperatorLineWidth; 2004 mTempRect.bottom = mHeight; 2005 2006 final boolean isLayoutRtl = isLayoutRtl(); 2007 2008 if (isLayoutRtl) { 2009 mTempRect.left = 0; 2010 mTempRect.right = mSelectedLeft - 2; 2011 } else { 2012 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; 2013 mTempRect.right = mSelectedLeft - 2; 2014 } 2015 canvas.drawRect(mTempRect, mDrawPaint); 2016 2017 if (isLayoutRtl) { 2018 mTempRect.left = mSelectedRight + 3; 2019 mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; 2020 } else { 2021 mTempRect.left = mSelectedRight + 3; 2022 mTempRect.right = mWidth; 2023 } 2024 canvas.drawRect(mTempRect, mDrawPaint); 2025 } 2026 2027 /** 2028 * Draws the week and month day numbers for this week. 2029 * 2030 * @param canvas The canvas to draw on 2031 */ 2032 private void drawWeekNumbersAndDates(Canvas canvas) { 2033 final float textHeight = mDrawPaint.getTextSize(); 2034 final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; 2035 final int nDays = mNumCells; 2036 final int divisor = 2 * nDays; 2037 2038 mDrawPaint.setTextAlign(Align.CENTER); 2039 mDrawPaint.setTextSize(mDateTextSize); 2040 2041 int i = 0; 2042 2043 if (isLayoutRtl()) { 2044 for (; i < nDays - 1; i++) { 2045 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor 2046 : mUnfocusedMonthDateColor); 2047 int x = (2 * i + 1) * mWidth / divisor; 2048 canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); 2049 } 2050 if (mShowWeekNumber) { 2051 mDrawPaint.setColor(mWeekNumberColor); 2052 int x = mWidth - mWidth / divisor; 2053 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); 2054 } 2055 } else { 2056 if (mShowWeekNumber) { 2057 mDrawPaint.setColor(mWeekNumberColor); 2058 int x = mWidth / divisor; 2059 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); 2060 i++; 2061 } 2062 for (; i < nDays; i++) { 2063 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor 2064 : mUnfocusedMonthDateColor); 2065 int x = (2 * i + 1) * mWidth / divisor; 2066 canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); 2067 } 2068 } 2069 } 2070 2071 /** 2072 * Draws a horizontal line for separating the weeks. 2073 * 2074 * @param canvas The canvas to draw on. 2075 */ 2076 private void drawWeekSeparators(Canvas canvas) { 2077 // If it is the topmost fully visible child do not draw separator line 2078 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); 2079 if (mListView.getChildAt(0).getTop() < 0) { 2080 firstFullyVisiblePosition++; 2081 } 2082 if (firstFullyVisiblePosition == mWeek) { 2083 return; 2084 } 2085 mDrawPaint.setColor(mWeekSeparatorLineColor); 2086 mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); 2087 float startX; 2088 float stopX; 2089 if (isLayoutRtl()) { 2090 startX = 0; 2091 stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; 2092 } else { 2093 startX = mShowWeekNumber ? mWidth / mNumCells : 0; 2094 stopX = mWidth; 2095 } 2096 canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); 2097 } 2098 2099 /** 2100 * Draws the selected date bars if this week has a selected day. 2101 * 2102 * @param canvas The canvas to draw on 2103 */ 2104 private void drawSelectedDateVerticalBars(Canvas canvas) { 2105 if (!mHasSelectedDay) { 2106 return; 2107 } 2108 mSelectedDateVerticalBar.setBounds( 2109 mSelectedLeft - mSelectedDateVerticalBarWidth / 2, 2110 mWeekSeperatorLineWidth, 2111 mSelectedLeft + mSelectedDateVerticalBarWidth / 2, 2112 mHeight); 2113 mSelectedDateVerticalBar.draw(canvas); 2114 mSelectedDateVerticalBar.setBounds( 2115 mSelectedRight - mSelectedDateVerticalBarWidth / 2, 2116 mWeekSeperatorLineWidth, 2117 mSelectedRight + mSelectedDateVerticalBarWidth / 2, 2118 mHeight); 2119 mSelectedDateVerticalBar.draw(canvas); 2120 } 2121 2122 @Override 2123 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 2124 mWidth = w; 2125 updateSelectionPositions(); 2126 } 2127 2128 /** 2129 * This calculates the positions for the selected day lines. 2130 */ 2131 private void updateSelectionPositions() { 2132 if (mHasSelectedDay) { 2133 final boolean isLayoutRtl = isLayoutRtl(); 2134 int selectedPosition = mSelectedDay - mFirstDayOfWeek; 2135 if (selectedPosition < 0) { 2136 selectedPosition += 7; 2137 } 2138 if (mShowWeekNumber && !isLayoutRtl) { 2139 selectedPosition++; 2140 } 2141 if (isLayoutRtl) { 2142 mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; 2143 2144 } else { 2145 mSelectedLeft = selectedPosition * mWidth / mNumCells; 2146 } 2147 mSelectedRight = mSelectedLeft + mWidth / mNumCells; 2148 } 2149 } 2150 2151 @Override 2152 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 2153 mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView 2154 .getPaddingBottom()) / mShownWeekCount; 2155 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); 2156 } 2157 } 2158 2159 } 2160 2161} 2162