[go: nahoru, domu]

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