Created
January 6, 2017 11:20
-
-
Save VladSumtsov/ba7f6cf56622d41bc1b2114b26279799 to your computer and use it in GitHub Desktop.
Port of the https://github.com/wdullaer/MaterialDateTimePicker
to view.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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