1 /* 2 * Copyright (C) 2007 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 17 package android.widget; 18 19 import android.annotation.Widget; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.content.res.TypedArray; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 import android.text.format.DateFormat; 27 import android.text.format.DateUtils; 28 import android.util.AttributeSet; 29 import android.util.Log; 30 import android.util.SparseArray; 31 import android.view.LayoutInflater; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.view.accessibility.AccessibilityManager; 34 import android.view.inputmethod.EditorInfo; 35 import android.view.inputmethod.InputMethodManager; 36 import android.widget.NumberPicker.OnValueChangeListener; 37 38 import com.android.internal.R; 39 40 import java.text.ParseException; 41 import java.text.SimpleDateFormat; 42 import java.util.Arrays; 43 import java.util.Calendar; 44 import java.util.Locale; 45 import java.util.TimeZone; 46 47 /** 48 * This class is a widget for selecting a date. The date can be selected by a 49 * year, month, and day spinners or a {@link CalendarView}. The set of spinners 50 * and the calendar view are automatically synchronized. The client can 51 * customize whether only the spinners, or only the calendar view, or both to be 52 * displayed. Also the minimal and maximal date from which dates to be selected 53 * can be customized. 54 * <p> 55 * See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date 56 * Picker tutorial</a>. 57 * </p> 58 * <p> 59 * For a dialog using this view, see {@link android.app.DatePickerDialog}. 60 * </p> 61 * 62 * @attr ref android.R.styleable#DatePicker_startYear 63 * @attr ref android.R.styleable#DatePicker_endYear 64 * @attr ref android.R.styleable#DatePicker_maxDate 65 * @attr ref android.R.styleable#DatePicker_minDate 66 * @attr ref android.R.styleable#DatePicker_spinnersShown 67 * @attr ref android.R.styleable#DatePicker_calendarViewShown 68 */ 69 @Widget 70 public class DatePicker extends FrameLayout { 71 72 private static final String LOG_TAG = DatePicker.class.getSimpleName(); 73 74 private static final String DATE_FORMAT = "MM/dd/yyyy"; 75 76 private static final int DEFAULT_START_YEAR = 1900; 77 78 private static final int DEFAULT_END_YEAR = 2100; 79 80 private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; 81 82 private static final boolean DEFAULT_SPINNERS_SHOWN = true; 83 84 private static final boolean DEFAULT_ENABLED_STATE = true; 85 86 private final LinearLayout mSpinners; 87 88 private final NumberPicker mDaySpinner; 89 90 private final NumberPicker mMonthSpinner; 91 92 private final NumberPicker mYearSpinner; 93 94 private final EditText mDaySpinnerInput; 95 96 private final EditText mMonthSpinnerInput; 97 98 private final EditText mYearSpinnerInput; 99 100 private final CalendarView mCalendarView; 101 102 private Locale mCurrentLocale; 103 104 private OnDateChangedListener mOnDateChangedListener; 105 106 private String[] mShortMonths; 107 108 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); 109 110 private int mNumberOfMonths; 111 112 private Calendar mTempDate; 113 114 private Calendar mMinDate; 115 116 private Calendar mMaxDate; 117 118 private Calendar mCurrentDate; 119 120 private boolean mIsEnabled = DEFAULT_ENABLED_STATE; 121 122 /** 123 * The callback used to indicate the user changes\d the date. 124 */ 125 public interface OnDateChangedListener { 126 127 /** 128 * Called upon a date change. 129 * 130 * @param view The view associated with this listener. 131 * @param year The year that was set. 132 * @param monthOfYear The month that was set (0-11) for compatibility 133 * with {@link java.util.Calendar}. 134 * @param dayOfMonth The day of the month that was set. 135 */ 136 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); 137 } 138 139 public DatePicker(Context context) { 140 this(context, null); 141 } 142 143 public DatePicker(Context context, AttributeSet attrs) { 144 this(context, attrs, R.attr.datePickerStyle); 145 } 146 147 public DatePicker(Context context, AttributeSet attrs, int defStyle) { 148 super(context, attrs, defStyle); 149 150 // initialization based on locale 151 setCurrentLocale(Locale.getDefault()); 152 153 TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker, 154 defStyle, 0); 155 boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown, 156 DEFAULT_SPINNERS_SHOWN); 157 boolean calendarViewShown = attributesArray.getBoolean( 158 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); 159 int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear, 160 DEFAULT_START_YEAR); 161 int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); 162 String minDate = attributesArray.getString(R.styleable.DatePicker_minDate); 163 String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate); 164 int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_layout, 165 R.layout.date_picker); 166 attributesArray.recycle(); 167 168 LayoutInflater inflater = (LayoutInflater) context 169 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 170 inflater.inflate(layoutResourceId, this, true); 171 172 OnValueChangeListener onChangeListener = new OnValueChangeListener() { 173 public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 174 updateInputState(); 175 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); 176 // take care of wrapping of days and months to update greater fields 177 if (picker == mDaySpinner) { 178 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); 179 if (oldVal == maxDayOfMonth && newVal == 1) { 180 mTempDate.add(Calendar.DAY_OF_MONTH, 1); 181 } else if (oldVal == 1 && newVal == maxDayOfMonth) { 182 mTempDate.add(Calendar.DAY_OF_MONTH, -1); 183 } else { 184 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); 185 } 186 } else if (picker == mMonthSpinner) { 187 if (oldVal == 11 && newVal == 0) { 188 mTempDate.add(Calendar.MONTH, 1); 189 } else if (oldVal == 0 && newVal == 11) { 190 mTempDate.add(Calendar.MONTH, -1); 191 } else { 192 mTempDate.add(Calendar.MONTH, newVal - oldVal); 193 } 194 } else if (picker == mYearSpinner) { 195 mTempDate.set(Calendar.YEAR, newVal); 196 } else { 197 throw new IllegalArgumentException(); 198 } 199 // now set the date to the adjusted one 200 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), 201 mTempDate.get(Calendar.DAY_OF_MONTH)); 202 updateSpinners(); 203 updateCalendarView(); 204 notifyDateChanged(); 205 } 206 }; 207 208 mSpinners = (LinearLayout) findViewById(R.id.pickers); 209 210 // calendar view day-picker 211 mCalendarView = (CalendarView) findViewById(R.id.calendar_view); 212 mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { 213 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { 214 setDate(year, month, monthDay); 215 updateSpinners(); 216 notifyDateChanged(); 217 } 218 }); 219 220 // day 221 mDaySpinner = (NumberPicker) findViewById(R.id.day); 222 mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); 223 mDaySpinner.setOnLongPressUpdateInterval(100); 224 mDaySpinner.setOnValueChangedListener(onChangeListener); 225 mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input); 226 227 // month 228 mMonthSpinner = (NumberPicker) findViewById(R.id.month); 229 mMonthSpinner.setMinValue(0); 230 mMonthSpinner.setMaxValue(mNumberOfMonths - 1); 231 mMonthSpinner.setDisplayedValues(mShortMonths); 232 mMonthSpinner.setOnLongPressUpdateInterval(200); 233 mMonthSpinner.setOnValueChangedListener(onChangeListener); 234 mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input); 235 236 // year 237 mYearSpinner = (NumberPicker) findViewById(R.id.year); 238 mYearSpinner.setOnLongPressUpdateInterval(100); 239 mYearSpinner.setOnValueChangedListener(onChangeListener); 240 mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input); 241 242 // show only what the user required but make sure we 243 // show something and the spinners have higher priority 244 if (!spinnersShown && !calendarViewShown) { 245 setSpinnersShown(true); 246 } else { 247 setSpinnersShown(spinnersShown); 248 setCalendarViewShown(calendarViewShown); 249 } 250 251 // set the min date giving priority of the minDate over startYear 252 mTempDate.clear(); 253 if (!TextUtils.isEmpty(minDate)) { 254 if (!parseDate(minDate, mTempDate)) { 255 mTempDate.set(startYear, 0, 1); 256 } 257 } else { 258 mTempDate.set(startYear, 0, 1); 259 } 260 setMinDate(mTempDate.getTimeInMillis()); 261 262 // set the max date giving priority of the maxDate over endYear 263 mTempDate.clear(); 264 if (!TextUtils.isEmpty(maxDate)) { 265 if (!parseDate(maxDate, mTempDate)) { 266 mTempDate.set(endYear, 11, 31); 267 } 268 } else { 269 mTempDate.set(endYear, 11, 31); 270 } 271 setMaxDate(mTempDate.getTimeInMillis()); 272 273 // initialize to current date 274 mCurrentDate.setTimeInMillis(System.currentTimeMillis()); 275 init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate 276 .get(Calendar.DAY_OF_MONTH), null); 277 278 // re-order the number spinners to match the current date format 279 reorderSpinners(); 280 281 // set content descriptions 282 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 283 setContentDescriptions(); 284 } 285 } 286 287 /** 288 * Gets the minimal date supported by this {@link DatePicker} in 289 * milliseconds since January 1, 1970 00:00:00 in 290 * {@link TimeZone#getDefault()} time zone. 291 * <p> 292 * Note: The default minimal date is 01/01/1900. 293 * <p> 294 * 295 * @return The minimal supported date. 296 */ 297 public long getMinDate() { 298 return mCalendarView.getMinDate(); 299 } 300 301 /** 302 * Sets the minimal date supported by this {@link NumberPicker} in 303 * milliseconds since January 1, 1970 00:00:00 in 304 * {@link TimeZone#getDefault()} time zone. 305 * 306 * @param minDate The minimal supported date. 307 */ 308 public void setMinDate(long minDate) { 309 mTempDate.setTimeInMillis(minDate); 310 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) 311 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { 312 return; 313 } 314 mMinDate.setTimeInMillis(minDate); 315 mCalendarView.setMinDate(minDate); 316 if (mCurrentDate.before(mMinDate)) { 317 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 318 updateCalendarView(); 319 } 320 updateSpinners(); 321 } 322 323 /** 324 * Gets the maximal date supported by this {@link DatePicker} in 325 * milliseconds since January 1, 1970 00:00:00 in 326 * {@link TimeZone#getDefault()} time zone. 327 * <p> 328 * Note: The default maximal date is 12/31/2100. 329 * <p> 330 * 331 * @return The maximal supported date. 332 */ 333 public long getMaxDate() { 334 return mCalendarView.getMaxDate(); 335 } 336 337 /** 338 * Sets the maximal date supported by this {@link DatePicker} in 339 * milliseconds since January 1, 1970 00:00:00 in 340 * {@link TimeZone#getDefault()} time zone. 341 * 342 * @param maxDate The maximal supported date. 343 */ 344 public void setMaxDate(long maxDate) { 345 mTempDate.setTimeInMillis(maxDate); 346 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) 347 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { 348 return; 349 } 350 mMaxDate.setTimeInMillis(maxDate); 351 mCalendarView.setMaxDate(maxDate); 352 if (mCurrentDate.after(mMaxDate)) { 353 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 354 updateCalendarView(); 355 } 356 updateSpinners(); 357 } 358 359 @Override 360 public void setEnabled(boolean enabled) { 361 if (mIsEnabled == enabled) { 362 return; 363 } 364 super.setEnabled(enabled); 365 mDaySpinner.setEnabled(enabled); 366 mMonthSpinner.setEnabled(enabled); 367 mYearSpinner.setEnabled(enabled); 368 mCalendarView.setEnabled(enabled); 369 mIsEnabled = enabled; 370 } 371 372 @Override 373 public boolean isEnabled() { 374 return mIsEnabled; 375 } 376 377 @Override 378 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 379 onPopulateAccessibilityEvent(event); 380 return true; 381 } 382 383 @Override 384 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 385 super.onPopulateAccessibilityEvent(event); 386 387 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; 388 String selectedDateUtterance = DateUtils.formatDateTime(mContext, 389 mCurrentDate.getTimeInMillis(), flags); 390 event.getText().add(selectedDateUtterance); 391 } 392 393 @Override 394 protected void onConfigurationChanged(Configuration newConfig) { 395 super.onConfigurationChanged(newConfig); 396 setCurrentLocale(newConfig.locale); 397 } 398 399 /** 400 * Gets whether the {@link CalendarView} is shown. 401 * 402 * @return True if the calendar view is shown. 403 * @see #getCalendarView() 404 */ 405 public boolean getCalendarViewShown() { 406 return mCalendarView.isShown(); 407 } 408 409 /** 410 * Gets the {@link CalendarView}. 411 * 412 * @return The calendar view. 413 * @see #getCalendarViewShown() 414 */ 415 public CalendarView getCalendarView () { 416 return mCalendarView; 417 } 418 419 /** 420 * Sets whether the {@link CalendarView} is shown. 421 * 422 * @param shown True if the calendar view is to be shown. 423 */ 424 public void setCalendarViewShown(boolean shown) { 425 mCalendarView.setVisibility(shown ? VISIBLE : GONE); 426 } 427 428 /** 429 * Gets whether the spinners are shown. 430 * 431 * @return True if the spinners are shown. 432 */ 433 public boolean getSpinnersShown() { 434 return mSpinners.isShown(); 435 } 436 437 /** 438 * Sets whether the spinners are shown. 439 * 440 * @param shown True if the spinners are to be shown. 441 */ 442 public void setSpinnersShown(boolean shown) { 443 mSpinners.setVisibility(shown ? VISIBLE : GONE); 444 } 445 446 /** 447 * Sets the current locale. 448 * 449 * @param locale The current locale. 450 */ 451 private void setCurrentLocale(Locale locale) { 452 if (locale.equals(mCurrentLocale)) { 453 return; 454 } 455 456 mCurrentLocale = locale; 457 458 mTempDate = getCalendarForLocale(mTempDate, locale); 459 mMinDate = getCalendarForLocale(mMinDate, locale); 460 mMaxDate = getCalendarForLocale(mMaxDate, locale); 461 mCurrentDate = getCalendarForLocale(mCurrentDate, locale); 462 463 mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; 464 mShortMonths = new String[mNumberOfMonths]; 465 for (int i = 0; i < mNumberOfMonths; i++) { 466 mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i, 467 DateUtils.LENGTH_MEDIUM); 468 } 469 } 470 471 /** 472 * Gets a calendar for locale bootstrapped with the value of a given calendar. 473 * 474 * @param oldCalendar The old calendar. 475 * @param locale The locale. 476 */ 477 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 478 if (oldCalendar == null) { 479 return Calendar.getInstance(locale); 480 } else { 481 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 482 Calendar newCalendar = Calendar.getInstance(locale); 483 newCalendar.setTimeInMillis(currentTimeMillis); 484 return newCalendar; 485 } 486 } 487 488 /** 489 * Reorders the spinners according to the date format that is 490 * explicitly set by the user and if no such is set fall back 491 * to the current locale's default format. 492 */ 493 private void reorderSpinners() { 494 mSpinners.removeAllViews(); 495 char[] order = DateFormat.getDateFormatOrder(getContext()); 496 final int spinnerCount = order.length; 497 for (int i = 0; i < spinnerCount; i++) { 498 switch (order[i]) { 499 case DateFormat.DATE: 500 mSpinners.addView(mDaySpinner); 501 setImeOptions(mDaySpinner, spinnerCount, i); 502 break; 503 case DateFormat.MONTH: 504 mSpinners.addView(mMonthSpinner); 505 setImeOptions(mMonthSpinner, spinnerCount, i); 506 break; 507 case DateFormat.YEAR: 508 mSpinners.addView(mYearSpinner); 509 setImeOptions(mYearSpinner, spinnerCount, i); 510 break; 511 default: 512 throw new IllegalArgumentException(); 513 } 514 } 515 } 516 517 /** 518 * Updates the current date. 519 * 520 * @param year The year. 521 * @param month The month which is <strong>starting from zero</strong>. 522 * @param dayOfMonth The day of the month. 523 */ 524 public void updateDate(int year, int month, int dayOfMonth) { 525 if (!isNewDate(year, month, dayOfMonth)) { 526 return; 527 } 528 setDate(year, month, dayOfMonth); 529 updateSpinners(); 530 updateCalendarView(); 531 notifyDateChanged(); 532 } 533 534 // Override so we are in complete control of save / restore for this widget. 535 @Override 536 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 537 dispatchThawSelfOnly(container); 538 } 539 540 @Override 541 protected Parcelable onSaveInstanceState() { 542 Parcelable superState = super.onSaveInstanceState(); 543 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth()); 544 } 545 546 @Override 547 protected void onRestoreInstanceState(Parcelable state) { 548 SavedState ss = (SavedState) state; 549 super.onRestoreInstanceState(ss.getSuperState()); 550 setDate(ss.mYear, ss.mMonth, ss.mDay); 551 updateSpinners(); 552 updateCalendarView(); 553 } 554 555 /** 556 * Initialize the state. If the provided values designate an inconsistent 557 * date the values are normalized before updating the spinners. 558 * 559 * @param year The initial year. 560 * @param monthOfYear The initial month <strong>starting from zero</strong>. 561 * @param dayOfMonth The initial day of the month. 562 * @param onDateChangedListener How user is notified date is changed by 563 * user, can be null. 564 */ 565 public void init(int year, int monthOfYear, int dayOfMonth, 566 OnDateChangedListener onDateChangedListener) { 567 setDate(year, monthOfYear, dayOfMonth); 568 updateSpinners(); 569 updateCalendarView(); 570 mOnDateChangedListener = onDateChangedListener; 571 } 572 573 /** 574 * Parses the given <code>date</code> and in case of success sets the result 575 * to the <code>outDate</code>. 576 * 577 * @return True if the date was parsed. 578 */ 579 private boolean parseDate(String date, Calendar outDate) { 580 try { 581 outDate.setTime(mDateFormat.parse(date)); 582 return true; 583 } catch (ParseException e) { 584 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); 585 return false; 586 } 587 } 588 589 private boolean isNewDate(int year, int month, int dayOfMonth) { 590 return (mCurrentDate.get(Calendar.YEAR) != year 591 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth 592 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month); 593 } 594 595 private void setDate(int year, int month, int dayOfMonth) { 596 mCurrentDate.set(year, month, dayOfMonth); 597 if (mCurrentDate.before(mMinDate)) { 598 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 599 } else if (mCurrentDate.after(mMaxDate)) { 600 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 601 } 602 } 603 604 private void updateSpinners() { 605 // set the spinner ranges respecting the min and max dates 606 if (mCurrentDate.equals(mMinDate)) { 607 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 608 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 609 mDaySpinner.setWrapSelectorWheel(false); 610 mMonthSpinner.setDisplayedValues(null); 611 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); 612 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); 613 mMonthSpinner.setWrapSelectorWheel(false); 614 } else if (mCurrentDate.equals(mMaxDate)) { 615 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); 616 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 617 mDaySpinner.setWrapSelectorWheel(false); 618 mMonthSpinner.setDisplayedValues(null); 619 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); 620 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); 621 mMonthSpinner.setWrapSelectorWheel(false); 622 } else { 623 mDaySpinner.setMinValue(1); 624 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 625 mDaySpinner.setWrapSelectorWheel(true); 626 mMonthSpinner.setDisplayedValues(null); 627 mMonthSpinner.setMinValue(0); 628 mMonthSpinner.setMaxValue(11); 629 mMonthSpinner.setWrapSelectorWheel(true); 630 } 631 632 // make sure the month names are a zero based array 633 // with the months in the month spinner 634 String[] displayedValues = Arrays.copyOfRange(mShortMonths, 635 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); 636 mMonthSpinner.setDisplayedValues(displayedValues); 637 638 // year spinner range does not change based on the current date 639 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); 640 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); 641 mYearSpinner.setWrapSelectorWheel(false); 642 643 // set the spinner values 644 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); 645 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); 646 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 647 } 648 649 /** 650 * Updates the calendar view with the current date. 651 */ 652 private void updateCalendarView() { 653 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false); 654 } 655 656 /** 657 * @return The selected year. 658 */ 659 public int getYear() { 660 return mCurrentDate.get(Calendar.YEAR); 661 } 662 663 /** 664 * @return The selected month. 665 */ 666 public int getMonth() { 667 return mCurrentDate.get(Calendar.MONTH); 668 } 669 670 /** 671 * @return The selected day of month. 672 */ 673 public int getDayOfMonth() { 674 return mCurrentDate.get(Calendar.DAY_OF_MONTH); 675 } 676 677 /** 678 * Notifies the listener, if such, for a change in the selected date. 679 */ 680 private void notifyDateChanged() { 681 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 682 if (mOnDateChangedListener != null) { 683 mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth()); 684 } 685 } 686 687 /** 688 * Sets the IME options for a spinner based on its ordering. 689 * 690 * @param spinner The spinner. 691 * @param spinnerCount The total spinner count. 692 * @param spinnerIndex The index of the given spinner. 693 */ 694 private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { 695 final int imeOptions; 696 if (spinnerIndex < spinnerCount - 1) { 697 imeOptions = EditorInfo.IME_ACTION_NEXT; 698 } else { 699 imeOptions = EditorInfo.IME_ACTION_DONE; 700 } 701 TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input); 702 input.setImeOptions(imeOptions); 703 } 704 705 private void setContentDescriptions() { 706 // Day 707 String text = mContext.getString(R.string.date_picker_increment_day_button); 708 mDaySpinner.findViewById(R.id.increment).setContentDescription(text); 709 text = mContext.getString(R.string.date_picker_decrement_day_button); 710 mDaySpinner.findViewById(R.id.decrement).setContentDescription(text); 711 // Month 712 text = mContext.getString(R.string.date_picker_increment_month_button); 713 mMonthSpinner.findViewById(R.id.increment).setContentDescription(text); 714 text = mContext.getString(R.string.date_picker_decrement_month_button); 715 mMonthSpinner.findViewById(R.id.decrement).setContentDescription(text); 716 // Year 717 text = mContext.getString(R.string.date_picker_increment_year_button); 718 mYearSpinner.findViewById(R.id.increment).setContentDescription(text); 719 text = mContext.getString(R.string.date_picker_decrement_year_button); 720 mYearSpinner.findViewById(R.id.decrement).setContentDescription(text); 721 } 722 723 private void updateInputState() { 724 // Make sure that if the user changes the value and the IME is active 725 // for one of the inputs if this widget, the IME is closed. If the user 726 // changed the value via the IME and there is a next input the IME will 727 // be shown, otherwise the user chose another means of changing the 728 // value and having the IME up makes no sense. 729 InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); 730 if (inputMethodManager != null) { 731 if (inputMethodManager.isActive(mYearSpinnerInput)) { 732 mYearSpinnerInput.clearFocus(); 733 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 734 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) { 735 mMonthSpinnerInput.clearFocus(); 736 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 737 } else if (inputMethodManager.isActive(mDaySpinnerInput)) { 738 mDaySpinnerInput.clearFocus(); 739 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 740 } 741 } 742 } 743 744 /** 745 * Class for managing state storing/restoring. 746 */ 747 private static class SavedState extends BaseSavedState { 748 749 private final int mYear; 750 751 private final int mMonth; 752 753 private final int mDay; 754 755 /** 756 * Constructor called from {@link DatePicker#onSaveInstanceState()} 757 */ 758 private SavedState(Parcelable superState, int year, int month, int day) { 759 super(superState); 760 mYear = year; 761 mMonth = month; 762 mDay = day; 763 } 764 765 /** 766 * Constructor called from {@link #CREATOR} 767 */ 768 private SavedState(Parcel in) { 769 super(in); 770 mYear = in.readInt(); 771 mMonth = in.readInt(); 772 mDay = in.readInt(); 773 } 774 775 @Override 776 public void writeToParcel(Parcel dest, int flags) { 777 super.writeToParcel(dest, flags); 778 dest.writeInt(mYear); 779 dest.writeInt(mMonth); 780 dest.writeInt(mDay); 781 } 782 783 @SuppressWarnings("all") 784 // suppress unused and hiding 785 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 786 787 public SavedState createFromParcel(Parcel in) { 788 return new SavedState(in); 789 } 790 791 public SavedState[] newArray(int size) { 792 return new SavedState[size]; 793 } 794 }; 795 } 796 } 797