DatePickerCalendarDelegate.java revision 0cecbc9c7208f918ddf9d117057649f6e132069f
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 mDateChangedListener = callBack; 376 377 onDateChanged(false, false); 378 } 379 380 @Override 381 public void updateDate(int year, int month, int dayOfMonth) { 382 mCurrentDate.set(Calendar.YEAR, year); 383 mCurrentDate.set(Calendar.MONTH, month); 384 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); 385 386 onDateChanged(false, true); 387 } 388 389 private void onDateChanged(boolean fromUser, boolean callbackToClient) { 390 if (callbackToClient && mDateChangedListener != null) { 391 final int year = mCurrentDate.get(Calendar.YEAR); 392 final int monthOfYear = mCurrentDate.get(Calendar.MONTH); 393 final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH); 394 mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth); 395 } 396 397 for (OnDateChangedListener listener : mListeners) { 398 listener.onDateChanged(); 399 } 400 401 mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); 402 403 updateDisplay(fromUser); 404 405 if (fromUser) { 406 tryVibrate(); 407 } 408 } 409 410 @Override 411 public int getYear() { 412 return mCurrentDate.get(Calendar.YEAR); 413 } 414 415 @Override 416 public int getMonth() { 417 return mCurrentDate.get(Calendar.MONTH); 418 } 419 420 @Override 421 public int getDayOfMonth() { 422 return mCurrentDate.get(Calendar.DAY_OF_MONTH); 423 } 424 425 @Override 426 public void setMinDate(long minDate) { 427 mTempDate.setTimeInMillis(minDate); 428 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) 429 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { 430 return; 431 } 432 if (mCurrentDate.before(mTempDate)) { 433 mCurrentDate.setTimeInMillis(minDate); 434 onDateChanged(false, true); 435 } 436 mMinDate.setTimeInMillis(minDate); 437 mDayPickerView.setMinDate(minDate); 438 mYearPickerView.setRange(mMinDate, mMaxDate); 439 } 440 441 @Override 442 public Calendar getMinDate() { 443 return mMinDate; 444 } 445 446 @Override 447 public void setMaxDate(long maxDate) { 448 mTempDate.setTimeInMillis(maxDate); 449 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) 450 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { 451 return; 452 } 453 if (mCurrentDate.after(mTempDate)) { 454 mCurrentDate.setTimeInMillis(maxDate); 455 onDateChanged(false, true); 456 } 457 mMaxDate.setTimeInMillis(maxDate); 458 mDayPickerView.setMaxDate(maxDate); 459 mYearPickerView.setRange(mMinDate, mMaxDate); 460 } 461 462 @Override 463 public Calendar getMaxDate() { 464 return mMaxDate; 465 } 466 467 @Override 468 public void setFirstDayOfWeek(int firstDayOfWeek) { 469 mFirstDayOfWeek = firstDayOfWeek; 470 471 mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); 472 } 473 474 @Override 475 public int getFirstDayOfWeek() { 476 if (mFirstDayOfWeek != USE_LOCALE) { 477 return mFirstDayOfWeek; 478 } 479 return mCurrentDate.getFirstDayOfWeek(); 480 } 481 482 @Override 483 public void setEnabled(boolean enabled) { 484 mMonthAndDayLayout.setEnabled(enabled); 485 mHeaderYearTextView.setEnabled(enabled); 486 mAnimator.setEnabled(enabled); 487 mIsEnabled = enabled; 488 } 489 490 @Override 491 public boolean isEnabled() { 492 return mIsEnabled; 493 } 494 495 @Override 496 public CalendarView getCalendarView() { 497 throw new UnsupportedOperationException( 498 "CalendarView does not exists for the new DatePicker"); 499 } 500 501 @Override 502 public void setCalendarViewShown(boolean shown) { 503 // No-op for compatibility with the old DatePicker. 504 } 505 506 @Override 507 public boolean getCalendarViewShown() { 508 return false; 509 } 510 511 @Override 512 public void setSpinnersShown(boolean shown) { 513 // No-op for compatibility with the old DatePicker. 514 } 515 516 @Override 517 public boolean getSpinnersShown() { 518 return false; 519 } 520 521 @Override 522 public void onConfigurationChanged(Configuration newConfig) { 523 mYearFormat = new SimpleDateFormat("y", newConfig.locale); 524 mDayFormat = new SimpleDateFormat("d", newConfig.locale); 525 } 526 527 @Override 528 public Parcelable onSaveInstanceState(Parcelable superState) { 529 final int year = mCurrentDate.get(Calendar.YEAR); 530 final int month = mCurrentDate.get(Calendar.MONTH); 531 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); 532 533 int listPosition = -1; 534 int listPositionOffset = -1; 535 536 if (mCurrentView == MONTH_AND_DAY_VIEW) { 537 listPosition = mDayPickerView.getMostVisiblePosition(); 538 } else if (mCurrentView == YEAR_VIEW) { 539 listPosition = mYearPickerView.getFirstVisiblePosition(); 540 listPositionOffset = mYearPickerView.getFirstPositionOffset(); 541 } 542 543 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(), 544 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset); 545 } 546 547 @Override 548 public void onRestoreInstanceState(Parcelable state) { 549 SavedState ss = (SavedState) state; 550 551 mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); 552 mCurrentView = ss.getCurrentView(); 553 mMinDate.setTimeInMillis(ss.getMinDate()); 554 mMaxDate.setTimeInMillis(ss.getMaxDate()); 555 556 updateDisplay(false); 557 setCurrentView(mCurrentView); 558 559 final int listPosition = ss.getListPosition(); 560 if (listPosition != -1) { 561 if (mCurrentView == MONTH_AND_DAY_VIEW) { 562 mDayPickerView.postSetSelection(listPosition); 563 } else if (mCurrentView == YEAR_VIEW) { 564 mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset()); 565 } 566 } 567 } 568 569 @Override 570 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 571 onPopulateAccessibilityEvent(event); 572 return true; 573 } 574 575 @Override 576 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 577 event.getText().add(mCurrentDate.getTime().toString()); 578 } 579 580 @Override 581 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 582 event.setClassName(DatePicker.class.getName()); 583 } 584 585 @Override 586 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 587 info.setClassName(DatePicker.class.getName()); 588 } 589 590 @Override 591 public void onYearSelected(int year) { 592 adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year); 593 mCurrentDate.set(Calendar.YEAR, year); 594 onDateChanged(true, true); 595 596 // Auto-advance to month and day view. 597 setCurrentView(MONTH_AND_DAY_VIEW); 598 } 599 600 // If the newly selected month / year does not contain the currently selected day number, 601 // change the selected day number to the last day of the selected month or year. 602 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 603 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 604 private void adjustDayInMonthIfNeeded(int month, int year) { 605 int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); 606 int daysInMonth = getDaysInMonth(month, year); 607 if (day > daysInMonth) { 608 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth); 609 } 610 } 611 612 public static int getDaysInMonth(int month, int year) { 613 switch (month) { 614 case Calendar.JANUARY: 615 case Calendar.MARCH: 616 case Calendar.MAY: 617 case Calendar.JULY: 618 case Calendar.AUGUST: 619 case Calendar.OCTOBER: 620 case Calendar.DECEMBER: 621 return 31; 622 case Calendar.APRIL: 623 case Calendar.JUNE: 624 case Calendar.SEPTEMBER: 625 case Calendar.NOVEMBER: 626 return 30; 627 case Calendar.FEBRUARY: 628 return (year % 4 == 0) ? 29 : 28; 629 default: 630 throw new IllegalArgumentException("Invalid Month"); 631 } 632 } 633 634 @Override 635 public void registerOnDateChangedListener(OnDateChangedListener listener) { 636 mListeners.add(listener); 637 } 638 639 @Override 640 public Calendar getSelectedDay() { 641 return mCurrentDate; 642 } 643 644 @Override 645 public void tryVibrate() { 646 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE); 647 } 648 649 @Override 650 public void onClick(View v) { 651 tryVibrate(); 652 if (v.getId() == R.id.date_picker_year) { 653 setCurrentView(YEAR_VIEW); 654 } else if (v.getId() == R.id.date_picker_month_and_day_layout) { 655 setCurrentView(MONTH_AND_DAY_VIEW); 656 } 657 } 658 659 /** 660 * Listener called when the user selects a day in the day picker view. 661 */ 662 private final DayPickerView.OnDaySelectedListener 663 mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() { 664 @Override 665 public void onDaySelected(DayPickerView view, Calendar day) { 666 mCurrentDate.setTimeInMillis(day.getTimeInMillis()); 667 onDateChanged(true, true); 668 } 669 }; 670 671 /** 672 * Class for managing state storing/restoring. 673 */ 674 private static class SavedState extends View.BaseSavedState { 675 676 private final int mSelectedYear; 677 private final int mSelectedMonth; 678 private final int mSelectedDay; 679 private final long mMinDate; 680 private final long mMaxDate; 681 private final int mCurrentView; 682 private final int mListPosition; 683 private final int mListPositionOffset; 684 685 /** 686 * Constructor called from {@link DatePicker#onSaveInstanceState()} 687 */ 688 private SavedState(Parcelable superState, int year, int month, int day, 689 long minDate, long maxDate, int currentView, int listPosition, 690 int listPositionOffset) { 691 super(superState); 692 mSelectedYear = year; 693 mSelectedMonth = month; 694 mSelectedDay = day; 695 mMinDate = minDate; 696 mMaxDate = maxDate; 697 mCurrentView = currentView; 698 mListPosition = listPosition; 699 mListPositionOffset = listPositionOffset; 700 } 701 702 /** 703 * Constructor called from {@link #CREATOR} 704 */ 705 private SavedState(Parcel in) { 706 super(in); 707 mSelectedYear = in.readInt(); 708 mSelectedMonth = in.readInt(); 709 mSelectedDay = in.readInt(); 710 mMinDate = in.readLong(); 711 mMaxDate = in.readLong(); 712 mCurrentView = in.readInt(); 713 mListPosition = in.readInt(); 714 mListPositionOffset = in.readInt(); 715 } 716 717 @Override 718 public void writeToParcel(Parcel dest, int flags) { 719 super.writeToParcel(dest, flags); 720 dest.writeInt(mSelectedYear); 721 dest.writeInt(mSelectedMonth); 722 dest.writeInt(mSelectedDay); 723 dest.writeLong(mMinDate); 724 dest.writeLong(mMaxDate); 725 dest.writeInt(mCurrentView); 726 dest.writeInt(mListPosition); 727 dest.writeInt(mListPositionOffset); 728 } 729 730 public int getSelectedDay() { 731 return mSelectedDay; 732 } 733 734 public int getSelectedMonth() { 735 return mSelectedMonth; 736 } 737 738 public int getSelectedYear() { 739 return mSelectedYear; 740 } 741 742 public long getMinDate() { 743 return mMinDate; 744 } 745 746 public long getMaxDate() { 747 return mMaxDate; 748 } 749 750 public int getCurrentView() { 751 return mCurrentView; 752 } 753 754 public int getListPosition() { 755 return mListPosition; 756 } 757 758 public int getListPositionOffset() { 759 return mListPositionOffset; 760 } 761 762 @SuppressWarnings("all") 763 // suppress unused and hiding 764 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 765 766 public SavedState createFromParcel(Parcel in) { 767 return new SavedState(in); 768 } 769 770 public SavedState[] newArray(int size) { 771 return new SavedState[size]; 772 } 773 }; 774 } 775} 776