• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.TypedArray;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.text.format.DateFormat;
25 import android.text.format.DateUtils;
26 import android.util.AttributeSet;
27 import android.util.SparseArray;
28 import android.view.LayoutInflater;
29 import android.widget.NumberPicker;
30 import android.widget.NumberPicker.OnChangedListener;
31 
32 import com.android.internal.R;
33 
34 import java.text.DateFormatSymbols;
35 import java.text.SimpleDateFormat;
36 import java.util.Calendar;
37 import java.util.Locale;
38 
39 /**
40  * A view for selecting a month / year / day based on a calendar like layout.
41  *
42  * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date Picker
43  * tutorial</a>.</p>
44  *
45  * For a dialog using this view, see {@link android.app.DatePickerDialog}.
46  */
47 @Widget
48 public class DatePicker extends FrameLayout {
49 
50     private static final int DEFAULT_START_YEAR = 1900;
51     private static final int DEFAULT_END_YEAR = 2100;
52 
53     // This ignores Undecimber, but we only support real Gregorian calendars.
54     private static final int NUMBER_OF_MONTHS = 12;
55 
56     /* UI Components */
57     private final NumberPicker mDayPicker;
58     private final NumberPicker mMonthPicker;
59     private final NumberPicker mYearPicker;
60 
61     /**
62      * How we notify users the date has changed.
63      */
64     private OnDateChangedListener mOnDateChangedListener;
65 
66     private int mDay;
67     private int mMonth;
68     private int mYear;
69 
70     private Object mMonthUpdateLock = new Object();
71     private volatile Locale mMonthLocale;
72     private String[] mShortMonths;
73 
74     /**
75      * The callback used to indicate the user changes the date.
76      */
77     public interface OnDateChangedListener {
78 
79         /**
80          * @param view The view associated with this listener.
81          * @param year The year that was set.
82          * @param monthOfYear The month that was set (0-11) for compatibility
83          *  with {@link java.util.Calendar}.
84          * @param dayOfMonth The day of the month that was set.
85          */
onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth)86         void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
87     }
88 
DatePicker(Context context)89     public DatePicker(Context context) {
90         this(context, null);
91     }
92 
DatePicker(Context context, AttributeSet attrs)93     public DatePicker(Context context, AttributeSet attrs) {
94         this(context, attrs, 0);
95     }
96 
DatePicker(Context context, AttributeSet attrs, int defStyle)97     public DatePicker(Context context, AttributeSet attrs, int defStyle) {
98         super(context, attrs, defStyle);
99 
100         LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
101         inflater.inflate(R.layout.date_picker, this, true);
102 
103         mDayPicker = (NumberPicker) findViewById(R.id.day);
104         mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
105         mDayPicker.setSpeed(100);
106         mDayPicker.setOnChangeListener(new OnChangedListener() {
107             public void onChanged(NumberPicker picker, int oldVal, int newVal) {
108                 mDay = newVal;
109                 notifyDateChanged();
110             }
111         });
112         mMonthPicker = (NumberPicker) findViewById(R.id.month);
113         mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
114         final String[] months = getShortMonths();
115 
116         /*
117          * If the user is in a locale where the month names are numeric,
118          * use just the number instead of the "month" character for
119          * consistency with the other fields.
120          */
121         if (months[0].startsWith("1")) {
122             for (int i = 0; i < months.length; i++) {
123                 months[i] = String.valueOf(i + 1);
124             }
125             mMonthPicker.setRange(1, NUMBER_OF_MONTHS);
126         } else {
127             mMonthPicker.setRange(1, NUMBER_OF_MONTHS, months);
128         }
129 
130         mMonthPicker.setSpeed(200);
131         mMonthPicker.setOnChangeListener(new OnChangedListener() {
132             public void onChanged(NumberPicker picker, int oldVal, int newVal) {
133 
134                 /* We display the month 1-12 but store it 0-11 so always
135                  * subtract by one to ensure our internal state is always 0-11
136                  */
137                 mMonth = newVal - 1;
138                 // Adjust max day of the month
139                 adjustMaxDay();
140                 notifyDateChanged();
141                 updateDaySpinner();
142             }
143         });
144         mYearPicker = (NumberPicker) findViewById(R.id.year);
145         mYearPicker.setSpeed(100);
146         mYearPicker.setOnChangeListener(new OnChangedListener() {
147             public void onChanged(NumberPicker picker, int oldVal, int newVal) {
148                 mYear = newVal;
149                 // Adjust max day for leap years if needed
150                 adjustMaxDay();
151                 notifyDateChanged();
152                 updateDaySpinner();
153             }
154         });
155 
156         // attributes
157         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker);
158 
159         int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR);
160         int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
161         mYearPicker.setRange(mStartYear, mEndYear);
162 
163         a.recycle();
164 
165         // initialize to current date
166         Calendar cal = Calendar.getInstance();
167         init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null);
168 
169         // re-order the number pickers to match the current date format
170         reorderPickers(months);
171 
172         if (!isEnabled()) {
173             setEnabled(false);
174         }
175     }
176 
177     @Override
setEnabled(boolean enabled)178     public void setEnabled(boolean enabled) {
179         super.setEnabled(enabled);
180         mDayPicker.setEnabled(enabled);
181         mMonthPicker.setEnabled(enabled);
182         mYearPicker.setEnabled(enabled);
183     }
184 
reorderPickers(String[] months)185     private void reorderPickers(String[] months) {
186         java.text.DateFormat format;
187         String order;
188 
189         /*
190          * If the user is in a locale where the medium date format is
191          * still numeric (Japanese and Czech, for example), respect
192          * the date format order setting.  Otherwise, use the order
193          * that the locale says is appropriate for a spelled-out date.
194          */
195 
196         if (months[0].startsWith("1")) {
197             format = DateFormat.getDateFormat(getContext());
198         } else {
199             format = DateFormat.getMediumDateFormat(getContext());
200         }
201 
202         if (format instanceof SimpleDateFormat) {
203             order = ((SimpleDateFormat) format).toPattern();
204         } else {
205             // Shouldn't happen, but just in case.
206             order = new String(DateFormat.getDateFormatOrder(getContext()));
207         }
208 
209         /* Remove the 3 pickers from their parent and then add them back in the
210          * required order.
211          */
212         LinearLayout parent = (LinearLayout) findViewById(R.id.parent);
213         parent.removeAllViews();
214 
215         boolean quoted = false;
216         boolean didDay = false, didMonth = false, didYear = false;
217 
218         for (int i = 0; i < order.length(); i++) {
219             char c = order.charAt(i);
220 
221             if (c == '\'') {
222                 quoted = !quoted;
223             }
224 
225             if (!quoted) {
226                 if (c == DateFormat.DATE && !didDay) {
227                     parent.addView(mDayPicker);
228                     didDay = true;
229                 } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) {
230                     parent.addView(mMonthPicker);
231                     didMonth = true;
232                 } else if (c == DateFormat.YEAR && !didYear) {
233                     parent.addView (mYearPicker);
234                     didYear = true;
235                 }
236             }
237         }
238 
239         // Shouldn't happen, but just in case.
240         if (!didMonth) {
241             parent.addView(mMonthPicker);
242         }
243         if (!didDay) {
244             parent.addView(mDayPicker);
245         }
246         if (!didYear) {
247             parent.addView(mYearPicker);
248         }
249     }
250 
updateDate(int year, int monthOfYear, int dayOfMonth)251     public void updateDate(int year, int monthOfYear, int dayOfMonth) {
252         if (mYear != year || mMonth != monthOfYear || mDay != dayOfMonth) {
253             mYear = year;
254             mMonth = monthOfYear;
255             mDay = dayOfMonth;
256             updateSpinners();
257             reorderPickers(getShortMonths());
258             notifyDateChanged();
259         }
260     }
261 
getShortMonths()262     private String[] getShortMonths() {
263         final Locale currentLocale = Locale.getDefault();
264         if (currentLocale.equals(mMonthLocale) && mShortMonths != null) {
265             return mShortMonths;
266         } else {
267             synchronized (mMonthUpdateLock) {
268                 if (!currentLocale.equals(mMonthLocale)) {
269                     mShortMonths = new String[NUMBER_OF_MONTHS];
270                     for (int i = 0; i < NUMBER_OF_MONTHS; i++) {
271                         mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
272                                 DateUtils.LENGTH_MEDIUM);
273                     }
274                     mMonthLocale = currentLocale;
275                 }
276             }
277             return mShortMonths;
278         }
279     }
280 
281     private static class SavedState extends BaseSavedState {
282 
283         private final int mYear;
284         private final int mMonth;
285         private final int mDay;
286 
287         /**
288          * Constructor called from {@link DatePicker#onSaveInstanceState()}
289          */
SavedState(Parcelable superState, int year, int month, int day)290         private SavedState(Parcelable superState, int year, int month, int day) {
291             super(superState);
292             mYear = year;
293             mMonth = month;
294             mDay = day;
295         }
296 
297         /**
298          * Constructor called from {@link #CREATOR}
299          */
SavedState(Parcel in)300         private SavedState(Parcel in) {
301             super(in);
302             mYear = in.readInt();
303             mMonth = in.readInt();
304             mDay = in.readInt();
305         }
306 
getYear()307         public int getYear() {
308             return mYear;
309         }
310 
getMonth()311         public int getMonth() {
312             return mMonth;
313         }
314 
getDay()315         public int getDay() {
316             return mDay;
317         }
318 
319         @Override
writeToParcel(Parcel dest, int flags)320         public void writeToParcel(Parcel dest, int flags) {
321             super.writeToParcel(dest, flags);
322             dest.writeInt(mYear);
323             dest.writeInt(mMonth);
324             dest.writeInt(mDay);
325         }
326 
327         public static final Parcelable.Creator<SavedState> CREATOR =
328                 new Creator<SavedState>() {
329 
330                     public SavedState createFromParcel(Parcel in) {
331                         return new SavedState(in);
332                     }
333 
334                     public SavedState[] newArray(int size) {
335                         return new SavedState[size];
336                     }
337                 };
338     }
339 
340 
341     /**
342      * Override so we are in complete control of save / restore for this widget.
343      */
344     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)345     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
346         dispatchThawSelfOnly(container);
347     }
348 
349     @Override
onSaveInstanceState()350     protected Parcelable onSaveInstanceState() {
351         Parcelable superState = super.onSaveInstanceState();
352 
353         return new SavedState(superState, mYear, mMonth, mDay);
354     }
355 
356     @Override
onRestoreInstanceState(Parcelable state)357     protected void onRestoreInstanceState(Parcelable state) {
358         SavedState ss = (SavedState) state;
359         super.onRestoreInstanceState(ss.getSuperState());
360         mYear = ss.getYear();
361         mMonth = ss.getMonth();
362         mDay = ss.getDay();
363         updateSpinners();
364     }
365 
366     /**
367      * Initialize the state.
368      * @param year The initial year.
369      * @param monthOfYear The initial month.
370      * @param dayOfMonth The initial day of the month.
371      * @param onDateChangedListener How user is notified date is changed by user, can be null.
372      */
init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)373     public void init(int year, int monthOfYear, int dayOfMonth,
374             OnDateChangedListener onDateChangedListener) {
375         mYear = year;
376         mMonth = monthOfYear;
377         mDay = dayOfMonth;
378         mOnDateChangedListener = onDateChangedListener;
379         updateSpinners();
380     }
381 
updateSpinners()382     private void updateSpinners() {
383         updateDaySpinner();
384         mYearPicker.setCurrent(mYear);
385 
386         /* The month display uses 1-12 but our internal state stores it
387          * 0-11 so add one when setting the display.
388          */
389         mMonthPicker.setCurrent(mMonth + 1);
390     }
391 
updateDaySpinner()392     private void updateDaySpinner() {
393         Calendar cal = Calendar.getInstance();
394         cal.set(mYear, mMonth, mDay);
395         int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
396         mDayPicker.setRange(1, max);
397         mDayPicker.setCurrent(mDay);
398     }
399 
getYear()400     public int getYear() {
401         return mYear;
402     }
403 
getMonth()404     public int getMonth() {
405         return mMonth;
406     }
407 
getDayOfMonth()408     public int getDayOfMonth() {
409         return mDay;
410     }
411 
adjustMaxDay()412     private void adjustMaxDay(){
413         Calendar cal = Calendar.getInstance();
414         cal.set(Calendar.YEAR, mYear);
415         cal.set(Calendar.MONTH, mMonth);
416         int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
417         if (mDay > max) {
418             mDay = max;
419         }
420     }
421 
notifyDateChanged()422     private void notifyDateChanged() {
423         if (mOnDateChangedListener != null) {
424             mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay);
425         }
426     }
427 }
428