Java tutorial
/* * 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.IntDef; import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.annotation.Widget; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.icu.util.Calendar; import android.icu.util.TimeZone; import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewStructure; import android.view.accessibility.AccessibilityEvent; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import com.android.internal.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Locale; /** * Provides a widget for selecting a date. * <p> * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is * set to {@code spinner}, the date can be selected using 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. * </p> * <p> * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is * set to {@code calendar}, the month and day can be selected using a * calendar-style view while the year can be selected separately using a list. * </p> * <p> * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a> * guide. * </p> * <p> * For a dialog using this view, see {@link android.app.DatePickerDialog}. * </p> * * @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 * @attr ref android.R.styleable#DatePicker_dayOfWeekBackground * @attr ref android.R.styleable#DatePicker_dayOfWeekTextAppearance * @attr ref android.R.styleable#DatePicker_headerBackground * @attr ref android.R.styleable#DatePicker_headerMonthTextAppearance * @attr ref android.R.styleable#DatePicker_headerDayOfMonthTextAppearance * @attr ref android.R.styleable#DatePicker_headerYearTextAppearance * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance * @attr ref android.R.styleable#DatePicker_yearListSelectorColor * @attr ref android.R.styleable#DatePicker_calendarTextColor * @attr ref android.R.styleable#DatePicker_datePickerMode */ @Widget public class DatePicker extends FrameLayout { private static final String LOG_TAG = DatePicker.class.getSimpleName(); /** * Presentation mode for the Holo-style date picker that uses a set of * {@link android.widget.NumberPicker}s. * * @see #getMode() * @hide Visible for testing only. */ @TestApi public static final int MODE_SPINNER = 1; /** * Presentation mode for the Material-style date picker that uses a * calendar. * * @see #getMode() * @hide Visible for testing only. */ @TestApi public static final int MODE_CALENDAR = 2; /** @hide */ @IntDef(prefix = { "MODE_" }, value = { MODE_SPINNER, MODE_CALENDAR }) @Retention(RetentionPolicy.SOURCE) public @interface DatePickerMode { } @UnsupportedAppUsage private final DatePickerDelegate mDelegate; @DatePickerMode private final int mMode; /** * The callback used to indicate the user changed 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); // DatePicker is important by default, unless app developer overrode attribute. if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); } final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker, defStyleAttr, defStyleRes); final boolean isDialogMode = a.getBoolean(R.styleable.DatePicker_dialogMode, false); final int requestedMode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER); final int firstDayOfWeek = a.getInt(R.styleable.DatePicker_firstDayOfWeek, 0); a.recycle(); if (requestedMode == MODE_CALENDAR && isDialogMode) { // You want MODE_CALENDAR? YOU CAN'T HANDLE MODE_CALENDAR! Well, // maybe you can depending on your screen size. Let's check... mMode = context.getResources().getInteger(R.integer.date_picker_mode); } else { mMode = requestedMode; } switch (mMode) { case MODE_CALENDAR: mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes); break; case MODE_SPINNER: default: mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes); break; } if (firstDayOfWeek != 0) { setFirstDayOfWeek(firstDayOfWeek); } mDelegate.setAutoFillChangeListener((v, y, m, d) -> { final AutofillManager afm = context.getSystemService(AutofillManager.class); if (afm != null) { afm.notifyValueChanged(this); } }); } private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes); } private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr, defStyleRes); } /** * @return the picker's presentation mode, one of {@link #MODE_CALENDAR} or * {@link #MODE_SPINNER} * @attr ref android.R.styleable#DatePicker_datePickerMode * @hide Visible for testing only. */ @DatePickerMode @TestApi public int getMode() { return mMode; } /** * 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 <strong>starting from zero</strong>. * @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); } /** * Set the callback that indicates the date has been adjusted by the user. * * @param onDateChangedListener How user is notified date is changed by * user, can be null. */ public void setOnDateChangedListener(OnDateChangedListener onDateChangedListener) { mDelegate.setOnDateChangedListener(onDateChangedListener); } /** * Update the current date. * * @param year The year. * @param month The month which is <strong>starting from zero</strong>. * @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. * <p> * Note: The default minimal date is 01/01/1900. * <p> * * @return The minimal supported date. */ public long getMinDate() { return mDelegate.getMinDate().getTimeInMillis(); } /** * 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. * <p> * Note: The default maximal date is 12/31/2100. * <p> * * @return The maximal supported date. */ public long getMaxDate() { return mDelegate.getMaxDate().getTimeInMillis(); } /** * 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); } /** * Sets the callback that indicates the current date is valid. * * @param callback the callback, may be null * @hide */ @UnsupportedAppUsage public void setValidationCallback(@Nullable ValidationCallback callback) { mDelegate.setValidationCallback(callback); } @Override public void setEnabled(boolean enabled) { if (mDelegate.isEnabled() == enabled) { return; } super.setEnabled(enabled); mDelegate.setEnabled(enabled); } @Override public boolean isEnabled() { return mDelegate.isEnabled(); } /** @hide */ @Override public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { return mDelegate.dispatchPopulateAccessibilityEvent(event); } /** @hide */ @Override public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { super.onPopulateAccessibilityEventInternal(event); mDelegate.onPopulateAccessibilityEvent(event); } @Override public CharSequence getAccessibilityClassName() { return DatePicker.class.getName(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDelegate.onConfigurationChanged(newConfig); } /** * Sets the first day of week. * * @param firstDayOfWeek The first day of the week conforming to the * {@link CalendarView} APIs. * @see Calendar#SUNDAY * @see Calendar#MONDAY * @see Calendar#TUESDAY * @see Calendar#WEDNESDAY * @see Calendar#THURSDAY * @see Calendar#FRIDAY * @see Calendar#SATURDAY * * @attr ref android.R.styleable#DatePicker_firstDayOfWeek */ public void setFirstDayOfWeek(int firstDayOfWeek) { if (firstDayOfWeek < Calendar.SUNDAY || firstDayOfWeek > Calendar.SATURDAY) { throw new IllegalArgumentException("firstDayOfWeek must be between 1 and 7"); } mDelegate.setFirstDayOfWeek(firstDayOfWeek); } /** * Gets the first day of week. * * @return The first day of the week conforming to the {@link CalendarView} * APIs. * @see Calendar#SUNDAY * @see Calendar#MONDAY * @see Calendar#TUESDAY * @see Calendar#WEDNESDAY * @see Calendar#THURSDAY * @see Calendar#FRIDAY * @see Calendar#SATURDAY * * @attr ref android.R.styleable#DatePicker_firstDayOfWeek */ public int getFirstDayOfWeek() { return mDelegate.getFirstDayOfWeek(); } /** * Returns whether the {@link CalendarView} is shown. * <p> * <strong>Note:</strong> This method returns {@code false} when the * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set * to {@code calendar}. * * @return {@code true} if the calendar view is shown * @see #getCalendarView() * @deprecated Not supported by Material-style {@code calendar} mode */ @Deprecated public boolean getCalendarViewShown() { return mDelegate.getCalendarViewShown(); } /** * Returns the {@link CalendarView} used by this picker. * <p> * <strong>Note:</strong> This method throws an * {@link UnsupportedOperationException} when the * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set * to {@code calendar}. * * @return the calendar view * @see #getCalendarViewShown() * @deprecated Not supported by Material-style {@code calendar} mode * @throws UnsupportedOperationException if called when the picker is * displayed in {@code calendar} mode */ @Deprecated public CalendarView getCalendarView() { return mDelegate.getCalendarView(); } /** * Sets whether the {@link CalendarView} is shown. * <p> * <strong>Note:</strong> Calling this method has no effect when the * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set * to {@code calendar}. * * @param shown {@code true} to show the calendar view, {@code false} to * hide it * @deprecated Not supported by Material-style {@code calendar} mode */ @Deprecated public void setCalendarViewShown(boolean shown) { mDelegate.setCalendarViewShown(shown); } /** * Returns whether the spinners are shown. * <p> * <strong>Note:</strong> his method returns {@code false} when the * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set * to {@code calendar}. * * @return {@code true} if the spinners are shown * @deprecated Not supported by Material-style {@code calendar} mode */ @Deprecated public boolean getSpinnersShown() { return mDelegate.getSpinnersShown(); } /** * Sets whether the spinners are shown. * <p> * Calling this method has no effect when the * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set * to {@code calendar}. * * @param shown {@code true} to show the spinners, {@code false} to hide * them * @deprecated Not supported by Material-style {@code calendar} mode */ @Deprecated public void setSpinnersShown(boolean shown) { mDelegate.setSpinnersShown(shown); } @Override protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { dispatchThawSelfOnly(container); } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); return mDelegate.onSaveInstanceState(superState); } @Override protected void onRestoreInstanceState(Parcelable state) { BaseSavedState ss = (BaseSavedState) state; super.onRestoreInstanceState(ss.getSuperState()); mDelegate.onRestoreInstanceState(ss); } /** * A delegate interface that defined the public API of the DatePicker. Allows different * DatePicker implementations. This would need to be implemented by the DatePicker delegates * for the real behavior. * * @hide */ interface DatePickerDelegate { void init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener); void setOnDateChangedListener(OnDateChangedListener onDateChangedListener); void setAutoFillChangeListener(OnDateChangedListener onDateChangedListener); void updateDate(int year, int month, int dayOfMonth); int getYear(); int getMonth(); int getDayOfMonth(); void autofill(AutofillValue value); AutofillValue getAutofillValue(); void setFirstDayOfWeek(int firstDayOfWeek); int getFirstDayOfWeek(); void setMinDate(long minDate); Calendar getMinDate(); void setMaxDate(long maxDate); Calendar getMaxDate(); void setEnabled(boolean enabled); boolean isEnabled(); CalendarView getCalendarView(); void setCalendarViewShown(boolean shown); boolean getCalendarViewShown(); void setSpinnersShown(boolean shown); boolean getSpinnersShown(); void setValidationCallback(ValidationCallback callback); void onConfigurationChanged(Configuration newConfig); Parcelable onSaveInstanceState(Parcelable superState); void onRestoreInstanceState(Parcelable state); boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); void onPopulateAccessibilityEvent(AccessibilityEvent event); } /** * An abstract class which can be used as a start for DatePicker implementations */ abstract static class AbstractDatePickerDelegate implements DatePickerDelegate { // The delegator protected DatePicker mDelegator; // The context protected Context mContext; // NOTE: when subclasses change this variable, they must call resetAutofilledValue(). protected Calendar mCurrentDate; // The current locale protected Locale mCurrentLocale; // Callbacks protected OnDateChangedListener mOnDateChangedListener; protected OnDateChangedListener mAutoFillChangeListener; protected ValidationCallback mValidationCallback; // The value that was passed to autofill() - it must be stored because it getAutofillValue() // must return the exact same value that was autofilled, otherwise the widget will not be // properly highlighted after autofill(). private long mAutofilledValue; public AbstractDatePickerDelegate(DatePicker delegator, Context context) { mDelegator = delegator; mContext = context; setCurrentLocale(Locale.getDefault()); } protected void setCurrentLocale(Locale locale) { if (!locale.equals(mCurrentLocale)) { mCurrentLocale = locale; onLocaleChanged(locale); } } @Override public void setOnDateChangedListener(OnDateChangedListener callback) { mOnDateChangedListener = callback; } @Override public void setAutoFillChangeListener(OnDateChangedListener callback) { mAutoFillChangeListener = callback; } @Override public void setValidationCallback(ValidationCallback callback) { mValidationCallback = callback; } @Override public final void autofill(AutofillValue value) { if (value == null || !value.isDate()) { Log.w(LOG_TAG, value + " could not be autofilled into " + this); return; } final long time = value.getDateValue(); final Calendar cal = Calendar.getInstance(mCurrentLocale); cal.setTimeInMillis(time); updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)); // Must set mAutofilledValue *after* calling subclass method to make sure the value // returned by getAutofillValue() matches it. mAutofilledValue = time; } @Override public final AutofillValue getAutofillValue() { final long time = mAutofilledValue != 0 ? mAutofilledValue : mCurrentDate.getTimeInMillis(); return AutofillValue.forDate(time); } /** * This method must be called every time the value of the year, month, and/or day is * changed by a subclass method. */ protected void resetAutofilledValue() { mAutofilledValue = 0; } protected void onValidationChanged(boolean valid) { if (mValidationCallback != null) { mValidationCallback.onValidationChanged(valid); } } protected void onLocaleChanged(Locale locale) { // Stub. } @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { event.getText().add(getFormattedCurrentDate()); } protected String getFormattedCurrentDate() { return DateUtils.formatDateTime(mContext, mCurrentDate.getTimeInMillis(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY); } /** * Class for managing state storing/restoring. */ static class SavedState extends View.BaseSavedState { private final int mSelectedYear; private final int mSelectedMonth; private final int mSelectedDay; private final long mMinDate; private final long mMaxDate; private final int mCurrentView; private final int mListPosition; private final int mListPositionOffset; public SavedState(Parcelable superState, int year, int month, int day, long minDate, long maxDate) { this(superState, year, month, day, minDate, maxDate, 0, 0, 0); } /** * Constructor called from {@link DatePicker#onSaveInstanceState()} */ public SavedState(Parcelable superState, int year, int month, int day, long minDate, long maxDate, int currentView, int listPosition, int listPositionOffset) { super(superState); mSelectedYear = year; mSelectedMonth = month; mSelectedDay = day; mMinDate = minDate; mMaxDate = maxDate; mCurrentView = currentView; mListPosition = listPosition; mListPositionOffset = listPositionOffset; } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); mSelectedYear = in.readInt(); mSelectedMonth = in.readInt(); mSelectedDay = in.readInt(); mMinDate = in.readLong(); mMaxDate = in.readLong(); mCurrentView = in.readInt(); mListPosition = in.readInt(); mListPositionOffset = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(mSelectedYear); dest.writeInt(mSelectedMonth); dest.writeInt(mSelectedDay); dest.writeLong(mMinDate); dest.writeLong(mMaxDate); dest.writeInt(mCurrentView); dest.writeInt(mListPosition); dest.writeInt(mListPositionOffset); } public int getSelectedDay() { return mSelectedDay; } public int getSelectedMonth() { return mSelectedMonth; } public int getSelectedYear() { return mSelectedYear; } public long getMinDate() { return mMinDate; } public long getMaxDate() { return mMaxDate; } public int getCurrentView() { return mCurrentView; } public int getListPosition() { return mListPosition; } public int getListPositionOffset() { return mListPositionOffset; } @SuppressWarnings("all") // suppress unused and hiding public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } } /** * A callback interface for updating input validity when the date picker * when included into a dialog. * * @hide */ public interface ValidationCallback { void onValidationChanged(boolean valid); } @Override public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) { // This view is self-sufficient for autofill, so it needs to call // onProvideAutoFillStructure() to fill itself, but it does not need to call // dispatchProvideAutoFillStructure() to fill its children. structure.setAutofillId(getAutofillId()); onProvideAutofillStructure(structure, flags); } @Override public void autofill(AutofillValue value) { if (!isEnabled()) return; mDelegate.autofill(value); } @Override public @AutofillType int getAutofillType() { return isEnabled() ? AUTOFILL_TYPE_DATE : AUTOFILL_TYPE_NONE; } @Override public AutofillValue getAutofillValue() { return isEnabled() ? mDelegate.getAutofillValue() : null; } }