package org.sana.android.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.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; import android.widget.DatePicker; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.NumberPicker; import org.sana.api.R; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; /** */ public class CustomDatePicker extends FrameLayout { private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; private static final char DATE = 'd'; private static final char MONTH = 'M'; private static final char YEAR = 'y'; public static class TwoDigitFormatter implements NumberPicker.Formatter { public String format(int value) { return String.format("%02d", value); } } public static class FourDigitFormatter implements NumberPicker.Formatter { public String format(int value) { return String.format("%04d", value); } } public static class MonthFormatter implements NumberPicker.Formatter { String[] months = new String[12]; public MonthFormatter(){ DateFormatSymbols dfs = new DateFormatSymbols(); months = dfs.getShortMonths(); } public void setMonthValues(String[] months){ this.months = months; } public String format(int value) { String month = months[value]; return String.format("%s",month); } } public static TwoDigitFormatter TWO_DIGIT_FORMATTER = new TwoDigitFormatter(); public static FourDigitFormatter FOUR_DIGIT_FORMATTER = new FourDigitFormatter(); public static MonthFormatter MONTH_FORMATTER = new MonthFormatter(); /* 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; /** * 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(CustomDatePicker view, int year, int monthOfYear, int dayOfMonth); } public CustomDatePicker(Context context) { this(context, null); } public CustomDatePicker(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomDatePicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.widget_custom_date_picker, this, true); mDayPicker = (NumberPicker) findViewById(R.id.day); mDayPicker.setFormatter(TWO_DIGIT_FORMATTER); mDayPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mDay = newVal; if (mOnDateChangedListener != null) { mOnDateChangedListener.onDateChanged(CustomDatePicker.this, mYear, mMonth, mDay); } } }); mMonthPicker = (NumberPicker) findViewById(R.id.month); DateFormatSymbols dfs = new DateFormatSymbols(); String[] months = dfs.getShortMonths(); mMonthPicker.setMaxValue(12); mMonthPicker.setMinValue(1); mMonthPicker.setDisplayedValues(months); mMonthPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { public void onValueChange(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(); if (mOnDateChangedListener != null) { mOnDateChangedListener.onDateChanged(CustomDatePicker.this, mYear, mMonth, mDay); } updateDaySpinner(); } }); mYearPicker = (NumberPicker) findViewById(R.id.year); mYearPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mYear = newVal; // Adjust max day for leap years if needed adjustMaxDay(); if (mOnDateChangedListener != null) { mOnDateChangedListener.onDateChanged(CustomDatePicker.this, mYear, mMonth, mDay); } updateDaySpinner(); } }); // attributes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomDatePicker); int mStartYear = a.getInt(R.styleable.CustomDatePicker_startYear, DEFAULT_START_YEAR); int mEndYear = a.getInt(R.styleable.CustomDatePicker_endYear, DEFAULT_END_YEAR); mYearPicker.setMinValue(mStartYear); mYearPicker.setMaxValue(mEndYear); a.recycle(); // initialize to current date Calendar cal = Calendar.getInstance(); if(!isInEditMode()) { init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null); } else { init(mStartYear, 1, 1, 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 == DATE && !didDay) { parent.addView(mDayPicker); didDay = true; } else if ((c == MONTH || c == 'L') && !didMonth) { parent.addView(mMonthPicker); didMonth = true; } else if (c == 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) { mYear = year; mMonth = monthOfYear; mDay = dayOfMonth; updateSpinners(); reorderPickers(new DateFormatSymbols().getShortMonths()); } 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(); } /** * 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.setValue(mYear); /* The month display uses 1-12 but our internal state stores it * 0-11 so add one when setting the display. */ mMonthPicker.setValue(mMonth + 1); } private void updateDaySpinner() { Calendar cal = Calendar.getInstance(); cal.set(mYear, mMonth, mDay); int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH); mDayPicker.setMinValue(1); mDayPicker.setMaxValue(max); mDayPicker.setValue(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; } } public void setMonthResource(int resId){ String[] months = getResources().getStringArray(resId); mMonthPicker.setDisplayedValues(months); } public void setMonthValues(String[] values){ mMonthPicker.setDisplayedValues(values); } }