blob: 1d442dbbdc82b3bb3c91ac4f41919984eb82368f [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
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080019import com.android.internal.R;
20
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.annotation.Widget;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.os.Parcel;
25import android.os.Parcelable;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080026import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.text.format.DateFormat;
Kenny Rootdddda8d2010-11-15 14:38:51 -080028import android.text.format.DateUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.util.AttributeSet;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080030import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.util.SparseArray;
32import android.view.LayoutInflater;
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -080033import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganovcedc4462011-01-19 19:25:46 -080034import android.widget.NumberPicker.OnValueChangeListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080036import java.text.ParseException;
Eric Fischer03a80172009-07-23 18:32:42 -070037import java.text.SimpleDateFormat;
Svetoslav Ganov156f2092011-01-24 02:13:36 -080038import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import java.util.Calendar;
Kenny Rootdddda8d2010-11-15 14:38:51 -080040import java.util.Locale;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080041import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042
43/**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080044 * This class is a widget for selecting a date. The date can be selected by a
45 * year, month, and day spinners or a {@link CalendarView}. The set of spinners
46 * and the calendar view are automatically synchronized. The client can
47 * customize whether only the spinners, or only the calendar view, or both to be
48 * displayed. Also the minimal and maximal date from which dates to be selected
49 * can be customized.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080050 * <p>
Dirk Dougherty9a143e62011-01-17 20:09:46 -080051 * See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date
52 * Picker tutorial</a>.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080053 * </p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080054 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080056 * </p>
57 *
58 * @attr ref android.R.styleable#DatePicker_startYear
59 * @attr ref android.R.styleable#DatePicker_endYear
60 * @attr ref android.R.styleable#DatePicker_maxDate
61 * @attr ref android.R.styleable#DatePicker_minDate
62 * @attr ref android.R.styleable#DatePicker_spinnersShown
63 * @attr ref android.R.styleable#DatePicker_calendarViewShown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 */
65@Widget
66public class DatePicker extends FrameLayout {
67
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080068 private static final String LOG_TAG = DatePicker.class.getSimpleName();
69
70 private static final String DATE_FORMAT = "MM/dd/yyyy";
71
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 private static final int DEFAULT_START_YEAR = 1900;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080073
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 private static final int DEFAULT_END_YEAR = 2100;
Kenny Rootdddda8d2010-11-15 14:38:51 -080075
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080076 private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080077
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080078 private static final boolean DEFAULT_SPINNERS_SHOWN = true;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080079
Svetoslav Ganov25f84f32010-12-29 22:39:54 -080080 private static final boolean DEFAULT_ENABLED_STATE = true;
81
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080082 private final NumberPicker mDaySpinner;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080084 private final LinearLayout mSpinners;
85
86 private final NumberPicker mMonthSpinner;
87
88 private final NumberPicker mYearSpinner;
89
90 private final CalendarView mCalendarView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080092 private OnDateChangedListener mOnDateChangedListener;
93
94 private Locale mMonthLocale;
95
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080096 private final Calendar mTempDate = Calendar.getInstance();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080097
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080098 private final int mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080099
100 private final String[] mShortMonths = new String[mNumberOfMonths];
Kenny Rootdddda8d2010-11-15 14:38:51 -0800101
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800102 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
103
104 private final Calendar mMinDate = Calendar.getInstance();
105
106 private final Calendar mMaxDate = Calendar.getInstance();
107
108 private final Calendar mCurrentDate = Calendar.getInstance();
109
Svetoslav Ganov25f84f32010-12-29 22:39:54 -0800110 private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800111
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800113 * The callback used to indicate the user changes\d the date.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 */
115 public interface OnDateChangedListener {
116
117 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800118 * Called upon a date change.
119 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 * @param view The view associated with this listener.
121 * @param year The year that was set.
122 * @param monthOfYear The month that was set (0-11) for compatibility
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800123 * with {@link java.util.Calendar}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 * @param dayOfMonth The day of the month that was set.
125 */
126 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
127 }
128
129 public DatePicker(Context context) {
130 this(context, null);
131 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800132
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 public DatePicker(Context context, AttributeSet attrs) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800134 this(context, attrs, R.attr.datePickerStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 }
136
137 public DatePicker(Context context, AttributeSet attrs, int defStyle) {
138 super(context, attrs, defStyle);
139
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800140 TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
141 defStyle, 0);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800142 boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
143 DEFAULT_SPINNERS_SHOWN);
144 boolean calendarViewShown = attributesArray.getBoolean(
145 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800146 int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
147 DEFAULT_START_YEAR);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800148 int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
149 String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
150 String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800151 int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_layout,
152 R.layout.date_picker);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800153 attributesArray.recycle();
154
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800155 LayoutInflater inflater = (LayoutInflater) context
156 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800157 inflater.inflate(layoutResourceId, this, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800159 OnValueChangeListener onChangeListener = new OnValueChangeListener() {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800160 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800161 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
162 // take care of wrapping of days and months to update greater fields
163 if (picker == mDaySpinner) {
164 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
165 if (oldVal == maxDayOfMonth && newVal == 1) {
166 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
167 } else if (oldVal == 1 && newVal == maxDayOfMonth) {
168 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
169 } else {
170 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
171 }
172 } else if (picker == mMonthSpinner) {
173 if (oldVal == 11 && newVal == 0) {
174 mTempDate.add(Calendar.MONTH, 1);
175 } else if (oldVal == 0 && newVal == 11) {
176 mTempDate.add(Calendar.MONTH, -1);
177 } else {
178 mTempDate.add(Calendar.MONTH, newVal - oldVal);
179 }
180 } else if (picker == mYearSpinner) {
181 mTempDate.set(Calendar.YEAR, newVal);
182 } else {
183 throw new IllegalArgumentException();
184 }
185 // now set the date to the adjusted one
186 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
187 mTempDate.get(Calendar.DAY_OF_MONTH));
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800188 updateSpinners();
189 updateCalendarView();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800190 notifyDateChanged();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800191 }
192 };
193
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800194 mSpinners = (LinearLayout) findViewById(R.id.pickers);
195
196 // calendar view day-picker
197 mCalendarView = (CalendarView) findViewById(R.id.calendar_view);
198 mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
199 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800200 setDate(year, month, monthDay);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800201 updateSpinners();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800202 notifyDateChanged();
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800203 }
204 });
205
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800206 // day
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800207 mDaySpinner = (NumberPicker) findViewById(R.id.day);
208 mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
209 mDaySpinner.setOnLongPressUpdateInterval(100);
210 mDaySpinner.setOnValueChangedListener(onChangeListener);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800211
212 // month
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800213 mMonthSpinner = (NumberPicker) findViewById(R.id.month);
214 mMonthSpinner.setMinValue(0);
215 mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
216 mMonthSpinner.setDisplayedValues(getShortMonths());
217 mMonthSpinner.setOnLongPressUpdateInterval(200);
218 mMonthSpinner.setOnValueChangedListener(onChangeListener);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800219
220 // year
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800221 mYearSpinner = (NumberPicker) findViewById(R.id.year);
222 mYearSpinner.setOnLongPressUpdateInterval(100);
223 mYearSpinner.setOnValueChangedListener(onChangeListener);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800224
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800225 // show only what the user required but make sure we
226 // show something and the spinners have higher priority
227 if (!spinnersShown && !calendarViewShown) {
228 setSpinnersShown(true);
229 } else {
230 setSpinnersShown(spinnersShown);
231 setCalendarViewShown(calendarViewShown);
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800232 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800233
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800234 // set the min date giving priority of the minDate over startYear
235 mTempDate.clear();
236 if (!TextUtils.isEmpty(minDate)) {
237 if (!parseDate(minDate, mTempDate)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800238 mTempDate.set(startYear, 0, 1);
239 }
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800240 } else {
241 mTempDate.set(startYear, 0, 1);
242 }
243 setMinDate(mTempDate.getTimeInMillis());
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800244
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800245 // set the max date giving priority of the maxDate over endYear
246 mTempDate.clear();
247 if (!TextUtils.isEmpty(maxDate)) {
248 if (!parseDate(maxDate, mTempDate)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800249 mTempDate.set(endYear, 11, 31);
250 }
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800251 } else {
252 mTempDate.set(endYear, 11, 31);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800253 }
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800254 setMaxDate(mTempDate.getTimeInMillis());
255
256 // initialize to current date
257 mCurrentDate.setTimeInMillis(System.currentTimeMillis());
258 init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
259 .get(Calendar.DAY_OF_MONTH), null);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800260
261 // re-order the number spinners to match the current date format
262 reorderSpinners();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800264
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800265 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800266 * Gets the minimal date supported by this {@link DatePicker} in
267 * milliseconds since January 1, 1970 00:00:00 in
268 * {@link TimeZone#getDefault()} time zone.
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800269 * <p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800270 * Note: The default minimal date is 01/01/1900.
271 * <p>
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800272 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800273 * @return The minimal supported date.
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800274 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800275 public long getMinDate() {
276 return mCalendarView.getMinDate();
277 }
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800278
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800279 /**
280 * Sets the minimal date supported by this {@link NumberPicker} in
281 * milliseconds since January 1, 1970 00:00:00 in
282 * {@link TimeZone#getDefault()} time zone.
283 *
284 * @param minDate The minimal supported date.
285 */
286 public void setMinDate(long minDate) {
287 mTempDate.setTimeInMillis(minDate);
288 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
289 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
290 return;
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800291 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800292 mMinDate.setTimeInMillis(minDate);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800293 mCalendarView.setMinDate(minDate);
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800294 if (mCurrentDate.before(mMinDate)) {
295 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
296 updateCalendarView();
297 }
298 updateSpinners();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800299 }
300
301 /**
302 * Gets the maximal date supported by this {@link DatePicker} in
303 * milliseconds since January 1, 1970 00:00:00 in
304 * {@link TimeZone#getDefault()} time zone.
305 * <p>
306 * Note: The default maximal date is 12/31/2100.
307 * <p>
308 *
309 * @return The maximal supported date.
310 */
311 public long getMaxDate() {
312 return mCalendarView.getMaxDate();
313 }
314
315 /**
316 * Sets the maximal date supported by this {@link DatePicker} in
317 * milliseconds since January 1, 1970 00:00:00 in
318 * {@link TimeZone#getDefault()} time zone.
319 *
320 * @param maxDate The maximal supported date.
321 */
322 public void setMaxDate(long maxDate) {
323 mTempDate.setTimeInMillis(maxDate);
324 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
325 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
326 return;
327 }
328 mMaxDate.setTimeInMillis(maxDate);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800329 mCalendarView.setMaxDate(maxDate);
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800330 if (mCurrentDate.after(mMaxDate)) {
331 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
332 updateCalendarView();
333 }
334 updateSpinners();
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800335 }
336
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 @Override
338 public void setEnabled(boolean enabled) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800339 if (mIsEnabled == enabled) {
340 return;
341 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 super.setEnabled(enabled);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800343 mDaySpinner.setEnabled(enabled);
344 mMonthSpinner.setEnabled(enabled);
345 mYearSpinner.setEnabled(enabled);
346 mCalendarView.setEnabled(enabled);
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800347 mIsEnabled = enabled;
348 }
349
350 @Override
351 public boolean isEnabled() {
352 return mIsEnabled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 }
354
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800355 @Override
356 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
357 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
358 | DateUtils.FORMAT_SHOW_YEAR;
359 String selectedDateUtterance = DateUtils.formatDateTime(mContext,
360 mCurrentDate.getTimeInMillis(), flags);
361 event.getText().add(selectedDateUtterance);
362 return true;
363 }
364
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800365 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800366 * Gets whether the {@link CalendarView} is shown.
367 *
368 * @return True if the calendar view is shown.
Svetoslav Ganov5f3f6ce2011-02-24 15:36:13 -0800369 * @see #getCalendarView()
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800370 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800371 public boolean getCalendarViewShown() {
372 return mCalendarView.isShown();
373 }
374
375 /**
Svetoslav Ganov5f3f6ce2011-02-24 15:36:13 -0800376 * Gets the {@link CalendarView}.
377 *
378 * @return The calendar view.
379 * @see #getCalendarViewShown()
380 */
381 public CalendarView getCalendarView () {
382 return mCalendarView;
383 }
384
385 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800386 * Sets whether the {@link CalendarView} is shown.
387 *
388 * @param shown True if the calendar view is to be shown.
389 */
390 public void setCalendarViewShown(boolean shown) {
391 mCalendarView.setVisibility(shown ? VISIBLE : GONE);
392 }
393
394 /**
395 * Gets whether the spinners are shown.
396 *
397 * @return True if the spinners are shown.
398 */
399 public boolean getSpinnersShown() {
400 return mSpinners.isShown();
401 }
402
403 /**
404 * Sets whether the spinners are shown.
405 *
406 * @param shown True if the spinners are to be shown.
407 */
408 public void setSpinnersShown(boolean shown) {
409 mSpinners.setVisibility(shown ? VISIBLE : GONE);
410 }
411
412 /**
413 * Reorders the spinners according to the date format in the current
414 * {@link Locale}.
415 */
416 private void reorderSpinners() {
Eric Fischer03a80172009-07-23 18:32:42 -0700417 java.text.DateFormat format;
418 String order;
419
420 /*
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800421 * If the user is in a locale where the medium date format is still
422 * numeric (Japanese and Czech, for example), respect the date format
423 * order setting. Otherwise, use the order that the locale says is
424 * appropriate for a spelled-out date.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 */
Eric Fischer03a80172009-07-23 18:32:42 -0700426
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800427 if (getShortMonths()[0].startsWith("1")) {
Eric Fischer03a80172009-07-23 18:32:42 -0700428 format = DateFormat.getDateFormat(getContext());
429 } else {
430 format = DateFormat.getMediumDateFormat(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 }
Eric Fischer03a80172009-07-23 18:32:42 -0700432
433 if (format instanceof SimpleDateFormat) {
434 order = ((SimpleDateFormat) format).toPattern();
435 } else {
436 // Shouldn't happen, but just in case.
437 order = new String(DateFormat.getDateFormatOrder(getContext()));
438 }
439
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800440 /*
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800441 * Remove the 3 spinners from their parent and then add them back in the
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 * required order.
443 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800444 LinearLayout parent = mSpinners;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 parent.removeAllViews();
Eric Fischer03a80172009-07-23 18:32:42 -0700446
447 boolean quoted = false;
448 boolean didDay = false, didMonth = false, didYear = false;
449
450 for (int i = 0; i < order.length(); i++) {
451 char c = order.charAt(i);
452
453 if (c == '\'') {
454 quoted = !quoted;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 }
Eric Fischer03a80172009-07-23 18:32:42 -0700456
457 if (!quoted) {
458 if (c == DateFormat.DATE && !didDay) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800459 parent.addView(mDaySpinner);
Eric Fischer03a80172009-07-23 18:32:42 -0700460 didDay = true;
461 } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800462 parent.addView(mMonthSpinner);
Eric Fischer03a80172009-07-23 18:32:42 -0700463 didMonth = true;
464 } else if (c == DateFormat.YEAR && !didYear) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800465 parent.addView(mYearSpinner);
Eric Fischer03a80172009-07-23 18:32:42 -0700466 didYear = true;
467 }
468 }
469 }
470
471 // Shouldn't happen, but just in case.
472 if (!didMonth) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800473 parent.addView(mMonthSpinner);
Eric Fischer03a80172009-07-23 18:32:42 -0700474 }
475 if (!didDay) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800476 parent.addView(mDaySpinner);
Eric Fischer03a80172009-07-23 18:32:42 -0700477 }
478 if (!didYear) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800479 parent.addView(mYearSpinner);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 }
481 }
482
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800483 /**
484 * Updates the current date.
485 *
486 * @param year The year.
487 * @param month The month which is <strong>starting from zero</strong>.
488 * @param dayOfMonth The day of the month.
489 */
490 public void updateDate(int year, int month, int dayOfMonth) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800491 if (!isNewDate(year, month, dayOfMonth)) {
492 return;
Kenny Rootdddda8d2010-11-15 14:38:51 -0800493 }
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800494 setDate(year, month, dayOfMonth);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800495 updateSpinners();
496 updateCalendarView();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800497 notifyDateChanged();
Kenny Rootdddda8d2010-11-15 14:38:51 -0800498 }
499
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800500 // Override so we are in complete control of save / restore for this widget.
501 @Override
502 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
503 dispatchThawSelfOnly(container);
504 }
505
506 @Override
507 protected Parcelable onSaveInstanceState() {
508 Parcelable superState = super.onSaveInstanceState();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800509 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800510 }
511
512 @Override
513 protected void onRestoreInstanceState(Parcelable state) {
514 SavedState ss = (SavedState) state;
515 super.onRestoreInstanceState(ss.getSuperState());
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800516 setDate(ss.mYear, ss.mMonth, ss.mDay);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800517 updateSpinners();
518 updateCalendarView();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800519 }
520
521 /**
522 * Initialize the state. If the provided values designate an inconsistent
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800523 * date the values are normalized before updating the spinners.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800524 *
525 * @param year The initial year.
526 * @param monthOfYear The initial month <strong>starting from zero</strong>.
527 * @param dayOfMonth The initial day of the month.
528 * @param onDateChangedListener How user is notified date is changed by
529 * user, can be null.
530 */
531 public void init(int year, int monthOfYear, int dayOfMonth,
532 OnDateChangedListener onDateChangedListener) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800533 setDate(year, monthOfYear, dayOfMonth);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800534 updateSpinners();
535 updateCalendarView();
Svetoslav Ganov2f136a82011-01-13 11:33:05 -0800536 mOnDateChangedListener = onDateChangedListener;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800537 }
538
539 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800540 * Parses the given <code>date</code> and in case of success sets the result
541 * to the <code>outDate</code>.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800542 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800543 * @return True if the date was parsed.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800544 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800545 private boolean parseDate(String date, Calendar outDate) {
546 try {
547 outDate.setTime(mDateFormat.parse(date));
548 return true;
549 } catch (ParseException e) {
550 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
551 return false;
552 }
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800553 }
554
555 /**
556 * @return The short month abbreviations.
557 */
558 private String[] getShortMonths() {
559 final Locale currentLocale = Locale.getDefault();
560 if (currentLocale.equals(mMonthLocale)) {
561 return mShortMonths;
562 } else {
563 for (int i = 0; i < mNumberOfMonths; i++) {
564 mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
565 DateUtils.LENGTH_MEDIUM);
566 }
567 mMonthLocale = currentLocale;
568 return mShortMonths;
569 }
570 }
571
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800572 private boolean isNewDate(int year, int month, int dayOfMonth) {
573 return (mCurrentDate.get(Calendar.YEAR) != year
574 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
575 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
576 }
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800577
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800578 private void setDate(int year, int month, int dayOfMonth) {
579 mCurrentDate.set(year, month, dayOfMonth);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800580 if (mCurrentDate.before(mMinDate)) {
581 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
582 } else if (mCurrentDate.after(mMaxDate)) {
583 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
584 }
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800585 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800586
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800587 private void updateSpinners() {
588 // set the spinner ranges respecting the min and max dates
589 if (mCurrentDate.equals(mMinDate)) {
590 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
591 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
592 mDaySpinner.setWrapSelectorWheel(false);
593 mMonthSpinner.setDisplayedValues(null);
594 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
595 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
596 mMonthSpinner.setWrapSelectorWheel(false);
597 } else if (mCurrentDate.equals(mMaxDate)) {
598 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
599 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
600 mDaySpinner.setWrapSelectorWheel(false);
601 mMonthSpinner.setDisplayedValues(null);
602 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
603 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
604 mMonthSpinner.setWrapSelectorWheel(false);
605 } else {
606 mDaySpinner.setMinValue(1);
607 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
608 mDaySpinner.setWrapSelectorWheel(true);
609 mMonthSpinner.setDisplayedValues(null);
610 mMonthSpinner.setMinValue(0);
611 mMonthSpinner.setMaxValue(11);
612 mMonthSpinner.setWrapSelectorWheel(true);
613 }
614
615 // make sure the month names are a zero based array
616 // with the months in the month spinner
617 String[] displayedValues = Arrays.copyOfRange(getShortMonths(),
618 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
619 mMonthSpinner.setDisplayedValues(displayedValues);
620
621 // year spinner range does not change based on the current date
622 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
623 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
624 mYearSpinner.setWrapSelectorWheel(false);
625
626 // set the spinner values
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800627 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
628 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800629 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800630 }
631
632 /**
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800633 * Updates the calendar view with the current date.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800634 */
635 private void updateCalendarView() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800636 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800637 }
638
639 /**
640 * @return The selected year.
641 */
642 public int getYear() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800643 return mCurrentDate.get(Calendar.YEAR);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800644 }
645
646 /**
647 * @return The selected month.
648 */
649 public int getMonth() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800650 return mCurrentDate.get(Calendar.MONTH);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800651 }
652
653 /**
654 * @return The selected day of month.
655 */
656 public int getDayOfMonth() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800657 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800658 }
659
660 /**
661 * Notifies the listener, if such, for a change in the selected date.
662 */
663 private void notifyDateChanged() {
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800664 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800665 if (mOnDateChangedListener != null) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800666 mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800667 }
668 }
669
670 /**
671 * Class for managing state storing/restoring.
672 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 private static class SavedState extends BaseSavedState {
674
675 private final int mYear;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800676
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677 private final int mMonth;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800678
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 private final int mDay;
680
681 /**
682 * Constructor called from {@link DatePicker#onSaveInstanceState()}
683 */
684 private SavedState(Parcelable superState, int year, int month, int day) {
685 super(superState);
686 mYear = year;
687 mMonth = month;
688 mDay = day;
689 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800690
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691 /**
692 * Constructor called from {@link #CREATOR}
693 */
694 private SavedState(Parcel in) {
695 super(in);
696 mYear = in.readInt();
697 mMonth = in.readInt();
698 mDay = in.readInt();
699 }
700
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 @Override
702 public void writeToParcel(Parcel dest, int flags) {
703 super.writeToParcel(dest, flags);
704 dest.writeInt(mYear);
705 dest.writeInt(mMonth);
706 dest.writeInt(mDay);
707 }
708
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800709 @SuppressWarnings("all")
710 // suppress unused and hiding
711 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800713 public SavedState createFromParcel(Parcel in) {
714 return new SavedState(in);
715 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800717 public SavedState[] newArray(int size) {
718 return new SavedState[size];
719 }
720 };
Kenneth Anderssone3491b62010-03-05 09:16:24 +0100721 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722}
Svetoslav Ganovf1148092011-02-25 12:39:59 -0800723