/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.annotation.Widget; import android.content.Context; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; import android.widget.NumberPicker; import android.widget.NumberPicker.OnChangedListener; import com.android.internal.R; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; /** * A view for selecting a month / year / day based on a calendar like layout. * *
See the Date Picker * tutorial.
* * For a dialog using this view, see {@link android.app.DatePickerDialog}. */ @Widget public class DatePicker extends FrameLayout { private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; // This ignores Undecimber, but we only support real Gregorian calendars. private static final int NUMBER_OF_MONTHS = 12; /* UI Components */ private final NumberPicker mDayPicker; private final NumberPicker mMonthPicker; private final NumberPicker mYearPicker; /** * How we notify users the date has changed. */ private OnDateChangedListener mOnDateChangedListener; private int mDay; private int mMonth; private int mYear; private Object mMonthUpdateLock = new Object(); private volatile Locale mMonthLocale; private String[] mShortMonths; /** * The callback used to indicate the user changes the date. */ public interface OnDateChangedListener { /** * @param view The view associated with this listener. * @param year The year that was set. * @param monthOfYear The month that was set (0-11) for compatibility * with {@link java.util.Calendar}. * @param dayOfMonth The day of the month that was set. */ void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); } public DatePicker(Context context) { this(context, null); } public DatePicker(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DatePicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.date_picker, this, true); mDayPicker = (NumberPicker) findViewById(R.id.day); mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); mDayPicker.setSpeed(100); mDayPicker.setOnChangeListener(new OnChangedListener() { public void onChanged(NumberPicker picker, int oldVal, int newVal) { mDay = newVal; notifyDateChanged(); } }); mMonthPicker = (NumberPicker) findViewById(R.id.month); mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); final String[] months = getShortMonths(); /* * If the user is in a locale where the month names are numeric, * use just the number instead of the "month" character for * consistency with the other fields. */ if (months[0].startsWith("1")) { for (int i = 0; i < months.length; i++) { months[i] = String.valueOf(i + 1); } mMonthPicker.setRange(1, NUMBER_OF_MONTHS); } else { mMonthPicker.setRange(1, NUMBER_OF_MONTHS, months); } mMonthPicker.setSpeed(200); mMonthPicker.setOnChangeListener(new OnChangedListener() { public void onChanged(NumberPicker picker, int oldVal, int newVal) { /* We display the month 1-12 but store it 0-11 so always * subtract by one to ensure our internal state is always 0-11 */ mMonth = newVal - 1; // Adjust max day of the month adjustMaxDay(); notifyDateChanged(); updateDaySpinner(); } }); mYearPicker = (NumberPicker) findViewById(R.id.year); mYearPicker.setSpeed(100); mYearPicker.setOnChangeListener(new OnChangedListener() { public void onChanged(NumberPicker picker, int oldVal, int newVal) { mYear = newVal; // Adjust max day for leap years if needed adjustMaxDay(); notifyDateChanged(); updateDaySpinner(); } }); // attributes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker); int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); mYearPicker.setRange(mStartYear, mEndYear); a.recycle(); // initialize to current date Calendar cal = Calendar.getInstance(); init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null); // re-order the number pickers to match the current date format reorderPickers(months); if (!isEnabled()) { setEnabled(false); } } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mDayPicker.setEnabled(enabled); mMonthPicker.setEnabled(enabled); mYearPicker.setEnabled(enabled); } private void reorderPickers(String[] months) { java.text.DateFormat format; String order; /* * If the user is in a locale where the medium date format is * still numeric (Japanese and Czech, for example), respect * the date format order setting. Otherwise, use the order * that the locale says is appropriate for a spelled-out date. */ if (months[0].startsWith("1")) { format = DateFormat.getDateFormat(getContext()); } else { format = DateFormat.getMediumDateFormat(getContext()); } if (format instanceof SimpleDateFormat) { order = ((SimpleDateFormat) format).toPattern(); } else { // Shouldn't happen, but just in case. order = new String(DateFormat.getDateFormatOrder(getContext())); } /* Remove the 3 pickers from their parent and then add them back in the * required order. */ LinearLayout parent = (LinearLayout) findViewById(R.id.parent); parent.removeAllViews(); boolean quoted = false; boolean didDay = false, didMonth = false, didYear = false; for (int i = 0; i < order.length(); i++) { char c = order.charAt(i); if (c == '\'') { quoted = !quoted; } if (!quoted) { if (c == DateFormat.DATE && !didDay) { parent.addView(mDayPicker); didDay = true; } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) { parent.addView(mMonthPicker); didMonth = true; } else if (c == DateFormat.YEAR && !didYear) { parent.addView (mYearPicker); didYear = true; } } } // Shouldn't happen, but just in case. if (!didMonth) { parent.addView(mMonthPicker); } if (!didDay) { parent.addView(mDayPicker); } if (!didYear) { parent.addView(mYearPicker); } } public void updateDate(int year, int monthOfYear, int dayOfMonth) { if (mYear != year || mMonth != monthOfYear || mDay != dayOfMonth) { mYear = year; mMonth = monthOfYear; mDay = dayOfMonth; updateSpinners(); reorderPickers(getShortMonths()); notifyDateChanged(); } } private String[] getShortMonths() { final Locale currentLocale = Locale.getDefault(); if (currentLocale.equals(mMonthLocale) && mShortMonths != null) { return mShortMonths; } else { synchronized (mMonthUpdateLock) { if (!currentLocale.equals(mMonthLocale)) { mShortMonths = new String[NUMBER_OF_MONTHS]; for (int i = 0; i < NUMBER_OF_MONTHS; i++) { mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i, DateUtils.LENGTH_MEDIUM); } mMonthLocale = currentLocale; } } return mShortMonths; } } private static class SavedState extends BaseSavedState { private final int mYear; private final int mMonth; private final int mDay; /** * Constructor called from {@link DatePicker#onSaveInstanceState()} */ private SavedState(Parcelable superState, int year, int month, int day) { super(superState); mYear = year; mMonth = month; mDay = day; } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); mYear = in.readInt(); mMonth = in.readInt(); mDay = in.readInt(); } public int getYear() { return mYear; } public int getMonth() { return mMonth; } public int getDay() { return mDay; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(mYear); dest.writeInt(mMonth); dest.writeInt(mDay); } public static final Parcelable.Creator