2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.test.hwui;
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.util.AttributeSet;
26 import android.util.SparseArray;
27 import android.view.ContextThemeWrapper;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.widget.CheckBox;
31 import android.widget.CompoundButton;
32 import android.widget.FrameLayout;
33 import android.widget.LinearLayout;
34 import android.widget.NumberPicker;
36 import java.text.DateFormatSymbols;
37 import java.text.SimpleDateFormat;
38 import java.util.Calendar;
41 * A view for selecting a month / year / day based on a calendar like layout.
43 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date Picker
46 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
49 public class DatePicker extends FrameLayout {
51 private static final int DEFAULT_START_YEAR = 1900;
52 private static final int DEFAULT_END_YEAR = 2100;
55 private final CheckBox mYearToggle;
56 private final NumberPicker mDayPicker;
57 private final NumberPicker mMonthPicker;
58 private final NumberPicker mYearPicker;
61 * How we notify users the date has changed.
63 private OnDateChangedListener mOnDateChangedListener;
68 private boolean mYearOptional = true;
69 private boolean mHasYear;
72 * The callback used to indicate the user changes the date.
74 public interface OnDateChangedListener {
77 * @param view The view associated with this listener.
78 * @param year The year that was set.
79 * @param monthOfYear The month that was set (0-11) for compatibility
80 * with {@link java.util.Calendar}.
81 * @param dayOfMonth The day of the month that was set.
83 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
86 public DatePicker(Context context) {
90 public DatePicker(Context context, AttributeSet attrs) {
91 this(context, attrs, 0);
94 @SuppressWarnings("deprecation")
95 public DatePicker(Context context, AttributeSet attrs, int defStyle) {
96 super(context, attrs, defStyle);
98 ContextThemeWrapper themed = new ContextThemeWrapper(context,
99 com.android.internal.R.style.Theme_Holo_Light_Dialog_Alert);
100 LayoutInflater inflater = (LayoutInflater) themed.getSystemService(
101 Context.LAYOUT_INFLATER_SERVICE);
102 inflater.inflate(R.layout.date_picker, this, true);
104 mDayPicker = findViewById(R.id.day);
105 mDayPicker.setFormatter(NumberPicker.getTwoDigitFormatter());
106 mDayPicker.setOnLongPressUpdateInterval(100);
107 mDayPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
108 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
113 mMonthPicker = findViewById(R.id.month);
114 mMonthPicker.setFormatter(NumberPicker.getTwoDigitFormatter());
115 DateFormatSymbols dfs = new DateFormatSymbols();
116 String[] months = dfs.getShortMonths();
119 * If the user is in a locale where the month names are numeric,
120 * use just the number instead of the "month" character for
121 * consistency with the other fields.
123 if (months[0].startsWith("1")) {
124 for (int i = 0; i < months.length; i++) {
125 months[i] = String.valueOf(i + 1);
127 mMonthPicker.setMinValue(1);
128 mMonthPicker.setMaxValue(12);
130 mMonthPicker.setMinValue(1);
131 mMonthPicker.setMaxValue(12);
132 mMonthPicker.setDisplayedValues(months);
135 mMonthPicker.setOnLongPressUpdateInterval(200);
136 mMonthPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
137 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
139 /* We display the month 1-12 but store it 0-11 so always
140 * subtract by one to ensure our internal state is always 0-11
143 // Adjust max day of the month
149 mYearPicker = findViewById(R.id.year);
150 mYearPicker.setOnLongPressUpdateInterval(100);
151 mYearPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
152 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
154 // Adjust max day for leap years if needed
161 mYearToggle = findViewById(R.id.yearToggle);
162 mYearToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
164 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
165 mHasYear = isChecked;
173 TypedArray a = context.obtainStyledAttributes(attrs,
174 com.android.internal.R.styleable.DatePicker);
177 a.getInt(com.android.internal.R.styleable.DatePicker_startYear, DEFAULT_START_YEAR);
179 a.getInt(com.android.internal.R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
180 mYearPicker.setMinValue(mStartYear);
181 mYearPicker.setMaxValue(mEndYear);
185 // initialize to current date
186 Calendar cal = Calendar.getInstance();
187 init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null);
189 // re-order the number pickers to match the current date format
190 reorderPickers(months);
198 public void setEnabled(boolean enabled) {
199 super.setEnabled(enabled);
200 mDayPicker.setEnabled(enabled);
201 mMonthPicker.setEnabled(enabled);
202 mYearPicker.setEnabled(enabled);
205 private void reorderPickers(String[] months) {
206 java.text.DateFormat format;
210 * If the user is in a locale where the medium date format is
211 * still numeric (Japanese and Czech, for example), respect
212 * the date format order setting. Otherwise, use the order
213 * that the locale says is appropriate for a spelled-out date.
216 if (months[0].startsWith("1")) {
217 format = DateFormat.getDateFormat(getContext());
219 format = DateFormat.getMediumDateFormat(getContext());
222 if (format instanceof SimpleDateFormat) {
223 order = ((SimpleDateFormat) format).toPattern();
225 // Shouldn't happen, but just in case.
226 order = new String(DateFormat.getDateFormatOrder(getContext()));
229 /* Remove the 3 pickers from their parent and then add them back in the
232 LinearLayout parent = findViewById(R.id.parent);
233 parent.removeAllViews();
235 boolean quoted = false;
236 boolean didDay = false, didMonth = false, didYear = false;
238 for (int i = 0; i < order.length(); i++) {
239 char c = order.charAt(i);
246 if (c == DateFormat.DATE && !didDay) {
247 parent.addView(mDayPicker);
249 } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) {
250 parent.addView(mMonthPicker);
252 } else if (c == DateFormat.YEAR && !didYear) {
253 parent.addView (mYearPicker);
259 // Shouldn't happen, but just in case.
261 parent.addView(mMonthPicker);
264 parent.addView(mDayPicker);
267 parent.addView(mYearPicker);
271 public void updateDate(int year, int monthOfYear, int dayOfMonth) {
272 if (mYear != year || mMonth != monthOfYear || mDay != dayOfMonth) {
273 mYear = (mYearOptional && year == 0) ? getCurrentYear() : year;
274 mMonth = monthOfYear;
277 reorderPickers(new DateFormatSymbols().getShortMonths());
282 private static int getCurrentYear() {
283 return Calendar.getInstance().get(Calendar.YEAR);
286 private static class SavedState extends BaseSavedState {
288 private final int mYear;
289 private final int mMonth;
290 private final int mDay;
291 private final boolean mHasYear;
292 private final boolean mYearOptional;
295 * Constructor called from {@link DatePicker#onSaveInstanceState()}
297 private SavedState(Parcelable superState, int year, int month, int day, boolean hasYear,
298 boolean yearOptional) {
304 mYearOptional = yearOptional;
308 * Constructor called from {@link #CREATOR}
310 private SavedState(Parcel in) {
312 mYear = in.readInt();
313 mMonth = in.readInt();
315 mHasYear = in.readInt() != 0;
316 mYearOptional = in.readInt() != 0;
319 public int getYear() {
323 public int getMonth() {
327 public int getDay() {
331 public boolean hasYear() {
335 public boolean isYearOptional() {
336 return mYearOptional;
340 public void writeToParcel(Parcel dest, int flags) {
341 super.writeToParcel(dest, flags);
342 dest.writeInt(mYear);
343 dest.writeInt(mMonth);
345 dest.writeInt(mHasYear ? 1 : 0);
346 dest.writeInt(mYearOptional ? 1 : 0);
349 @SuppressWarnings("unused")
350 public static final Parcelable.Creator<SavedState> CREATOR =
351 new Creator<SavedState>() {
353 public SavedState createFromParcel(Parcel in) {
354 return new SavedState(in);
357 public SavedState[] newArray(int size) {
358 return new SavedState[size];
365 * Override so we are in complete control of save / restore for this widget.
368 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
369 dispatchThawSelfOnly(container);
373 protected Parcelable onSaveInstanceState() {
374 Parcelable superState = super.onSaveInstanceState();
376 return new SavedState(superState, mYear, mMonth, mDay, mHasYear, mYearOptional);
380 protected void onRestoreInstanceState(Parcelable state) {
381 SavedState ss = (SavedState) state;
382 super.onRestoreInstanceState(ss.getSuperState());
383 mYear = ss.getYear();
384 mMonth = ss.getMonth();
386 mHasYear = ss.hasYear();
387 mYearOptional = ss.isYearOptional();
392 * Initialize the state.
393 * @param year The initial year.
394 * @param monthOfYear The initial month.
395 * @param dayOfMonth The initial day of the month.
396 * @param onDateChangedListener How user is notified date is changed by user, can be null.
398 public void init(int year, int monthOfYear, int dayOfMonth,
399 OnDateChangedListener onDateChangedListener) {
400 init(year, monthOfYear, dayOfMonth, false, onDateChangedListener);
404 * Initialize the state.
405 * @param year The initial year or 0 if no year has been specified
406 * @param monthOfYear The initial month.
407 * @param dayOfMonth The initial day of the month.
408 * @param yearOptional True if the user can toggle the year
409 * @param onDateChangedListener How user is notified date is changed by user, can be null.
411 public void init(int year, int monthOfYear, int dayOfMonth, boolean yearOptional,
412 OnDateChangedListener onDateChangedListener) {
413 mYear = (yearOptional && year == 0) ? getCurrentYear() : year;
414 mMonth = monthOfYear;
416 mYearOptional = yearOptional;
417 mHasYear = !yearOptional || (year != 0);
418 mOnDateChangedListener = onDateChangedListener;
422 private void updateSpinners() {
424 mYearToggle.setChecked(mHasYear);
425 mYearToggle.setVisibility(mYearOptional ? View.VISIBLE : View.GONE);
426 mYearPicker.setValue(mYear);
427 mYearPicker.setVisibility(mHasYear ? View.VISIBLE : View.GONE);
429 /* The month display uses 1-12 but our internal state stores it
430 * 0-11 so add one when setting the display.
432 mMonthPicker.setValue(mMonth + 1);
435 private void updateDaySpinner() {
436 Calendar cal = Calendar.getInstance();
437 // if year was not set, use 2000 as it was a leap year
438 cal.set(mHasYear ? mYear : 2000, mMonth, 1);
439 int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
440 mDayPicker.setMinValue(1);
441 mDayPicker.setMaxValue(max);
442 mDayPicker.setValue(mDay);
445 public int getYear() {
446 return (mYearOptional && !mHasYear) ? 0 : mYear;
449 public int getMonth() {
453 public int getDayOfMonth() {
457 private void adjustMaxDay(){
458 Calendar cal = Calendar.getInstance();
459 // if year was not set, use 2000 as it was a leap year
460 cal.set(Calendar.YEAR, mHasYear ? mYear : 2000);
461 cal.set(Calendar.MONTH, mMonth);
462 int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
468 private void notifyDateChanged() {
469 if (mOnDateChangedListener != null) {
470 int year = (mYearOptional && !mHasYear) ? 0 : mYear;
471 mOnDateChangedListener.onDateChanged(DatePicker.this, year, mMonth, mDay);