Skip to content

Instantly share code, notes, and snippets.

@VladSumtsov
Created January 6, 2017 11:20
Show Gist options
  • Save VladSumtsov/ba7f6cf56622d41bc1b2114b26279799 to your computer and use it in GitHub Desktop.
Save VladSumtsov/ba7f6cf56622d41bc1b2114b26279799 to your computer and use it in GitHub Desktop.
package de.dom.android.ui.widget;
/*
* Copyright (C) 2013 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.
*/
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.wdullaer.materialdatetimepicker.HapticFeedbackController;
import com.wdullaer.materialdatetimepicker.TypefaceHelper;
import com.wdullaer.materialdatetimepicker.Utils;
import com.wdullaer.materialdatetimepicker.date.AccessibleDateAnimator;
import com.wdullaer.materialdatetimepicker.date.DatePickerController;
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog;
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog.OnDateChangedListener;
import com.wdullaer.materialdatetimepicker.date.DayPickerView;
import com.wdullaer.materialdatetimepicker.date.MonthAdapter;
import com.wdullaer.materialdatetimepicker.date.SimpleDayPickerView;
import com.wdullaer.materialdatetimepicker.date.YearPickerView;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Locale;
import rx.functions.Action0;
import static com.wdullaer.materialdatetimepicker.date.DatePickerDialog.Version.VERSION_1;
import static com.wdullaer.materialdatetimepicker.date.DatePickerDialog.Version.VERSION_2;
/**
* DatePickerDialog fragment replacement. Was rewritter to View to avoid using fragments in no-fragments apps.
* Based on
*
* @see com.wdullaer.materialdatetimepicker.date.DatePickerDialog
*/
public class DatePickerDialogView extends FrameLayout implements View.OnClickListener, DatePickerController {
private static final String TAG = "DatePickerDialog";
public enum Version {
VERSION_1,
VERSION_2
}
private static final int UNINITIALIZED = -1;
private static final int MONTH_AND_DAY_VIEW = 0;
private static final int YEAR_VIEW = 1;
private static final String KEY_SELECTED_YEAR = "year";
private static final String KEY_SELECTED_MONTH = "month";
private static final String KEY_SELECTED_DAY = "day";
private static final String KEY_LIST_POSITION = "list_position";
private static final String KEY_WEEK_START = "week_start";
private static final String KEY_YEAR_START = "year_start";
private static final String KEY_YEAR_END = "year_end";
private static final String KEY_CURRENT_VIEW = "current_view";
private static final String KEY_LIST_POSITION_OFFSET = "list_position_offset";
private static final String KEY_MIN_DATE = "min_date";
private static final String KEY_MAX_DATE = "max_date";
private static final String KEY_HIGHLIGHTED_DAYS = "highlighted_days";
private static final String KEY_SELECTABLE_DAYS = "selectable_days";
private static final String KEY_DISABLED_DAYS = "disabled_days";
private static final String KEY_THEME_DARK = "theme_dark";
private static final String KEY_THEME_DARK_CHANGED = "theme_dark_changed";
private static final String KEY_ACCENT = "accent";
private static final String KEY_VIBRATE = "vibrate";
private static final String KEY_DISMISS = "dismiss";
private static final String KEY_AUTO_DISMISS = "auto_dismiss";
private static final String KEY_DEFAULT_VIEW = "default_view";
private static final String KEY_TITLE = "title";
private static final String KEY_OK_RESID = "ok_resid";
private static final String KEY_OK_STRING = "ok_string";
private static final String KEY_CANCEL_RESID = "cancel_resid";
private static final String KEY_CANCEL_STRING = "cancel_string";
private static final String KEY_VERSION = "version";
private static final int DEFAULT_START_YEAR = 1900;
private static final int DEFAULT_END_YEAR = 2100;
private static final int ANIMATION_DURATION = 300;
private static final int ANIMATION_DELAY = 500;
private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
private static SimpleDateFormat MONTH_FORMAT = new SimpleDateFormat("MMM", Locale.getDefault());
private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault());
private static SimpleDateFormat VERSION_2_FORMAT;
private final Calendar calendar = trimToMidnight(Calendar.getInstance());
private OnDateSetListener callback;
private HashSet<OnDateChangedListener> listeners = new HashSet<>();
private AccessibleDateAnimator animator;
private TextView datepickerheaderview;
private LinearLayout monthanddayview;
private TextView selectedmonthtextview;
private TextView selecteddaytextview;
private TextView yearview;
private DayPickerView daypickerview;
private YearPickerView yearpickerview;
private int currentview = UNINITIALIZED;
private int weekstart = calendar.getFirstDayOfWeek();
private int minyear = DEFAULT_START_YEAR;
private int maxyear = DEFAULT_END_YEAR;
private String title;
private Calendar mindate;
private Calendar maxdate;
private Calendar[] highlightedDays;
private Calendar[] selectableDays;
private Calendar[] disabledDays;
private boolean themedark = false;
private boolean themedarkchanged = false;
private int accentcolor = -1;
private boolean vibrate = true;
private boolean dismissonpause = false;
private boolean autodismiss = false;
private int defaultview = MONTH_AND_DAY_VIEW;
private int okresid = com.wdullaer.materialdatetimepicker.R.string.mdtp_ok;
private String okstring;
private int cancelresid = com.wdullaer.materialdatetimepicker.R.string.mdtp_cancel;
private String cancelstring;
private DatePickerDialog.Version version;
int listPosition = -1;
int listPositionOffset = 0;
int currentView = defaultview;
private HapticFeedbackController hapticfeedbackcontroller;
private boolean delayanimation = true;
// Accessibility strings.
private String daypickerdescription;
private String selectday;
private String yearpickerdescription;
private String selectyear;
private boolean cancelable;
private Action0 cancelCallback = () -> { };
/**
* @param context
* @param calendar The initial date of the view.
* @param calendar The initial date of the view.
*/
public DatePickerDialogView(Context context, Calendar calendar) {
super(context);
if (calendar == null) {
this.calendar.setTimeInMillis(System.currentTimeMillis());
} else {
this.calendar.setTimeInMillis(calendar.getTimeInMillis());
}
version = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? VERSION_1 : VERSION_2;
if (Build.VERSION.SDK_INT < 18) {
VERSION_2_FORMAT = new SimpleDateFormat(getContext().getResources().getString(com.wdullaer.materialdatetimepicker.R.string.mdtp_date_v2_daymonthyear), Locale.getDefault());
} else {
VERSION_2_FORMAT = new SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "EEEMMMdd"), Locale.getDefault());
}
}
public void setOnDateSet(OnDateSetListener callback) {
this.callback = callback;
}
public void setOnCanceled(Action0 callback) {
this.cancelCallback = callback;
}
/**
* The callback used to indicate the user is done filling in the date.
*/
public interface OnDateSetListener {
/**
* @param calendar The date that was set.
*/
void onDateSet(Calendar calendar);
}
public void setCancelable(boolean cancelable) {
this.cancelable = cancelable;
}
@Override protected void onAttachedToWindow() {
currentview = UNINITIALIZED;
LayoutInflater inflater = LayoutInflater.from(getContext());
int viewRes = version == VERSION_1
? com.wdullaer.materialdatetimepicker.R.layout.mdtp_date_picker_dialog
: com.wdullaer.materialdatetimepicker.R.layout.mdtp_date_picker_dialog_v2;
View view = inflater.inflate(viewRes, this, false);
// All options have been set at this point: round the initial selection if necessary
setToNearestDate(calendar);
datepickerheaderview = (TextView) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.date_picker_header);
monthanddayview = (LinearLayout) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.date_picker_month_and_day);
monthanddayview.setOnClickListener(this);
selectedmonthtextview = (TextView) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.date_picker_month);
selecteddaytextview = (TextView) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.date_picker_day);
yearview = (TextView) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.date_picker_year);
yearview.setOnClickListener(this);
daypickerview = new SimpleDayPickerView(getContext(), this);
yearpickerview = new YearPickerView(getContext(), this);
// if theme mode has not been set by java code, check if it is specified in Style.xml
if (!themedarkchanged) {
themedark = Utils.isDarkTheme(getContext(), themedark);
}
Resources res = getResources();
daypickerdescription = res.getString(com.wdullaer.materialdatetimepicker.R.string.mdtp_day_picker_description);
selectday = res.getString(com.wdullaer.materialdatetimepicker.R.string.mdtp_select_day);
yearpickerdescription = res.getString(com.wdullaer.materialdatetimepicker.R.string.mdtp_year_picker_description);
selectyear = res.getString(com.wdullaer.materialdatetimepicker.R.string.mdtp_select_year);
int bgColorResource = themedark
? com.wdullaer.materialdatetimepicker.R.color.mdtp_date_picker_view_animator_dark_theme
: com.wdullaer.materialdatetimepicker.R.color.mdtp_date_picker_view_animator;
view.setBackgroundColor(ContextCompat.getColor(getContext(), bgColorResource));
animator = (AccessibleDateAnimator) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.animator);
animator.addView(daypickerview);
animator.addView(yearpickerview);
animator.setDateMillis(calendar.getTimeInMillis());
// TODO: Replace with animation decided upon by the design team.
Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(ANIMATION_DURATION);
animator.setInAnimation(animation);
// TODO: Replace with animation decided upon by the design team.
Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
animation2.setDuration(ANIMATION_DURATION);
animator.setOutAnimation(animation2);
Button okButton = (Button) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.ok);
okButton.setOnClickListener(v -> {
tryVibrate();
notifyOnDateListener();
});
okButton.setTypeface(TypefaceHelper.get(getContext(), "Roboto-Medium"));
if (okstring != null)
okButton.setText(okstring);
else
okButton.setText(okresid);
Button cancelButton = (Button) view.findViewById(com.wdullaer.materialdatetimepicker.R.id.cancel);
cancelButton.setOnClickListener(v -> {
tryVibrate();
cancelCallback.call();
});
cancelButton.setTypeface(TypefaceHelper.get(getContext(), "Roboto-Medium"));
if (cancelstring != null)
cancelButton.setText(cancelstring);
else
cancelButton.setText(cancelresid);
cancelButton.setVisibility(cancelable ? View.VISIBLE : View.GONE);
// If an accent color has not been set manually, get it from the context
if (accentcolor == -1) {
accentcolor = Utils.getAccentColorFromThemeIfAvailable(getContext());
}
if (datepickerheaderview != null)
datepickerheaderview.setBackgroundColor(Utils.darkenColor(accentcolor));
view.findViewById(com.wdullaer.materialdatetimepicker.R.id.day_picker_selected_date_layout).setBackgroundColor(accentcolor);
okButton.setTextColor(accentcolor);
cancelButton.setTextColor(accentcolor);
updateDisplay(false);
setCurrentView(currentView);
if (listPosition != -1) {
if (currentView == MONTH_AND_DAY_VIEW) {
daypickerview.postSetSelection(listPosition);
} else if (currentView == YEAR_VIEW) {
yearpickerview.postSetSelectionFromTop(listPosition, listPositionOffset);
}
}
hapticfeedbackcontroller = new HapticFeedbackController(getContext());
addView(view);
hapticfeedbackcontroller.start();
}
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putInt(KEY_SELECTED_YEAR, calendar.get(Calendar.YEAR));
outState.putInt(KEY_SELECTED_MONTH, calendar.get(Calendar.MONTH));
outState.putInt(KEY_SELECTED_DAY, calendar.get(Calendar.DAY_OF_MONTH));
outState.putInt(KEY_WEEK_START, weekstart);
outState.putInt(KEY_YEAR_START, minyear);
outState.putInt(KEY_YEAR_END, maxyear);
outState.putInt(KEY_CURRENT_VIEW, currentview);
int listPosition = -1;
if (currentview == MONTH_AND_DAY_VIEW) {
listPosition = daypickerview.getMostVisiblePosition();
} else if (currentview == YEAR_VIEW) {
listPosition = yearpickerview.getFirstVisiblePosition();
outState.putInt(KEY_LIST_POSITION_OFFSET, yearpickerview.getFirstPositionOffset());
}
outState.putInt(KEY_LIST_POSITION, listPosition);
outState.putSerializable(KEY_MIN_DATE, mindate);
outState.putSerializable(KEY_MAX_DATE, maxdate);
outState.putSerializable(KEY_HIGHLIGHTED_DAYS, highlightedDays);
outState.putSerializable(KEY_SELECTABLE_DAYS, selectableDays);
outState.putSerializable(KEY_DISABLED_DAYS, disabledDays);
outState.putBoolean(KEY_THEME_DARK, themedark);
outState.putBoolean(KEY_THEME_DARK_CHANGED, themedarkchanged);
outState.putInt(KEY_ACCENT, accentcolor);
outState.putBoolean(KEY_VIBRATE, vibrate);
outState.putBoolean(KEY_DISMISS, dismissonpause);
outState.putBoolean(KEY_AUTO_DISMISS, autodismiss);
outState.putInt(KEY_DEFAULT_VIEW, defaultview);
outState.putString(KEY_TITLE, title);
outState.putInt(KEY_OK_RESID, okresid);
outState.putString(KEY_OK_STRING, okstring);
outState.putInt(KEY_CANCEL_RESID, cancelresid);
outState.putString(KEY_CANCEL_STRING, cancelstring);
outState.putSerializable(KEY_VERSION, version);
}
private void onRestoreState(Bundle savedInstanceState) {
weekstart = savedInstanceState.getInt(KEY_WEEK_START);
minyear = savedInstanceState.getInt(KEY_YEAR_START);
maxyear = savedInstanceState.getInt(KEY_YEAR_END);
currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW);
listPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
listPositionOffset = savedInstanceState.getInt(KEY_LIST_POSITION_OFFSET);
mindate = (Calendar) savedInstanceState.getSerializable(KEY_MIN_DATE);
maxdate = (Calendar) savedInstanceState.getSerializable(KEY_MAX_DATE);
highlightedDays = (Calendar[]) savedInstanceState.getSerializable(KEY_HIGHLIGHTED_DAYS);
selectableDays = (Calendar[]) savedInstanceState.getSerializable(KEY_SELECTABLE_DAYS);
disabledDays = (Calendar[]) savedInstanceState.getSerializable(KEY_DISABLED_DAYS);
themedark = savedInstanceState.getBoolean(KEY_THEME_DARK);
themedarkchanged = savedInstanceState.getBoolean(KEY_THEME_DARK_CHANGED);
accentcolor = savedInstanceState.getInt(KEY_ACCENT);
vibrate = savedInstanceState.getBoolean(KEY_VIBRATE);
dismissonpause = savedInstanceState.getBoolean(KEY_DISMISS);
autodismiss = savedInstanceState.getBoolean(KEY_AUTO_DISMISS);
title = savedInstanceState.getString(KEY_TITLE);
okresid = savedInstanceState.getInt(KEY_OK_RESID);
okstring = savedInstanceState.getString(KEY_OK_STRING);
cancelresid = savedInstanceState.getInt(KEY_CANCEL_RESID);
cancelstring = savedInstanceState.getString(KEY_CANCEL_STRING);
version = (DatePickerDialog.Version) savedInstanceState.getSerializable(KEY_VERSION);
calendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR));
calendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH));
calendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY));
defaultview = savedInstanceState.getInt(KEY_DEFAULT_VIEW);
}
@Override protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
hapticfeedbackcontroller.stop();
}
private void setCurrentView(final int viewIndex) {
long millis = calendar.getTimeInMillis();
switch (viewIndex) {
case MONTH_AND_DAY_VIEW:
if (version == VERSION_1) {
ObjectAnimator pulseAnimator = Utils.getPulseAnimator(monthanddayview, 0.9f,
1.05f);
if (delayanimation) {
pulseAnimator.setStartDelay(ANIMATION_DELAY);
delayanimation = false;
}
daypickerview.onDateChanged();
if (currentview != viewIndex) {
monthanddayview.setSelected(true);
yearview.setSelected(false);
animator.setDisplayedChild(MONTH_AND_DAY_VIEW);
currentview = viewIndex;
}
pulseAnimator.start();
} else {
daypickerview.onDateChanged();
if (currentview != viewIndex) {
monthanddayview.setSelected(true);
yearview.setSelected(false);
animator.setDisplayedChild(MONTH_AND_DAY_VIEW);
currentview = viewIndex;
}
}
int flags = DateUtils.FORMAT_SHOW_DATE;
String dayString = DateUtils.formatDateTime(getContext(), millis, flags);
animator.setContentDescription(daypickerdescription + ": " + dayString);
Utils.tryAccessibilityAnnounce(animator, selectday);
break;
case YEAR_VIEW:
if (version == VERSION_1) {
ObjectAnimator pulseAnimator = Utils.getPulseAnimator(yearview, 0.85f, 1.1f);
if (delayanimation) {
pulseAnimator.setStartDelay(ANIMATION_DELAY);
delayanimation = false;
}
yearpickerview.onDateChanged();
if (currentview != viewIndex) {
monthanddayview.setSelected(false);
yearview.setSelected(true);
animator.setDisplayedChild(YEAR_VIEW);
currentview = viewIndex;
}
pulseAnimator.start();
} else {
yearpickerview.onDateChanged();
if (currentview != viewIndex) {
monthanddayview.setSelected(false);
yearview.setSelected(true);
animator.setDisplayedChild(YEAR_VIEW);
currentview = viewIndex;
}
}
CharSequence yearString = YEAR_FORMAT.format(millis);
animator.setContentDescription(yearpickerdescription + ": " + yearString);
Utils.tryAccessibilityAnnounce(animator, selectyear);
break;
}
}
private void updateDisplay(boolean announce) {
yearview.setText(YEAR_FORMAT.format(calendar.getTime()));
if (version == VERSION_1) {
if (datepickerheaderview != null) {
if (title != null)
datepickerheaderview.setText(title.toUpperCase(Locale.getDefault()));
else {
datepickerheaderview.setText(calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
Locale.getDefault()).toUpperCase(Locale.getDefault()));
}
}
selectedmonthtextview.setText(MONTH_FORMAT.format(calendar.getTime()));
selecteddaytextview.setText(DAY_FORMAT.format(calendar.getTime()));
}
if (version == VERSION_2) {
selecteddaytextview.setText(VERSION_2_FORMAT.format(calendar.getTime()));
if (title != null)
datepickerheaderview.setText(title.toUpperCase(Locale.getDefault()));
else
datepickerheaderview.setVisibility(View.GONE);
}
// Accessibility.
long millis = calendar.getTimeInMillis();
animator.setDateMillis(millis);
int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
String monthAndDayText = DateUtils.formatDateTime(getContext(), millis, flags);
monthanddayview.setContentDescription(monthAndDayText);
if (announce) {
flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
String fullDateText = DateUtils.formatDateTime(getContext(), millis, flags);
Utils.tryAccessibilityAnnounce(animator, fullDateText);
}
}
/**
* Set whether the device should vibrate when touching fields
*
* @param vibrate true if the device should vibrate when touching a field
*/
public void vibrate(boolean vibrate) {
vibrate = vibrate;
}
/**
* Set whether the picker should dismiss itself when being paused or whether it should try to survive an orientation change
*
* @param dismissOnPause true if the dialog should dismiss itself when it's pausing
*/
public void dismissOnPause(boolean dismissOnPause) {
dismissonpause = dismissOnPause;
}
/**
* Set whether the picker should dismiss itself when a day is selected
*
* @param autoDismiss true if the dialog should dismiss itself when a day is selected
*/
@SuppressWarnings("unused")
public void autoDismiss(boolean autoDismiss) {
autodismiss = autoDismiss;
}
/**
* Set whether the dark theme should be used
*
* @param themeDark true if the dark theme should be used, false if the default theme should be used
*/
public void setThemeDark(boolean themeDark) {
themedark = themeDark;
themedarkchanged = true;
}
/**
* Returns true when the dark theme should be used
*
* @return true if the dark theme should be used, false if the default theme should be used
*/
@Override
public boolean isThemeDark() {
return themedark;
}
/**
* Set the accent color of this dialog
*
* @param color the accent color you want
*/
@SuppressWarnings("unused")
public void setAccentColor(String color) {
accentcolor = Color.parseColor(color);
}
/**
* Set the accent color of this dialog
*
* @param color the accent color you want
*/
public void setAccentColor(@ColorInt int color) {
accentcolor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
}
/**
* Get the accent color of this dialog
*
* @return accent color
*/
@Override
public int getAccentColor() {
return accentcolor;
}
/**
* Set whether the year picker of the month and day picker is shown first
*
* @param yearPicker boolean
*/
public void showYearPickerFirst(boolean yearPicker) {
defaultview = yearPicker ? YEAR_VIEW : MONTH_AND_DAY_VIEW;
}
@SuppressWarnings("unused")
public void setFirstDayOfWeek(int startOfWeek) {
if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) {
throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " +
"Calendar.SATURDAY");
}
weekstart = startOfWeek;
if (daypickerview != null) {
daypickerview.onChange();
}
}
@SuppressWarnings("unused")
public void setYearRange(int startYear, int endYear) {
if (endYear < startYear) {
throw new IllegalArgumentException("Year end must be larger than or equal to year start");
}
minyear = startYear;
maxyear = endYear;
if (daypickerview != null) {
daypickerview.onChange();
}
}
/**
* Sets the minimal date supported by this DatePicker. Dates before (but not including) the
* specified date will be disallowed from being selected.
*
* @param calendar a Calendar object set to the year, month, day desired as the mindate.
*/
@SuppressWarnings("unused")
public void setMinDate(Calendar calendar) {
mindate = trimToMidnight(calendar);
if (daypickerview != null) {
daypickerview.onChange();
}
}
/**
* @return The minimal date supported by this DatePicker. Null if it has not been set.
*/
@SuppressWarnings("unused")
public Calendar getMinDate() {
return mindate;
}
/**
* Sets the minimal date supported by this DatePicker. Dates after (but not including) the
* specified date will be disallowed from being selected.
*
* @param calendar a Calendar object set to the year, month, day desired as the maxdate.
*/
@SuppressWarnings("unused")
public void setMaxDate(Calendar calendar) {
maxdate = trimToMidnight(calendar);
if (daypickerview != null) {
daypickerview.onChange();
}
}
/**
* @return The maximal date supported by this DatePicker. Null if it has not been set.
*/
@SuppressWarnings("unused")
public Calendar getMaxDate() {
return maxdate;
}
/**
* Sets an array of dates which should be highlighted when the picker is drawn
*
* @param highlightedDays an Array of Calendar objects containing the dates to be highlighted
*/
@SuppressWarnings("unused")
public void setHighlightedDays(Calendar[] highlightedDays) {
Arrays.sort(highlightedDays);
for (Calendar highlightedDay : highlightedDays) { trimToMidnight(highlightedDay); }
this.highlightedDays = highlightedDays;
if (daypickerview != null)
daypickerview.onChange();
}
/**
* @return The list of dates, as Calendar Objects, which should be highlighted. null is no dates should be highlighted
*/
@Override
public Calendar[] getHighlightedDays() {
return highlightedDays;
}
/**
* Sets a list of days which are the only valid selections.
* Setting this value will take precedence over using setMinDate() and setMaxDate()
*
* @param selectableDays an Array of Calendar Objects containing the selectable dates
*/
@SuppressWarnings("unused")
public void setSelectableDays(Calendar[] selectableDays) {
Arrays.sort(selectableDays);
for (Calendar selectableDay : selectableDays) { trimToMidnight(selectableDay); }
this.selectableDays = selectableDays;
if (daypickerview != null)
daypickerview.onChange();
}
/**
* @return an Array of Calendar objects containing the list with selectable items. null if no restriction is set
*/
@SuppressWarnings("unused")
public Calendar[] getSelectableDays() {
return selectableDays;
}
/**
* Sets a list of days that are not selectable in the picker
* Setting this value will take precedence over using setMinDate() and setMaxDate(), but stacks with setSelectableDays()
*
* @param disabledDays an Array of Calendar Objects containing the disabled dates
*/
@SuppressWarnings("unused")
public void setDisabledDays(Calendar[] disabledDays) {
Arrays.sort(disabledDays);
for (Calendar disabledDay : disabledDays) { trimToMidnight(disabledDay); }
this.disabledDays = disabledDays;
if (daypickerview != null)
daypickerview.onChange();
}
/**
* @return an Array of Calendar objects containing the list of days that are not selectable. null if no restriction is set
*/
@SuppressWarnings("unused")
public Calendar[] getDisabledDays() {
return disabledDays;
}
/**
* Set a title to be displayed instead of the weekday
*
* @param title String - The title to be displayed
*/
public void setTitle(String title) {
title = title;
}
/**
* Set the label for the Ok button (max 12 characters)
*
* @param okString A literal String to be used as the Ok button label
*/
@SuppressWarnings("unused")
public void setOkText(String okString) {
okstring = okString;
}
/**
* Set the label for the Ok button (max 12 characters)
*
* @param okResid A resource ID to be used as the Ok button label
*/
@SuppressWarnings("unused")
public void setOkText(@StringRes int okResid) {
okstring = null;
okresid = okResid;
}
/**
* Set the label for the Cancel button (max 12 characters)
*
* @param cancelString A literal String to be used as the Cancel button label
*/
@SuppressWarnings("unused")
public void setCancelText(String cancelString) {
cancelstring = cancelString;
}
/**
* Set the label for the Cancel button (max 12 characters)
*
* @param cancelResid A resource ID to be used as the Cancel button label
*/
@SuppressWarnings("unused")
public void setCancelText(@StringRes int cancelResid) {
cancelstring = null;
cancelresid = cancelResid;
}
/**
* Set which layout version the picker should use
*
* @param version The version to use
*/
public void setVersion(DatePickerDialog.Version version) {
version = version;
}
@SuppressWarnings("unused")
public void setOnDateSetListener(OnDateSetListener listener) {
callback = listener;
}
// If the newly selected month / year does not contain the currently selected day number,
// change the selected day number to the last day of the selected month or year.
// e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
// e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
private void adjustDayInMonthIfNeeded(Calendar calendar) {
int day = calendar.get(Calendar.DAY_OF_MONTH);
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
if (day > daysInMonth) {
calendar.set(Calendar.DAY_OF_MONTH, daysInMonth);
}
setToNearestDate(calendar);
}
@Override
public void onClick(View v) {
tryVibrate();
if (v.getId() == com.wdullaer.materialdatetimepicker.R.id.date_picker_year) {
setCurrentView(YEAR_VIEW);
} else if (v.getId() == com.wdullaer.materialdatetimepicker.R.id.date_picker_month_and_day) {
setCurrentView(MONTH_AND_DAY_VIEW);
}
}
@Override
public void onYearSelected(int year) {
calendar.set(Calendar.YEAR, year);
adjustDayInMonthIfNeeded(calendar);
updatePickers();
setCurrentView(MONTH_AND_DAY_VIEW);
updateDisplay(true);
}
@Override
public void onDayOfMonthSelected(int year, int month, int day) {
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month);
calendar.set(Calendar.DAY_OF_MONTH, day);
updatePickers();
updateDisplay(true);
if (autodismiss) {
notifyOnDateListener();
}
}
private void updatePickers() {
for (OnDateChangedListener listener : listeners) { listener.onDateChanged(); }
}
@Override
public MonthAdapter.CalendarDay getSelectedDay() {
return new MonthAdapter.CalendarDay(calendar);
}
@Override
public Calendar getStartDate() {
if (selectableDays != null)
return selectableDays[0];
if (mindate != null)
return mindate;
Calendar output = Calendar.getInstance();
output.set(Calendar.YEAR, minyear);
output.set(Calendar.DAY_OF_MONTH, 1);
output.set(Calendar.MONTH, Calendar.JANUARY);
return output;
}
@Override
public Calendar getEndDate() {
if (selectableDays != null)
return selectableDays[selectableDays.length - 1];
if (maxdate != null)
return maxdate;
Calendar output = Calendar.getInstance();
output.set(Calendar.YEAR, maxyear);
output.set(Calendar.DAY_OF_MONTH, 31);
output.set(Calendar.MONTH, Calendar.DECEMBER);
return output;
}
@Override
public int getMinYear() {
if (selectableDays != null)
return selectableDays[0].get(Calendar.YEAR);
// Ensure no years can be selected outside of the given minimum date
return mindate != null && mindate.get(Calendar.YEAR) > minyear ? mindate.get(Calendar.YEAR) : minyear;
}
@Override
public int getMaxYear() {
if (selectableDays != null)
return selectableDays[selectableDays.length - 1].get(Calendar.YEAR);
// Ensure no years can be selected outside of the given maximum date
return maxdate != null && maxdate.get(Calendar.YEAR) < maxyear ? maxdate.get(Calendar.YEAR) : maxyear;
}
/**
* @return true if the specified year/month/day are within the selectable days or the range set by minDate and maxDate.
* If one or either have not been set, they are considered as Integer.MIN_VALUE and
* Integer.MAX_VALUE.
*/
@Override
public boolean isOutOfRange(int year, int month, int day) {
return isDisabled(year, month, day) || !isSelectable(year, month, day);
}
@SuppressWarnings("unused")
public boolean isOutOfRange(Calendar calendar) {
return isOutOfRange(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
);
}
private boolean isDisabled(int year, int month, int day) {
return containsDate(disabledDays, year, month, day) || isBeforeMin(year, month, day) || isAfterMax(year, month, day);
}
private boolean isDisabled(Calendar c) {
return isDisabled(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
}
private boolean isSelectable(int year, int month, int day) {
return selectableDays == null || containsDate(selectableDays, year, month, day);
}
/**
* Checks whether the given data is contained in an array of dates
*
* @param dates Calendar[] which contents we want to search
* @param year the year as an int
* @param month the month as an int
* @param day the day as an int
* @return true if the data is present in the array
*/
private boolean containsDate(Calendar[] dates, int year, int month, int day) {
if (dates == null)
return false;
for (Calendar c : dates) {
if (year < c.get(Calendar.YEAR))
break;
if (year > c.get(Calendar.YEAR))
continue;
if (month < c.get(Calendar.MONTH))
break;
if (month > c.get(Calendar.MONTH))
continue;
if (day < c.get(Calendar.DAY_OF_MONTH))
break;
if (day > c.get(Calendar.DAY_OF_MONTH))
continue;
return true;
}
return false;
}
private boolean isBeforeMin(int year, int month, int day) {
if (mindate == null) {
return false;
}
if (year < mindate.get(Calendar.YEAR)) {
return true;
} else if (year > mindate.get(Calendar.YEAR)) {
return false;
}
if (month < mindate.get(Calendar.MONTH)) {
return true;
} else if (month > mindate.get(Calendar.MONTH)) {
return false;
}
if (day < mindate.get(Calendar.DAY_OF_MONTH)) {
return true;
} else {
return false;
}
}
private boolean isBeforeMin(Calendar calendar) {
return isBeforeMin(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
);
}
private boolean isAfterMax(int year, int month, int day) {
if (maxdate == null) {
return false;
}
if (year > maxdate.get(Calendar.YEAR)) {
return true;
} else if (year < maxdate.get(Calendar.YEAR)) {
return false;
}
if (month > maxdate.get(Calendar.MONTH)) {
return true;
} else if (month < maxdate.get(Calendar.MONTH)) {
return false;
}
if (day > maxdate.get(Calendar.DAY_OF_MONTH)) {
return true;
} else {
return false;
}
}
private boolean isAfterMax(Calendar calendar) {
return isAfterMax(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
);
}
private void setToNearestDate(Calendar calendar) {
if (selectableDays != null) {
long distance = Long.MAX_VALUE;
Calendar currentBest = calendar;
for (Calendar c : selectableDays) {
long newDistance = Math.abs(calendar.getTimeInMillis() - c.getTimeInMillis());
if (newDistance < distance && !isDisabled(c)) {
distance = newDistance;
currentBest = c;
} else
break;
}
calendar.setTimeInMillis(currentBest.getTimeInMillis());
return;
}
if (disabledDays != null) {
Calendar forwardDate = (Calendar) calendar.clone();
Calendar backwardDate = (Calendar) calendar.clone();
while (isDisabled(forwardDate) && isDisabled(backwardDate)) {
forwardDate.add(Calendar.DAY_OF_MONTH, 1);
backwardDate.add(Calendar.DAY_OF_MONTH, -1);
}
if (!isDisabled(backwardDate)) {
calendar.setTimeInMillis(backwardDate.getTimeInMillis());
return;
}
if (!isDisabled(forwardDate)) {
calendar.setTimeInMillis(forwardDate.getTimeInMillis());
return;
}
}
if (isBeforeMin(calendar)) {
calendar.setTimeInMillis(mindate.getTimeInMillis());
return;
}
if (isAfterMax(calendar)) {
calendar.setTimeInMillis(maxdate.getTimeInMillis());
return;
}
}
/**
* Trims off all time information, effectively setting it to midnight
* Makes it easier to compare at just the day level
*
* @param calendar The Calendar object to trim
* @return The trimmed Calendar object
*/
private Calendar trimToMidnight(Calendar calendar) {
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
@Override
public int getFirstDayOfWeek() {
return weekstart;
}
@Override
public void registerOnDateChangedListener(OnDateChangedListener listener) {
listeners.add(listener);
}
@Override
public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
listeners.remove(listener);
}
@Override
public void tryVibrate() {
if (vibrate)
hapticfeedbackcontroller.tryVibrate();
}
public void notifyOnDateListener() {
if (callback != null) {
callback.onDateSet(calendar);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment