/* * 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.Configuration; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.text.InputType; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.NumberPicker.OnValueChangeListener; import com.android.internal.R; import java.text.DateFormatSymbols; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; import libcore.icu.ICU; /** * This class is a widget for selecting a date. The date can be selected by a * year, month, and day spinners or a {@link CalendarView}. The set of spinners * and the calendar view are automatically synchronized. The client can * customize whether only the spinners, or only the calendar view, or both to be * displayed. Also the minimal and maximal date from which dates to be selected * can be customized. *
* See the Pickers * guide. *
** For a dialog using this view, see {@link android.app.DatePickerDialog}. *
* * @attr ref android.R.styleable#DatePicker_startYear * @attr ref android.R.styleable#DatePicker_endYear * @attr ref android.R.styleable#DatePicker_maxDate * @attr ref android.R.styleable#DatePicker_minDate * @attr ref android.R.styleable#DatePicker_spinnersShown * @attr ref android.R.styleable#DatePicker_calendarViewShown */ @Widget public class DatePicker extends FrameLayout { private static final String LOG_TAG = DatePicker.class.getSimpleName(); private DatePickerDelegate mDelegate; /** * The callback used to indicate the user changes\d the date. */ public interface OnDateChangedListener { /** * Called upon a date change. * * @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, R.attr.datePickerStyle); } public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mDelegate = new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes); } /** * Initialize the state. If the provided values designate an inconsistent * date the values are normalized before updating the spinners. * * @param year The initial year. * @param monthOfYear The initial month starting from zero. * @param dayOfMonth The initial day of the month. * @param onDateChangedListener How user is notified date is changed by * user, can be null. */ public void init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener) { mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener); } /** * Updates the current date. * * @param year The year. * @param month The month which is starting from zero. * @param dayOfMonth The day of the month. */ public void updateDate(int year, int month, int dayOfMonth) { mDelegate.updateDate(year, month, dayOfMonth); } /** * @return The selected year. */ public int getYear() { return mDelegate.getYear(); } /** * @return The selected month. */ public int getMonth() { return mDelegate.getMonth(); } /** * @return The selected day of month. */ public int getDayOfMonth() { return mDelegate.getDayOfMonth(); } /** * Gets the minimal date supported by this {@link DatePicker} in * milliseconds since January 1, 1970 00:00:00 in * {@link TimeZone#getDefault()} time zone. ** Note: The default minimal date is 01/01/1900. *
* * @return The minimal supported date. */ public long getMinDate() { return mDelegate.getMinDate(); } /** * Sets the minimal date supported by this {@link NumberPicker} in * milliseconds since January 1, 1970 00:00:00 in * {@link TimeZone#getDefault()} time zone. * * @param minDate The minimal supported date. */ public void setMinDate(long minDate) { mDelegate.setMinDate(minDate); } /** * Gets the maximal date supported by this {@link DatePicker} in * milliseconds since January 1, 1970 00:00:00 in * {@link TimeZone#getDefault()} time zone. *
* Note: The default maximal date is 12/31/2100. *
*
* @return The maximal supported date.
*/
public long getMaxDate() {
return mDelegate.getMaxDate();
}
/**
* Sets the maximal date supported by this {@link DatePicker} in
* milliseconds since January 1, 1970 00:00:00 in
* {@link TimeZone#getDefault()} time zone.
*
* @param maxDate The maximal supported date.
*/
public void setMaxDate(long maxDate) {
mDelegate.setMaxDate(maxDate);
}
@Override
public void setEnabled(boolean enabled) {
if (mDelegate.isEnabled() == enabled) {
return;
}
super.setEnabled(enabled);
mDelegate.setEnabled(enabled);
}
@Override
public boolean isEnabled() {
return mDelegate.isEnabled();
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
return mDelegate.dispatchPopulateAccessibilityEvent(event);
}
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
mDelegate.onPopulateAccessibilityEvent(event);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
mDelegate.onInitializeAccessibilityEvent(event);
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
mDelegate.onInitializeAccessibilityNodeInfo(info);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDelegate.onConfigurationChanged(newConfig);
}
/**
* Gets whether the {@link CalendarView} is shown.
*
* @return True if the calendar view is shown.
* @see #getCalendarView()
*/
public boolean getCalendarViewShown() {
return mDelegate.getCalendarViewShown();
}
/**
* Gets the {@link CalendarView}.
*
* @return The calendar view.
* @see #getCalendarViewShown()
*/
public CalendarView getCalendarView () {
return mDelegate.getCalendarView();
}
/**
* Sets whether the {@link CalendarView} is shown.
*
* @param shown True if the calendar view is to be shown.
*/
public void setCalendarViewShown(boolean shown) {
mDelegate.setCalendarViewShown(shown);
}
/**
* Gets whether the spinners are shown.
*
* @return True if the spinners are shown.
*/
public boolean getSpinnersShown() {
return mDelegate.getSpinnersShown();
}
/**
* Sets whether the spinners are shown.
*
* @param shown True if the spinners are to be shown.
*/
public void setSpinnersShown(boolean shown) {
mDelegate.setSpinnersShown(shown);
}
// Override so we are in complete control of save / restore for this widget.
@Override
protected void dispatchRestoreInstanceState(SparseArraydate
and in case of success sets the result
* to the outDate
.
*
* @return True if the date was parsed.
*/
private boolean parseDate(String date, Calendar outDate) {
try {
outDate.setTime(mDateFormat.parse(date));
return true;
} catch (ParseException e) {
Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
return false;
}
}
private boolean isNewDate(int year, int month, int dayOfMonth) {
return (mCurrentDate.get(Calendar.YEAR) != year
|| mCurrentDate.get(Calendar.MONTH) != dayOfMonth
|| mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
}
private void setDate(int year, int month, int dayOfMonth) {
mCurrentDate.set(year, month, dayOfMonth);
if (mCurrentDate.before(mMinDate)) {
mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
} else if (mCurrentDate.after(mMaxDate)) {
mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
}
}
private void updateSpinners() {
// set the spinner ranges respecting the min and max dates
if (mCurrentDate.equals(mMinDate)) {
mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
mDaySpinner.setWrapSelectorWheel(false);
mMonthSpinner.setDisplayedValues(null);
mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
mMonthSpinner.setWrapSelectorWheel(false);
} else if (mCurrentDate.equals(mMaxDate)) {
mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
mDaySpinner.setWrapSelectorWheel(false);
mMonthSpinner.setDisplayedValues(null);
mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
mMonthSpinner.setWrapSelectorWheel(false);
} else {
mDaySpinner.setMinValue(1);
mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
mDaySpinner.setWrapSelectorWheel(true);
mMonthSpinner.setDisplayedValues(null);
mMonthSpinner.setMinValue(0);
mMonthSpinner.setMaxValue(11);
mMonthSpinner.setWrapSelectorWheel(true);
}
// make sure the month names are a zero based array
// with the months in the month spinner
String[] displayedValues = Arrays.copyOfRange(mShortMonths,
mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
mMonthSpinner.setDisplayedValues(displayedValues);
// year spinner range does not change based on the current date
mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
mYearSpinner.setWrapSelectorWheel(false);
// set the spinner values
mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
if (usingNumericMonths()) {
mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
}
}
/**
* Updates the calendar view with the current date.
*/
private void updateCalendarView() {
mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
}
/**
* Notifies the listener, if such, for a change in the selected date.
*/
private void notifyDateChanged() {
mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
if (mOnDateChangedListener != null) {
mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
getDayOfMonth());
}
}
/**
* Sets the IME options for a spinner based on its ordering.
*
* @param spinner The spinner.
* @param spinnerCount The total spinner count.
* @param spinnerIndex The index of the given spinner.
*/
private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
final int imeOptions;
if (spinnerIndex < spinnerCount - 1) {
imeOptions = EditorInfo.IME_ACTION_NEXT;
} else {
imeOptions = EditorInfo.IME_ACTION_DONE;
}
TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
input.setImeOptions(imeOptions);
}
private void setContentDescriptions() {
// Day
trySetContentDescription(mDaySpinner, R.id.increment,
R.string.date_picker_increment_day_button);
trySetContentDescription(mDaySpinner, R.id.decrement,
R.string.date_picker_decrement_day_button);
// Month
trySetContentDescription(mMonthSpinner, R.id.increment,
R.string.date_picker_increment_month_button);
trySetContentDescription(mMonthSpinner, R.id.decrement,
R.string.date_picker_decrement_month_button);
// Year
trySetContentDescription(mYearSpinner, R.id.increment,
R.string.date_picker_increment_year_button);
trySetContentDescription(mYearSpinner, R.id.decrement,
R.string.date_picker_decrement_year_button);
}
private void trySetContentDescription(View root, int viewId, int contDescResId) {
View target = root.findViewById(viewId);
if (target != null) {
target.setContentDescription(mContext.getString(contDescResId));
}
}
private void updateInputState() {
// Make sure that if the user changes the value and the IME is active
// for one of the inputs if this widget, the IME is closed. If the user
// changed the value via the IME and there is a next input the IME will
// be shown, otherwise the user chose another means of changing the
// value and having the IME up makes no sense.
InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
if (inputMethodManager != null) {
if (inputMethodManager.isActive(mYearSpinnerInput)) {
mYearSpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
} else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
mMonthSpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
} else if (inputMethodManager.isActive(mDaySpinnerInput)) {
mDaySpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
}
}
}
}
/**
* Class for managing state storing/restoring.
*/
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();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mYear);
dest.writeInt(mMonth);
dest.writeInt(mDay);
}
@SuppressWarnings("all")
// suppress unused and hiding
public static final Parcelable.Creator