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 17package android.widget; 18 19import com.android.internal.R; 20 21import android.annotation.Nullable; 22import android.annotation.Widget; 23import android.content.Context; 24import android.content.res.Configuration; 25import android.content.res.TypedArray; 26import android.icu.util.Calendar; 27import android.icu.util.TimeZone; 28import android.os.Parcel; 29import android.os.Parcelable; 30import android.util.AttributeSet; 31import android.util.SparseArray; 32import android.view.View; 33import android.view.accessibility.AccessibilityEvent; 34 35import java.util.Locale; 36 37/** 38 * Provides a widget for selecting a date. 39 * <p> 40 * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is 41 * set to {@code spinner}, the date can be selected using year, month, and day 42 * spinners or a {@link CalendarView}. The set of spinners and the calendar 43 * view are automatically synchronized. The client can customize whether only 44 * the spinners, or only the calendar view, or both to be displayed. 45 * </p> 46 * <p> 47 * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is 48 * set to {@code calendar}, the month and day can be selected using a 49 * calendar-style view while the year can be selected separately using a list. 50 * </p> 51 * <p> 52 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a> 53 * guide. 54 * </p> 55 * <p> 56 * For a dialog using this view, see {@link android.app.DatePickerDialog}. 57 * </p> 58 * 59 * @attr ref android.R.styleable#DatePicker_startYear 60 * @attr ref android.R.styleable#DatePicker_endYear 61 * @attr ref android.R.styleable#DatePicker_maxDate 62 * @attr ref android.R.styleable#DatePicker_minDate 63 * @attr ref android.R.styleable#DatePicker_spinnersShown 64 * @attr ref android.R.styleable#DatePicker_calendarViewShown 65 * @attr ref android.R.styleable#DatePicker_dayOfWeekBackground 66 * @attr ref android.R.styleable#DatePicker_dayOfWeekTextAppearance 67 * @attr ref android.R.styleable#DatePicker_headerBackground 68 * @attr ref android.R.styleable#DatePicker_headerMonthTextAppearance 69 * @attr ref android.R.styleable#DatePicker_headerDayOfMonthTextAppearance 70 * @attr ref android.R.styleable#DatePicker_headerYearTextAppearance 71 * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance 72 * @attr ref android.R.styleable#DatePicker_yearListSelectorColor 73 * @attr ref android.R.styleable#DatePicker_calendarTextColor 74 * @attr ref android.R.styleable#DatePicker_datePickerMode 75 */ 76@Widget 77public class DatePicker extends FrameLayout { 78 private static final int MODE_SPINNER = 1; 79 private static final int MODE_CALENDAR = 2; 80 81 private final DatePickerDelegate mDelegate; 82 83 /** 84 * The callback used to indicate the user changed the date. 85 */ 86 public interface OnDateChangedListener { 87 88 /** 89 * Called upon a date change. 90 * 91 * @param view The view associated with this listener. 92 * @param year The year that was set. 93 * @param monthOfYear The month that was set (0-11) for compatibility 94 * with {@link java.util.Calendar}. 95 * @param dayOfMonth The day of the month that was set. 96 */ 97 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); 98 } 99 100 public DatePicker(Context context) { 101 this(context, null); 102 } 103 104 public DatePicker(Context context, AttributeSet attrs) { 105 this(context, attrs, R.attr.datePickerStyle); 106 } 107 108 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) { 109 this(context, attrs, defStyleAttr, 0); 110 } 111 112 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 113 super(context, attrs, defStyleAttr, defStyleRes); 114 115 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker, 116 defStyleAttr, defStyleRes); 117 final int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER); 118 final int firstDayOfWeek = a.getInt(R.styleable.DatePicker_firstDayOfWeek, 0); 119 a.recycle(); 120 121 switch (mode) { 122 case MODE_CALENDAR: 123 mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes); 124 break; 125 case MODE_SPINNER: 126 default: 127 mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes); 128 break; 129 } 130 131 if (firstDayOfWeek != 0) { 132 setFirstDayOfWeek(firstDayOfWeek); 133 } 134 } 135 136 private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs, 137 int defStyleAttr, int defStyleRes) { 138 return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes); 139 } 140 141 private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs, 142 int defStyleAttr, int defStyleRes) { 143 return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr, 144 defStyleRes); 145 } 146 147 /** 148 * Initialize the state. If the provided values designate an inconsistent 149 * date the values are normalized before updating the spinners. 150 * 151 * @param year The initial year. 152 * @param monthOfYear The initial month <strong>starting from zero</strong>. 153 * @param dayOfMonth The initial day of the month. 154 * @param onDateChangedListener How user is notified date is changed by 155 * user, can be null. 156 */ 157 public void init(int year, int monthOfYear, int dayOfMonth, 158 OnDateChangedListener onDateChangedListener) { 159 mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener); 160 } 161 162 /** 163 * Update the current date. 164 * 165 * @param year The year. 166 * @param month The month which is <strong>starting from zero</strong>. 167 * @param dayOfMonth The day of the month. 168 */ 169 public void updateDate(int year, int month, int dayOfMonth) { 170 mDelegate.updateDate(year, month, dayOfMonth); 171 } 172 173 /** 174 * @return The selected year. 175 */ 176 public int getYear() { 177 return mDelegate.getYear(); 178 } 179 180 /** 181 * @return The selected month. 182 */ 183 public int getMonth() { 184 return mDelegate.getMonth(); 185 } 186 187 /** 188 * @return The selected day of month. 189 */ 190 public int getDayOfMonth() { 191 return mDelegate.getDayOfMonth(); 192 } 193 194 /** 195 * Gets the minimal date supported by this {@link DatePicker} in 196 * milliseconds since January 1, 1970 00:00:00 in 197 * {@link TimeZone#getDefault()} time zone. 198 * <p> 199 * Note: The default minimal date is 01/01/1900. 200 * <p> 201 * 202 * @return The minimal supported date. 203 */ 204 public long getMinDate() { 205 return mDelegate.getMinDate().getTimeInMillis(); 206 } 207 208 /** 209 * Sets the minimal date supported by this {@link NumberPicker} in 210 * milliseconds since January 1, 1970 00:00:00 in 211 * {@link TimeZone#getDefault()} time zone. 212 * 213 * @param minDate The minimal supported date. 214 */ 215 public void setMinDate(long minDate) { 216 mDelegate.setMinDate(minDate); 217 } 218 219 /** 220 * Gets the maximal date supported by this {@link DatePicker} in 221 * milliseconds since January 1, 1970 00:00:00 in 222 * {@link TimeZone#getDefault()} time zone. 223 * <p> 224 * Note: The default maximal date is 12/31/2100. 225 * <p> 226 * 227 * @return The maximal supported date. 228 */ 229 public long getMaxDate() { 230 return mDelegate.getMaxDate().getTimeInMillis(); 231 } 232 233 /** 234 * Sets the maximal date supported by this {@link DatePicker} in 235 * milliseconds since January 1, 1970 00:00:00 in 236 * {@link TimeZone#getDefault()} time zone. 237 * 238 * @param maxDate The maximal supported date. 239 */ 240 public void setMaxDate(long maxDate) { 241 mDelegate.setMaxDate(maxDate); 242 } 243 244 /** 245 * Sets the callback that indicates the current date is valid. 246 * 247 * @param callback the callback, may be null 248 * @hide 249 */ 250 public void setValidationCallback(@Nullable ValidationCallback callback) { 251 mDelegate.setValidationCallback(callback); 252 } 253 254 @Override 255 public void setEnabled(boolean enabled) { 256 if (mDelegate.isEnabled() == enabled) { 257 return; 258 } 259 super.setEnabled(enabled); 260 mDelegate.setEnabled(enabled); 261 } 262 263 @Override 264 public boolean isEnabled() { 265 return mDelegate.isEnabled(); 266 } 267 268 /** @hide */ 269 @Override 270 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 271 return mDelegate.dispatchPopulateAccessibilityEvent(event); 272 } 273 274 /** @hide */ 275 @Override 276 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 277 super.onPopulateAccessibilityEventInternal(event); 278 mDelegate.onPopulateAccessibilityEvent(event); 279 } 280 281 @Override 282 public CharSequence getAccessibilityClassName() { 283 return DatePicker.class.getName(); 284 } 285 286 @Override 287 protected void onConfigurationChanged(Configuration newConfig) { 288 super.onConfigurationChanged(newConfig); 289 mDelegate.onConfigurationChanged(newConfig); 290 } 291 292 /** 293 * Sets the first day of week. 294 * 295 * @param firstDayOfWeek The first day of the week conforming to the 296 * {@link CalendarView} APIs. 297 * @see Calendar#SUNDAY 298 * @see Calendar#MONDAY 299 * @see Calendar#TUESDAY 300 * @see Calendar#WEDNESDAY 301 * @see Calendar#THURSDAY 302 * @see Calendar#FRIDAY 303 * @see Calendar#SATURDAY 304 * 305 * @attr ref android.R.styleable#DatePicker_firstDayOfWeek 306 */ 307 public void setFirstDayOfWeek(int firstDayOfWeek) { 308 if (firstDayOfWeek < Calendar.SUNDAY || firstDayOfWeek > Calendar.SATURDAY) { 309 throw new IllegalArgumentException("firstDayOfWeek must be between 1 and 7"); 310 } 311 mDelegate.setFirstDayOfWeek(firstDayOfWeek); 312 } 313 314 /** 315 * Gets the first day of week. 316 * 317 * @return The first day of the week conforming to the {@link CalendarView} 318 * APIs. 319 * @see Calendar#SUNDAY 320 * @see Calendar#MONDAY 321 * @see Calendar#TUESDAY 322 * @see Calendar#WEDNESDAY 323 * @see Calendar#THURSDAY 324 * @see Calendar#FRIDAY 325 * @see Calendar#SATURDAY 326 * 327 * @attr ref android.R.styleable#DatePicker_firstDayOfWeek 328 */ 329 public int getFirstDayOfWeek() { 330 return mDelegate.getFirstDayOfWeek(); 331 } 332 333 /** 334 * Returns whether the {@link CalendarView} is shown. 335 * <p> 336 * <strong>Note:</strong> This method returns {@code false} when the 337 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 338 * to {@code calendar}. 339 * 340 * @return {@code true} if the calendar view is shown 341 * @see #getCalendarView() 342 * @deprecated Not supported by Material-style {@code calendar} mode 343 */ 344 @Deprecated 345 public boolean getCalendarViewShown() { 346 return mDelegate.getCalendarViewShown(); 347 } 348 349 /** 350 * Returns the {@link CalendarView} used by this picker. 351 * <p> 352 * <strong>Note:</strong> This method throws an 353 * {@link UnsupportedOperationException} when the 354 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 355 * to {@code calendar}. 356 * 357 * @return the calendar view 358 * @see #getCalendarViewShown() 359 * @deprecated Not supported by Material-style {@code calendar} mode 360 * @throws UnsupportedOperationException if called when the picker is 361 * displayed in {@code calendar} mode 362 */ 363 @Deprecated 364 public CalendarView getCalendarView() { 365 return mDelegate.getCalendarView(); 366 } 367 368 /** 369 * Sets whether the {@link CalendarView} is shown. 370 * <p> 371 * <strong>Note:</strong> Calling this method has no effect when the 372 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 373 * to {@code calendar}. 374 * 375 * @param shown {@code true} to show the calendar view, {@code false} to 376 * hide it 377 * @deprecated Not supported by Material-style {@code calendar} mode 378 */ 379 @Deprecated 380 public void setCalendarViewShown(boolean shown) { 381 mDelegate.setCalendarViewShown(shown); 382 } 383 384 /** 385 * Returns whether the spinners are shown. 386 * <p> 387 * <strong>Note:</strong> his method returns {@code false} when the 388 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 389 * to {@code calendar}. 390 * 391 * @return {@code true} if the spinners are shown 392 * @deprecated Not supported by Material-style {@code calendar} mode 393 */ 394 @Deprecated 395 public boolean getSpinnersShown() { 396 return mDelegate.getSpinnersShown(); 397 } 398 399 /** 400 * Sets whether the spinners are shown. 401 * <p> 402 * Calling this method has no effect when the 403 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 404 * to {@code calendar}. 405 * 406 * @param shown {@code true} to show the spinners, {@code false} to hide 407 * them 408 * @deprecated Not supported by Material-style {@code calendar} mode 409 */ 410 @Deprecated 411 public void setSpinnersShown(boolean shown) { 412 mDelegate.setSpinnersShown(shown); 413 } 414 415 @Override 416 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 417 dispatchThawSelfOnly(container); 418 } 419 420 @Override 421 protected Parcelable onSaveInstanceState() { 422 Parcelable superState = super.onSaveInstanceState(); 423 return mDelegate.onSaveInstanceState(superState); 424 } 425 426 @Override 427 protected void onRestoreInstanceState(Parcelable state) { 428 BaseSavedState ss = (BaseSavedState) state; 429 super.onRestoreInstanceState(ss.getSuperState()); 430 mDelegate.onRestoreInstanceState(ss); 431 } 432 433 /** 434 * A delegate interface that defined the public API of the DatePicker. Allows different 435 * DatePicker implementations. This would need to be implemented by the DatePicker delegates 436 * for the real behavior. 437 * 438 * @hide 439 */ 440 interface DatePickerDelegate { 441 void init(int year, int monthOfYear, int dayOfMonth, 442 OnDateChangedListener onDateChangedListener); 443 444 void updateDate(int year, int month, int dayOfMonth); 445 446 int getYear(); 447 int getMonth(); 448 int getDayOfMonth(); 449 450 void setFirstDayOfWeek(int firstDayOfWeek); 451 int getFirstDayOfWeek(); 452 453 void setMinDate(long minDate); 454 Calendar getMinDate(); 455 456 void setMaxDate(long maxDate); 457 Calendar getMaxDate(); 458 459 void setEnabled(boolean enabled); 460 boolean isEnabled(); 461 462 CalendarView getCalendarView(); 463 464 void setCalendarViewShown(boolean shown); 465 boolean getCalendarViewShown(); 466 467 void setSpinnersShown(boolean shown); 468 boolean getSpinnersShown(); 469 470 void setValidationCallback(ValidationCallback callback); 471 472 void onConfigurationChanged(Configuration newConfig); 473 474 Parcelable onSaveInstanceState(Parcelable superState); 475 void onRestoreInstanceState(Parcelable state); 476 477 boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); 478 void onPopulateAccessibilityEvent(AccessibilityEvent event); 479 } 480 481 /** 482 * An abstract class which can be used as a start for DatePicker implementations 483 */ 484 abstract static class AbstractDatePickerDelegate implements DatePickerDelegate { 485 // The delegator 486 protected DatePicker mDelegator; 487 488 // The context 489 protected Context mContext; 490 491 // The current locale 492 protected Locale mCurrentLocale; 493 494 // Callbacks 495 protected OnDateChangedListener mOnDateChangedListener; 496 protected ValidationCallback mValidationCallback; 497 498 public AbstractDatePickerDelegate(DatePicker delegator, Context context) { 499 mDelegator = delegator; 500 mContext = context; 501 502 setCurrentLocale(Locale.getDefault()); 503 } 504 505 protected void setCurrentLocale(Locale locale) { 506 if (!locale.equals(mCurrentLocale)) { 507 mCurrentLocale = locale; 508 onLocaleChanged(locale); 509 } 510 } 511 512 @Override 513 public void setValidationCallback(ValidationCallback callback) { 514 mValidationCallback = callback; 515 } 516 517 protected void onValidationChanged(boolean valid) { 518 if (mValidationCallback != null) { 519 mValidationCallback.onValidationChanged(valid); 520 } 521 } 522 523 protected void onLocaleChanged(Locale locale) { 524 // Stub. 525 } 526 527 /** 528 * Class for managing state storing/restoring. 529 */ 530 static class SavedState extends View.BaseSavedState { 531 private final int mSelectedYear; 532 private final int mSelectedMonth; 533 private final int mSelectedDay; 534 private final long mMinDate; 535 private final long mMaxDate; 536 private final int mCurrentView; 537 private final int mListPosition; 538 private final int mListPositionOffset; 539 540 public SavedState(Parcelable superState, int year, int month, int day, long minDate, 541 long maxDate) { 542 this(superState, year, month, day, minDate, maxDate, 0, 0, 0); 543 } 544 545 /** 546 * Constructor called from {@link DatePicker#onSaveInstanceState()} 547 */ 548 public SavedState(Parcelable superState, int year, int month, int day, long minDate, 549 long maxDate, int currentView, int listPosition, int listPositionOffset) { 550 super(superState); 551 mSelectedYear = year; 552 mSelectedMonth = month; 553 mSelectedDay = day; 554 mMinDate = minDate; 555 mMaxDate = maxDate; 556 mCurrentView = currentView; 557 mListPosition = listPosition; 558 mListPositionOffset = listPositionOffset; 559 } 560 561 /** 562 * Constructor called from {@link #CREATOR} 563 */ 564 private SavedState(Parcel in) { 565 super(in); 566 mSelectedYear = in.readInt(); 567 mSelectedMonth = in.readInt(); 568 mSelectedDay = in.readInt(); 569 mMinDate = in.readLong(); 570 mMaxDate = in.readLong(); 571 mCurrentView = in.readInt(); 572 mListPosition = in.readInt(); 573 mListPositionOffset = in.readInt(); 574 } 575 576 @Override 577 public void writeToParcel(Parcel dest, int flags) { 578 super.writeToParcel(dest, flags); 579 dest.writeInt(mSelectedYear); 580 dest.writeInt(mSelectedMonth); 581 dest.writeInt(mSelectedDay); 582 dest.writeLong(mMinDate); 583 dest.writeLong(mMaxDate); 584 dest.writeInt(mCurrentView); 585 dest.writeInt(mListPosition); 586 dest.writeInt(mListPositionOffset); 587 } 588 589 public int getSelectedDay() { 590 return mSelectedDay; 591 } 592 593 public int getSelectedMonth() { 594 return mSelectedMonth; 595 } 596 597 public int getSelectedYear() { 598 return mSelectedYear; 599 } 600 601 public long getMinDate() { 602 return mMinDate; 603 } 604 605 public long getMaxDate() { 606 return mMaxDate; 607 } 608 609 public int getCurrentView() { 610 return mCurrentView; 611 } 612 613 public int getListPosition() { 614 return mListPosition; 615 } 616 617 public int getListPositionOffset() { 618 return mListPositionOffset; 619 } 620 621 @SuppressWarnings("all") 622 // suppress unused and hiding 623 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 624 625 public SavedState createFromParcel(Parcel in) { 626 return new SavedState(in); 627 } 628 629 public SavedState[] newArray(int size) { 630 return new SavedState[size]; 631 } 632 }; 633 } 634 } 635 636 /** 637 * A callback interface for updating input validity when the date picker 638 * when included into a dialog. 639 * 640 * @hide 641 */ 642 public interface ValidationCallback { 643 void onValidationChanged(boolean valid); 644 } 645} 646