428 lines
14 KiB
Java
428 lines
14 KiB
Java
|
/*
|
||
|
* 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.
|
||
|
*
|
||
|
* <p>See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date Picker
|
||
|
* tutorial</a>.</p>
|
||
|
*
|
||
|
* 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<SavedState> CREATOR =
|
||
|
new Creator<SavedState>() {
|
||
|
|
||
|
public SavedState createFromParcel(Parcel in) {
|
||
|
return new SavedState(in);
|
||
|
}
|
||
|
|
||
|
public SavedState[] newArray(int size) {
|
||
|
return new SavedState[size];
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Override so we are in complete control of save / restore for this widget.
|
||
|
*/
|
||
|
@Override
|
||
|
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
|
||
|
dispatchThawSelfOnly(container);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected Parcelable onSaveInstanceState() {
|
||
|
Parcelable superState = super.onSaveInstanceState();
|
||
|
|
||
|
return new SavedState(superState, mYear, mMonth, mDay);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void onRestoreInstanceState(Parcelable state) {
|
||
|
SavedState ss = (SavedState) state;
|
||
|
super.onRestoreInstanceState(ss.getSuperState());
|
||
|
mYear = ss.getYear();
|
||
|
mMonth = ss.getMonth();
|
||
|
mDay = ss.getDay();
|
||
|
updateSpinners();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize the state.
|
||
|
* @param year The initial year.
|
||
|
* @param monthOfYear The initial month.
|
||
|
* @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) {
|
||
|
mYear = year;
|
||
|
mMonth = monthOfYear;
|
||
|
mDay = dayOfMonth;
|
||
|
mOnDateChangedListener = onDateChangedListener;
|
||
|
updateSpinners();
|
||
|
}
|
||
|
|
||
|
private void updateSpinners() {
|
||
|
updateDaySpinner();
|
||
|
mYearPicker.setCurrent(mYear);
|
||
|
|
||
|
/* The month display uses 1-12 but our internal state stores it
|
||
|
* 0-11 so add one when setting the display.
|
||
|
*/
|
||
|
mMonthPicker.setCurrent(mMonth + 1);
|
||
|
}
|
||
|
|
||
|
private void updateDaySpinner() {
|
||
|
Calendar cal = Calendar.getInstance();
|
||
|
cal.set(mYear, mMonth, mDay);
|
||
|
int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||
|
mDayPicker.setRange(1, max);
|
||
|
mDayPicker.setCurrent(mDay);
|
||
|
}
|
||
|
|
||
|
public int getYear() {
|
||
|
return mYear;
|
||
|
}
|
||
|
|
||
|
public int getMonth() {
|
||
|
return mMonth;
|
||
|
}
|
||
|
|
||
|
public int getDayOfMonth() {
|
||
|
return mDay;
|
||
|
}
|
||
|
|
||
|
private void adjustMaxDay(){
|
||
|
Calendar cal = Calendar.getInstance();
|
||
|
cal.set(Calendar.YEAR, mYear);
|
||
|
cal.set(Calendar.MONTH, mMonth);
|
||
|
int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||
|
if (mDay > max) {
|
||
|
mDay = max;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void notifyDateChanged() {
|
||
|
if (mOnDateChangedListener != null) {
|
||
|
mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay);
|
||
|
}
|
||
|
}
|
||
|
}
|