DatePickerCalendarDelegate.java revision e6ec07fc5d7ce7732f63b6da73d3cc9e207df69f
1/* 2 * Copyright (C) 2014 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.content.Context; 20import android.content.res.ColorStateList; 21import android.content.res.Configuration; 22import android.content.res.Resources; 23import android.content.res.TypedArray; 24import android.os.Parcel; 25import android.os.Parcelable; 26import android.text.format.DateFormat; 27import android.text.format.DateUtils; 28import android.util.AttributeSet; 29import android.view.HapticFeedbackConstants; 30import android.view.LayoutInflater; 31import android.view.View; 32import android.view.accessibility.AccessibilityEvent; 33import android.view.accessibility.AccessibilityNodeInfo; 34import android.view.animation.AlphaAnimation; 35import android.view.animation.Animation; 36 37import com.android.internal.R; 38import com.android.internal.widget.AccessibleDateAnimator; 39 40import java.text.SimpleDateFormat; 41import java.util.Calendar; 42import java.util.HashSet; 43import java.util.Locale; 44 45/** 46 * A delegate for picking up a date (day / month / year). 47 */ 48class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements 49 View.OnClickListener, DatePickerController { 50 private static final int USE_LOCALE = 0; 51 52 private static final int UNINITIALIZED = -1; 53 private static final int MONTH_AND_DAY_VIEW = 0; 54 private static final int YEAR_VIEW = 1; 55 56 private static final int DEFAULT_START_YEAR = 1900; 57 private static final int DEFAULT_END_YEAR = 2100; 58 59 private static final int ANIMATION_DURATION = 300; 60 61 private static final int MONTH_INDEX = 0; 62 private static final int DAY_INDEX = 1; 63 private static final int YEAR_INDEX = 2; 64 65 private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault()); 66 private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault()); 67 68 private TextView mDayOfWeekView; 69 70 /** Layout that contains the current month, day, and year. */ 71 private LinearLayout mMonthDayYearLayout; 72 73 /** Clickable layout that contains the current day and year. */ 74 private LinearLayout mMonthAndDayLayout; 75 76 private TextView mHeaderMonthTextView; 77 private TextView mHeaderDayOfMonthTextView; 78 private TextView mHeaderYearTextView; 79 private DayPickerView mDayPickerView; 80 private YearPickerView mYearPickerView; 81 82 private boolean mIsEnabled = true; 83 84 // Accessibility strings. 85 private String mDayPickerDescription; 86 private String mSelectDay; 87 private String mYearPickerDescription; 88 private String mSelectYear; 89 90 private AccessibleDateAnimator mAnimator; 91 92 private DatePicker.OnDateChangedListener mDateChangedListener; 93 94 private int mCurrentView = UNINITIALIZED; 95 96 private Calendar mCurrentDate; 97 private Calendar mTempDate; 98 private Calendar mMinDate; 99 private Calendar mMaxDate; 100 101 private int mFirstDayOfWeek = USE_LOCALE; 102 103 private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>(); 104 105 public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs, 106 int defStyleAttr, int defStyleRes) { 107 super(delegator, context); 108 109 final Locale locale = Locale.getDefault(); 110 mMinDate = getCalendarForLocale(mMinDate, locale); 111 mMaxDate = getCalendarForLocale(mMaxDate, locale); 112 mTempDate = getCalendarForLocale(mMaxDate, locale); 113 mCurrentDate = getCalendarForLocale(mCurrentDate, locale); 114 115 mMinDate.set(DEFAULT_START_YEAR, 1, 1); 116 mMaxDate.set(DEFAULT_END_YEAR, 12, 31); 117 118 final Resources res = mDelegator.getResources(); 119 final TypedArray a = mContext.obtainStyledAttributes(attrs, 120 R.styleable.DatePicker, defStyleAttr, defStyleRes); 121 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 122 Context.LAYOUT_INFLATER_SERVICE); 123 final int layoutResourceId = a.getResourceId( 124 R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo); 125 final View mainView = inflater.inflate(layoutResourceId, null); 126 mDelegator.addView(mainView); 127 128 mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header); 129 130 // Layout that contains the current date and day name header. 131 final LinearLayout dateLayout = (LinearLayout) mainView.findViewById( 132 R.id.day_picker_selector_layout); 133 mMonthDayYearLayout = (LinearLayout) mainView.findViewById( 134 R.id.date_picker_month_day_year_layout); 135 mMonthAndDayLayout = (LinearLayout) mainView.findViewById( 136 R.id.date_picker_month_and_day_layout); 137 mMonthAndDayLayout.setOnClickListener(this); 138 mHeaderMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month); 139 mHeaderDayOfMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_day); 140 mHeaderYearTextView = (TextView) mainView.findViewById(R.id.date_picker_year); 141 mHeaderYearTextView.setOnClickListener(this); 142 143 // Obtain default highlight color from the theme. 144 final int defaultHighlightColor = mHeaderYearTextView.getHighlightColor(); 145 146 // Use Theme attributes if possible 147 final int dayOfWeekTextAppearanceResId = a.getResourceId( 148 R.styleable.DatePicker_dayOfWeekTextAppearance, -1); 149 if (dayOfWeekTextAppearanceResId != -1) { 150 mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId); 151 } 152 153 mDayOfWeekView.setBackground(a.getDrawable(R.styleable.DatePicker_dayOfWeekBackground)); 154 155 dateLayout.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground)); 156 157 final int headerSelectedTextColor = a.getColor( 158 R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor); 159 final int monthTextAppearanceResId = a.getResourceId( 160 R.styleable.DatePicker_headerMonthTextAppearance, -1); 161 if (monthTextAppearanceResId != -1) { 162 mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId); 163 } 164 mHeaderMonthTextView.setTextColor(ColorStateList.addFirstIfMissing( 165 mHeaderMonthTextView.getTextColors(), R.attr.state_selected, 166 headerSelectedTextColor)); 167 168 final int dayOfMonthTextAppearanceResId = a.getResourceId( 169 R.styleable.DatePicker_headerDayOfMonthTextAppearance, -1); 170 if (dayOfMonthTextAppearanceResId != -1) { 171 mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId); 172 } 173 mHeaderDayOfMonthTextView.setTextColor(ColorStateList.addFirstIfMissing( 174 mHeaderDayOfMonthTextView.getTextColors(), R.attr.state_selected, 175 headerSelectedTextColor)); 176 177 final int yearTextAppearanceResId = a.getResourceId( 178 R.styleable.DatePicker_headerYearTextAppearance, -1); 179 if (yearTextAppearanceResId != -1) { 180 mHeaderYearTextView.setTextAppearance(context, yearTextAppearanceResId); 181 } 182 mHeaderYearTextView.setTextColor(ColorStateList.addFirstIfMissing( 183 mHeaderYearTextView.getTextColors(), R.attr.state_selected, 184 headerSelectedTextColor)); 185 186 mDayPickerView = new DayPickerView(mContext); 187 mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek); 188 mDayPickerView.setMinDate(mMinDate.getTimeInMillis()); 189 mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis()); 190 mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); 191 mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener); 192 193 mYearPickerView = new YearPickerView(mContext); 194 mYearPickerView.init(this); 195 196 final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor, 197 defaultHighlightColor); 198 mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor); 199 200 final ColorStateList calendarTextColor = a.getColorStateList( 201 R.styleable.DatePicker_calendarTextColor); 202 final int calendarSelectedTextColor = a.getColor( 203 R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor); 204 mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing( 205 calendarTextColor, R.attr.state_selected, calendarSelectedTextColor)); 206 207 mDayPickerDescription = res.getString(R.string.day_picker_description); 208 mSelectDay = res.getString(R.string.select_day); 209 mYearPickerDescription = res.getString(R.string.year_picker_description); 210 mSelectYear = res.getString(R.string.select_year); 211 212 mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator); 213 mAnimator.addView(mDayPickerView); 214 mAnimator.addView(mYearPickerView); 215 mAnimator.setDateMillis(mCurrentDate.getTimeInMillis()); 216 217 final Animation animation = new AlphaAnimation(0.0f, 1.0f); 218 animation.setDuration(ANIMATION_DURATION); 219 mAnimator.setInAnimation(animation); 220 221 final Animation animation2 = new AlphaAnimation(1.0f, 0.0f); 222 animation2.setDuration(ANIMATION_DURATION); 223 mAnimator.setOutAnimation(animation2); 224 225 updateDisplay(false); 226 setCurrentView(MONTH_AND_DAY_VIEW); 227 } 228 229 /** 230 * Gets a calendar for locale bootstrapped with the value of a given calendar. 231 * 232 * @param oldCalendar The old calendar. 233 * @param locale The locale. 234 */ 235 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 236 if (oldCalendar == null) { 237 return Calendar.getInstance(locale); 238 } else { 239 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 240 Calendar newCalendar = Calendar.getInstance(locale); 241 newCalendar.setTimeInMillis(currentTimeMillis); 242 return newCalendar; 243 } 244 } 245 246 /** 247 * Compute the array representing the order of Month / Day / Year views in their layout. 248 * Will be used for I18N purpose as the order of them depends on the Locale. 249 */ 250 private int[] getMonthDayYearIndexes(String pattern) { 251 int[] result = new int[3]; 252 253 final String filteredPattern = pattern.replaceAll("'.*?'", ""); 254 255 final int dayIndex = filteredPattern.indexOf('d'); 256 final int monthMIndex = filteredPattern.indexOf("M"); 257 final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L"); 258 final int yearIndex = filteredPattern.indexOf("y"); 259 260 if (yearIndex < monthIndex) { 261 result[YEAR_INDEX] = 0; 262 263 if (monthIndex < dayIndex) { 264 result[MONTH_INDEX] = 1; 265 result[DAY_INDEX] = 2; 266 } else { 267 result[MONTH_INDEX] = 2; 268 result[DAY_INDEX] = 1; 269 } 270 } else { 271 result[YEAR_INDEX] = 2; 272 273 if (monthIndex < dayIndex) { 274 result[MONTH_INDEX] = 0; 275 result[DAY_INDEX] = 1; 276 } else { 277 result[MONTH_INDEX] = 1; 278 result[DAY_INDEX] = 0; 279 } 280 } 281 return result; 282 } 283 284 private void updateDisplay(boolean announce) { 285 if (mDayOfWeekView != null) { 286 mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, 287 Locale.getDefault())); 288 } 289 290 // Compute indices of Month, Day and Year views 291 final String bestDateTimePattern = 292 DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd"); 293 final int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern); 294 295 // Position the Year and MonthAndDay views within the header. 296 mMonthDayYearLayout.removeAllViews(); 297 if (viewIndices[YEAR_INDEX] == 0) { 298 mMonthDayYearLayout.addView(mHeaderYearTextView); 299 mMonthDayYearLayout.addView(mMonthAndDayLayout); 300 } else { 301 mMonthDayYearLayout.addView(mMonthAndDayLayout); 302 mMonthDayYearLayout.addView(mHeaderYearTextView); 303 } 304 305 // Position Day and Month views within the MonthAndDay view. 306 mMonthAndDayLayout.removeAllViews(); 307 if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) { 308 mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); 309 mMonthAndDayLayout.addView(mHeaderMonthTextView); 310 } else { 311 mMonthAndDayLayout.addView(mHeaderMonthTextView); 312 mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); 313 } 314 315 mHeaderMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT, 316 Locale.getDefault()).toUpperCase(Locale.getDefault())); 317 mHeaderDayOfMonthTextView.setText(mDayFormat.format(mCurrentDate.getTime())); 318 mHeaderYearTextView.setText(mYearFormat.format(mCurrentDate.getTime())); 319 320 // Accessibility. 321 long millis = mCurrentDate.getTimeInMillis(); 322 mAnimator.setDateMillis(millis); 323 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR; 324 String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags); 325 mMonthAndDayLayout.setContentDescription(monthAndDayText); 326 327 if (announce) { 328 flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; 329 String fullDateText = DateUtils.formatDateTime(mContext, millis, flags); 330 mAnimator.announceForAccessibility(fullDateText); 331 } 332 } 333 334 private void setCurrentView(final int viewIndex) { 335 long millis = mCurrentDate.getTimeInMillis(); 336 337 switch (viewIndex) { 338 case MONTH_AND_DAY_VIEW: 339 mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); 340 if (mCurrentView != viewIndex) { 341 mMonthAndDayLayout.setSelected(true); 342 mHeaderYearTextView.setSelected(false); 343 mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); 344 mCurrentView = viewIndex; 345 } 346 347 final int flags = DateUtils.FORMAT_SHOW_DATE; 348 final String dayString = DateUtils.formatDateTime(mContext, millis, flags); 349 mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString); 350 mAnimator.announceForAccessibility(mSelectDay); 351 break; 352 case YEAR_VIEW: 353 mYearPickerView.onDateChanged(); 354 if (mCurrentView != viewIndex) { 355 mMonthAndDayLayout.setSelected(false); 356 mHeaderYearTextView.setSelected(true); 357 mAnimator.setDisplayedChild(YEAR_VIEW); 358 mCurrentView = viewIndex; 359 } 360 361 final CharSequence yearString = mYearFormat.format(millis); 362 mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString); 363 mAnimator.announceForAccessibility(mSelectYear); 364 break; 365 } 366 } 367 368 @Override 369 public void init(int year, int monthOfYear, int dayOfMonth, 370 DatePicker.OnDateChangedListener callBack) { 371 mCurrentDate.set(Calendar.YEAR, year); 372 mCurrentDate.set(Calendar.MONTH, monthOfYear); 373 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); 374 375 onDateChanged(false); 376 377 // Set the listener last so that we don't call it. 378 mDateChangedListener = callBack; 379 } 380 381 @Override 382 public void updateDate(int year, int month, int dayOfMonth) { 383 mCurrentDate.set(Calendar.YEAR, year); 384 mCurrentDate.set(Calendar.MONTH, month); 385 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); 386 387 onDateChanged(false); 388 } 389 390 private void onDateChanged(boolean fromUser) { 391 if (mDateChangedListener != null) { 392 final int year = mCurrentDate.get(Calendar.YEAR); 393 final int monthOfYear = mCurrentDate.get(Calendar.MONTH); 394 final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH); 395 mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth); 396 } 397 398 for (OnDateChangedListener listener : mListeners) { 399 listener.onDateChanged(); 400 } 401 402 mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); 403 404 updateDisplay(fromUser); 405 406 if (fromUser) { 407 tryVibrate(); 408 } 409 } 410 411 @Override 412 public int getYear() { 413 return mCurrentDate.get(Calendar.YEAR); 414 } 415 416 @Override 417 public int getMonth() { 418 return mCurrentDate.get(Calendar.MONTH); 419 } 420 421 @Override 422 public int getDayOfMonth() { 423 return mCurrentDate.get(Calendar.DAY_OF_MONTH); 424 } 425 426 @Override 427 public void setMinDate(long minDate) { 428 mTempDate.setTimeInMillis(minDate); 429 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) 430 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { 431 return; 432 } 433 if (mCurrentDate.before(mTempDate)) { 434 mCurrentDate.setTimeInMillis(minDate); 435 onDateChanged(false); 436 } 437 mMinDate.setTimeInMillis(minDate); 438 mDayPickerView.setMinDate(minDate); 439 mYearPickerView.setRange(mMinDate, mMaxDate); 440 } 441 442 @Override 443 public Calendar getMinDate() { 444 return mMinDate; 445 } 446 447 @Override 448 public void setMaxDate(long maxDate) { 449 mTempDate.setTimeInMillis(maxDate); 450 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) 451 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { 452 return; 453 } 454 if (mCurrentDate.after(mTempDate)) { 455 mCurrentDate.setTimeInMillis(maxDate); 456 onDateChanged(false); 457 } 458 mMaxDate.setTimeInMillis(maxDate); 459 mDayPickerView.setMaxDate(maxDate); 460 mYearPickerView.setRange(mMinDate, mMaxDate); 461 } 462 463 @Override 464 public Calendar getMaxDate() { 465 return mMaxDate; 466 } 467 468 @Override 469 public void setFirstDayOfWeek(int firstDayOfWeek) { 470 mFirstDayOfWeek = firstDayOfWeek; 471 472 mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); 473 } 474 475 @Override 476 public int getFirstDayOfWeek() { 477 if (mFirstDayOfWeek != USE_LOCALE) { 478 return mFirstDayOfWeek; 479 } 480 return mCurrentDate.getFirstDayOfWeek(); 481 } 482 483 @Override 484 public void setEnabled(boolean enabled) { 485 mMonthAndDayLayout.setEnabled(enabled); 486 mHeaderYearTextView.setEnabled(enabled); 487 mAnimator.setEnabled(enabled); 488 mIsEnabled = enabled; 489 } 490 491 @Override 492 public boolean isEnabled() { 493 return mIsEnabled; 494 } 495 496 @Override 497 public CalendarView getCalendarView() { 498 throw new UnsupportedOperationException( 499 "CalendarView does not exists for the new DatePicker"); 500 } 501 502 @Override 503 public void setCalendarViewShown(boolean shown) { 504 // No-op for compatibility with the old DatePicker. 505 } 506 507 @Override 508 public boolean getCalendarViewShown() { 509 return false; 510 } 511 512 @Override 513 public void setSpinnersShown(boolean shown) { 514 // No-op for compatibility with the old DatePicker. 515 } 516 517 @Override 518 public boolean getSpinnersShown() { 519 return false; 520 } 521 522 @Override 523 public void onConfigurationChanged(Configuration newConfig) { 524 mYearFormat = new SimpleDateFormat("y", newConfig.locale); 525 mDayFormat = new SimpleDateFormat("d", newConfig.locale); 526 } 527 528 @Override 529 public Parcelable onSaveInstanceState(Parcelable superState) { 530 final int year = mCurrentDate.get(Calendar.YEAR); 531 final int month = mCurrentDate.get(Calendar.MONTH); 532 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); 533 534 int listPosition = -1; 535 int listPositionOffset = -1; 536 537 if (mCurrentView == MONTH_AND_DAY_VIEW) { 538 listPosition = mDayPickerView.getMostVisiblePosition(); 539 } else if (mCurrentView == YEAR_VIEW) { 540 listPosition = mYearPickerView.getFirstVisiblePosition(); 541 listPositionOffset = mYearPickerView.getFirstPositionOffset(); 542 } 543 544 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(), 545 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset); 546 } 547 548 @Override 549 public void onRestoreInstanceState(Parcelable state) { 550 SavedState ss = (SavedState) state; 551 552 mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); 553 mCurrentView = ss.getCurrentView(); 554 mMinDate.setTimeInMillis(ss.getMinDate()); 555 mMaxDate.setTimeInMillis(ss.getMaxDate()); 556 557 updateDisplay(false); 558 setCurrentView(mCurrentView); 559 560 final int listPosition = ss.getListPosition(); 561 if (listPosition != -1) { 562 if (mCurrentView == MONTH_AND_DAY_VIEW) { 563 mDayPickerView.postSetSelection(listPosition); 564 } else if (mCurrentView == YEAR_VIEW) { 565 mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset()); 566 } 567 } 568 } 569 570 @Override 571 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 572 onPopulateAccessibilityEvent(event); 573 return true; 574 } 575 576 @Override 577 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 578 event.getText().add(mCurrentDate.getTime().toString()); 579 } 580 581 @Override 582 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 583 event.setClassName(DatePicker.class.getName()); 584 } 585 586 @Override 587 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 588 info.setClassName(DatePicker.class.getName()); 589 } 590 591 @Override 592 public void onYearSelected(int year) { 593 adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year); 594 mCurrentDate.set(Calendar.YEAR, year); 595 onDateChanged(true); 596 597 // Auto-advance to month and day view. 598 setCurrentView(MONTH_AND_DAY_VIEW); 599 } 600 601 // If the newly selected month / year does not contain the currently selected day number, 602 // change the selected day number to the last day of the selected month or year. 603 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 604 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 605 private void adjustDayInMonthIfNeeded(int month, int year) { 606 int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); 607 int daysInMonth = getDaysInMonth(month, year); 608 if (day > daysInMonth) { 609 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth); 610 } 611 } 612 613 public static int getDaysInMonth(int month, int year) { 614 switch (month) { 615 case Calendar.JANUARY: 616 case Calendar.MARCH: 617 case Calendar.MAY: 618 case Calendar.JULY: 619 case Calendar.AUGUST: 620 case Calendar.OCTOBER: 621 case Calendar.DECEMBER: 622 return 31; 623 case Calendar.APRIL: 624 case Calendar.JUNE: 625 case Calendar.SEPTEMBER: 626 case Calendar.NOVEMBER: 627 return 30; 628 case Calendar.FEBRUARY: 629 return (year % 4 == 0) ? 29 : 28; 630 default: 631 throw new IllegalArgumentException("Invalid Month"); 632 } 633 } 634 635 @Override 636 public void registerOnDateChangedListener(OnDateChangedListener listener) { 637 mListeners.add(listener); 638 } 639 640 @Override 641 public Calendar getSelectedDay() { 642 return mCurrentDate; 643 } 644 645 @Override 646 public void tryVibrate() { 647 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE); 648 } 649 650 @Override 651 public void onClick(View v) { 652 tryVibrate(); 653 if (v.getId() == R.id.date_picker_year) { 654 setCurrentView(YEAR_VIEW); 655 } else if (v.getId() == R.id.date_picker_month_and_day_layout) { 656 setCurrentView(MONTH_AND_DAY_VIEW); 657 } 658 } 659 660 /** 661 * Listener called when the user selects a day in the day picker view. 662 */ 663 private final DayPickerView.OnDaySelectedListener 664 mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() { 665 @Override 666 public void onDaySelected(DayPickerView view, Calendar day) { 667 mCurrentDate.setTimeInMillis(day.getTimeInMillis()); 668 onDateChanged(true); 669 } 670 }; 671 672 /** 673 * Class for managing state storing/restoring. 674 */ 675 private static class SavedState extends View.BaseSavedState { 676 677 private final int mSelectedYear; 678 private final int mSelectedMonth; 679 private final int mSelectedDay; 680 private final long mMinDate; 681 private final long mMaxDate; 682 private final int mCurrentView; 683 private final int mListPosition; 684 private final int mListPositionOffset; 685 686 /** 687 * Constructor called from {@link DatePicker#onSaveInstanceState()} 688 */ 689 private SavedState(Parcelable superState, int year, int month, int day, 690 long minDate, long maxDate, int currentView, int listPosition, 691 int listPositionOffset) { 692 super(superState); 693 mSelectedYear = year; 694 mSelectedMonth = month; 695 mSelectedDay = day; 696 mMinDate = minDate; 697 mMaxDate = maxDate; 698 mCurrentView = currentView; 699 mListPosition = listPosition; 700 mListPositionOffset = listPositionOffset; 701 } 702 703 /** 704 * Constructor called from {@link #CREATOR} 705 */ 706 private SavedState(Parcel in) { 707 super(in); 708 mSelectedYear = in.readInt(); 709 mSelectedMonth = in.readInt(); 710 mSelectedDay = in.readInt(); 711 mMinDate = in.readLong(); 712 mMaxDate = in.readLong(); 713 mCurrentView = in.readInt(); 714 mListPosition = in.readInt(); 715 mListPositionOffset = in.readInt(); 716 } 717 718 @Override 719 public void writeToParcel(Parcel dest, int flags) { 720 super.writeToParcel(dest, flags); 721 dest.writeInt(mSelectedYear); 722 dest.writeInt(mSelectedMonth); 723 dest.writeInt(mSelectedDay); 724 dest.writeLong(mMinDate); 725 dest.writeLong(mMaxDate); 726 dest.writeInt(mCurrentView); 727 dest.writeInt(mListPosition); 728 dest.writeInt(mListPositionOffset); 729 } 730 731 public int getSelectedDay() { 732 return mSelectedDay; 733 } 734 735 public int getSelectedMonth() { 736 return mSelectedMonth; 737 } 738 739 public int getSelectedYear() { 740 return mSelectedYear; 741 } 742 743 public long getMinDate() { 744 return mMinDate; 745 } 746 747 public long getMaxDate() { 748 return mMaxDate; 749 } 750 751 public int getCurrentView() { 752 return mCurrentView; 753 } 754 755 public int getListPosition() { 756 return mListPosition; 757 } 758 759 public int getListPositionOffset() { 760 return mListPositionOffset; 761 } 762 763 @SuppressWarnings("all") 764 // suppress unused and hiding 765 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 766 767 public SavedState createFromParcel(Parcel in) { 768 return new SavedState(in); 769 } 770 771 public SavedState[] newArray(int size) { 772 return new SavedState[size]; 773 } 774 }; 775 } 776} 777