blob: 1fc23abe1eb2d8e5fad77da49c91a9d8483fb766 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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 android.annotation.Widget;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.text.format.DateFormat;
25import android.util.AttributeSet;
26import android.util.SparseArray;
27import android.view.LayoutInflater;
Paul Westbrook68f2f542010-01-13 12:13:57 -080028import android.widget.NumberPicker;
29import android.widget.NumberPicker.OnChangedListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
31import com.android.internal.R;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
33import java.text.DateFormatSymbols;
Eric Fischer03a80172009-07-23 18:32:42 -070034import java.text.SimpleDateFormat;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import java.util.Calendar;
36
37/**
38 * A view for selecting a month / year / day based on a calendar like layout.
39 *
40 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
41 */
42@Widget
43public class DatePicker extends FrameLayout {
44
45 private static final int DEFAULT_START_YEAR = 1900;
46 private static final int DEFAULT_END_YEAR = 2100;
47
48 /* UI Components */
49 private final NumberPicker mDayPicker;
50 private final NumberPicker mMonthPicker;
51 private final NumberPicker mYearPicker;
52
53 /**
54 * How we notify users the date has changed.
55 */
56 private OnDateChangedListener mOnDateChangedListener;
57
58 private int mDay;
59 private int mMonth;
60 private int mYear;
61
62 /**
63 * The callback used to indicate the user changes the date.
64 */
65 public interface OnDateChangedListener {
66
67 /**
68 * @param view The view associated with this listener.
69 * @param year The year that was set.
70 * @param monthOfYear The month that was set (0-11) for compatibility
71 * with {@link java.util.Calendar}.
72 * @param dayOfMonth The day of the month that was set.
73 */
74 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
75 }
76
77 public DatePicker(Context context) {
78 this(context, null);
79 }
80
81 public DatePicker(Context context, AttributeSet attrs) {
82 this(context, attrs, 0);
83 }
84
85 public DatePicker(Context context, AttributeSet attrs, int defStyle) {
86 super(context, attrs, defStyle);
87
88 LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
89 inflater.inflate(R.layout.date_picker, this, true);
90
91 mDayPicker = (NumberPicker) findViewById(R.id.day);
92 mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
93 mDayPicker.setSpeed(100);
94 mDayPicker.setOnChangeListener(new OnChangedListener() {
95 public void onChanged(NumberPicker picker, int oldVal, int newVal) {
96 mDay = newVal;
Kenneth Anderssone3491b62010-03-05 09:16:24 +010097 notifyDateChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 }
99 });
100 mMonthPicker = (NumberPicker) findViewById(R.id.month);
101 mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
102 DateFormatSymbols dfs = new DateFormatSymbols();
Eric Fischer03a80172009-07-23 18:32:42 -0700103 String[] months = dfs.getShortMonths();
Eric Fischer5fe1e622010-02-09 15:58:50 -0800104
105 /*
106 * If the user is in a locale where the month names are numeric,
107 * use just the number instead of the "month" character for
108 * consistency with the other fields.
109 */
110 if (months[0].startsWith("1")) {
111 for (int i = 0; i < months.length; i++) {
112 months[i] = String.valueOf(i + 1);
113 }
114 }
115
Eric Fischer03a80172009-07-23 18:32:42 -0700116 mMonthPicker.setRange(1, 12, months);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 mMonthPicker.setSpeed(200);
118 mMonthPicker.setOnChangeListener(new OnChangedListener() {
119 public void onChanged(NumberPicker picker, int oldVal, int newVal) {
120
121 /* We display the month 1-12 but store it 0-11 so always
122 * subtract by one to ensure our internal state is always 0-11
123 */
124 mMonth = newVal - 1;
Suchi Amalapurapu2bf761c2009-07-13 16:24:58 -0700125 // Adjust max day of the month
126 adjustMaxDay();
Kenneth Anderssone3491b62010-03-05 09:16:24 +0100127 notifyDateChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 updateDaySpinner();
129 }
130 });
131 mYearPicker = (NumberPicker) findViewById(R.id.year);
132 mYearPicker.setSpeed(100);
133 mYearPicker.setOnChangeListener(new OnChangedListener() {
134 public void onChanged(NumberPicker picker, int oldVal, int newVal) {
135 mYear = newVal;
Suchi Amalapurapu2bf761c2009-07-13 16:24:58 -0700136 // Adjust max day for leap years if needed
137 adjustMaxDay();
Kenneth Anderssone3491b62010-03-05 09:16:24 +0100138 notifyDateChanged();
Suchi Amalapurapu2bf761c2009-07-13 16:24:58 -0700139 updateDaySpinner();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 }
141 });
142
143 // attributes
144 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker);
145
146 int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR);
147 int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
148 mYearPicker.setRange(mStartYear, mEndYear);
149
150 a.recycle();
151
152 // initialize to current date
153 Calendar cal = Calendar.getInstance();
154 init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null);
155
156 // re-order the number pickers to match the current date format
Eric Fischer03a80172009-07-23 18:32:42 -0700157 reorderPickers(months);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158
159 if (!isEnabled()) {
160 setEnabled(false);
161 }
162 }
163
164 @Override
165 public void setEnabled(boolean enabled) {
166 super.setEnabled(enabled);
167 mDayPicker.setEnabled(enabled);
168 mMonthPicker.setEnabled(enabled);
169 mYearPicker.setEnabled(enabled);
170 }
171
Eric Fischer03a80172009-07-23 18:32:42 -0700172 private void reorderPickers(String[] months) {
173 java.text.DateFormat format;
174 String order;
175
176 /*
177 * If the user is in a locale where the medium date format is
178 * still numeric (Japanese and Czech, for example), respect
179 * the date format order setting. Otherwise, use the order
180 * that the locale says is appropriate for a spelled-out date.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 */
Eric Fischer03a80172009-07-23 18:32:42 -0700182
183 if (months[0].startsWith("1")) {
184 format = DateFormat.getDateFormat(getContext());
185 } else {
186 format = DateFormat.getMediumDateFormat(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 }
Eric Fischer03a80172009-07-23 18:32:42 -0700188
189 if (format instanceof SimpleDateFormat) {
190 order = ((SimpleDateFormat) format).toPattern();
191 } else {
192 // Shouldn't happen, but just in case.
193 order = new String(DateFormat.getDateFormatOrder(getContext()));
194 }
195
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 /* Remove the 3 pickers from their parent and then add them back in the
197 * required order.
198 */
199 LinearLayout parent = (LinearLayout) findViewById(R.id.parent);
200 parent.removeAllViews();
Eric Fischer03a80172009-07-23 18:32:42 -0700201
202 boolean quoted = false;
203 boolean didDay = false, didMonth = false, didYear = false;
204
205 for (int i = 0; i < order.length(); i++) {
206 char c = order.charAt(i);
207
208 if (c == '\'') {
209 quoted = !quoted;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 }
Eric Fischer03a80172009-07-23 18:32:42 -0700211
212 if (!quoted) {
213 if (c == DateFormat.DATE && !didDay) {
214 parent.addView(mDayPicker);
215 didDay = true;
216 } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) {
217 parent.addView(mMonthPicker);
218 didMonth = true;
219 } else if (c == DateFormat.YEAR && !didYear) {
220 parent.addView (mYearPicker);
221 didYear = true;
222 }
223 }
224 }
225
226 // Shouldn't happen, but just in case.
227 if (!didMonth) {
228 parent.addView(mMonthPicker);
229 }
230 if (!didDay) {
231 parent.addView(mDayPicker);
232 }
233 if (!didYear) {
234 parent.addView(mYearPicker);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 }
236 }
237
238 public void updateDate(int year, int monthOfYear, int dayOfMonth) {
Kenneth Anderssone3491b62010-03-05 09:16:24 +0100239 if (mYear != year || mMonth != monthOfYear || mDay != dayOfMonth) {
240 mYear = year;
241 mMonth = monthOfYear;
242 mDay = dayOfMonth;
243 updateSpinners();
244 reorderPickers(new DateFormatSymbols().getShortMonths());
245 notifyDateChanged();
246 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 }
248
249 private static class SavedState extends BaseSavedState {
250
251 private final int mYear;
252 private final int mMonth;
253 private final int mDay;
254
255 /**
256 * Constructor called from {@link DatePicker#onSaveInstanceState()}
257 */
258 private SavedState(Parcelable superState, int year, int month, int day) {
259 super(superState);
260 mYear = year;
261 mMonth = month;
262 mDay = day;
263 }
264
265 /**
266 * Constructor called from {@link #CREATOR}
267 */
268 private SavedState(Parcel in) {
269 super(in);
270 mYear = in.readInt();
271 mMonth = in.readInt();
272 mDay = in.readInt();
273 }
274
275 public int getYear() {
276 return mYear;
277 }
278
279 public int getMonth() {
280 return mMonth;
281 }
282
283 public int getDay() {
284 return mDay;
285 }
286
287 @Override
288 public void writeToParcel(Parcel dest, int flags) {
289 super.writeToParcel(dest, flags);
290 dest.writeInt(mYear);
291 dest.writeInt(mMonth);
292 dest.writeInt(mDay);
293 }
294
295 public static final Parcelable.Creator<SavedState> CREATOR =
296 new Creator<SavedState>() {
297
298 public SavedState createFromParcel(Parcel in) {
299 return new SavedState(in);
300 }
301
302 public SavedState[] newArray(int size) {
303 return new SavedState[size];
304 }
305 };
306 }
307
308
309 /**
310 * Override so we are in complete control of save / restore for this widget.
311 */
312 @Override
313 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
314 dispatchThawSelfOnly(container);
315 }
316
317 @Override
318 protected Parcelable onSaveInstanceState() {
319 Parcelable superState = super.onSaveInstanceState();
320
321 return new SavedState(superState, mYear, mMonth, mDay);
322 }
323
324 @Override
325 protected void onRestoreInstanceState(Parcelable state) {
326 SavedState ss = (SavedState) state;
327 super.onRestoreInstanceState(ss.getSuperState());
328 mYear = ss.getYear();
329 mMonth = ss.getMonth();
330 mDay = ss.getDay();
Nicholas Killewald0ba2d472010-05-03 02:17:08 -0400331 updateSpinners();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 }
333
334 /**
335 * Initialize the state.
336 * @param year The initial year.
337 * @param monthOfYear The initial month.
338 * @param dayOfMonth The initial day of the month.
339 * @param onDateChangedListener How user is notified date is changed by user, can be null.
340 */
341 public void init(int year, int monthOfYear, int dayOfMonth,
342 OnDateChangedListener onDateChangedListener) {
343 mYear = year;
344 mMonth = monthOfYear;
345 mDay = dayOfMonth;
346 mOnDateChangedListener = onDateChangedListener;
347 updateSpinners();
348 }
349
350 private void updateSpinners() {
351 updateDaySpinner();
352 mYearPicker.setCurrent(mYear);
353
354 /* The month display uses 1-12 but our internal state stores it
355 * 0-11 so add one when setting the display.
356 */
357 mMonthPicker.setCurrent(mMonth + 1);
358 }
359
360 private void updateDaySpinner() {
361 Calendar cal = Calendar.getInstance();
362 cal.set(mYear, mMonth, mDay);
363 int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
364 mDayPicker.setRange(1, max);
365 mDayPicker.setCurrent(mDay);
366 }
367
368 public int getYear() {
369 return mYear;
370 }
371
372 public int getMonth() {
373 return mMonth;
374 }
375
376 public int getDayOfMonth() {
377 return mDay;
378 }
Suchi Amalapurapu2bf761c2009-07-13 16:24:58 -0700379
380 private void adjustMaxDay(){
381 Calendar cal = Calendar.getInstance();
382 cal.set(Calendar.YEAR, mYear);
383 cal.set(Calendar.MONTH, mMonth);
384 int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
385 if (mDay > max) {
386 mDay = max;
387 }
388 }
Kenneth Anderssone3491b62010-03-05 09:16:24 +0100389
390 private void notifyDateChanged() {
391 if (mOnDateChangedListener != null) {
392 mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay);
393 }
394 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395}