blob: a7ae926ed467696eabbd9cc9c418607693f9f97d [file] [log] [blame]
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Alan Viverette0ef59ac2015-03-23 13:13:25 -070019import com.android.internal.widget.ViewPager;
20import com.android.internal.R;
21
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070022import android.content.Context;
23import android.content.res.ColorStateList;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070024import android.content.res.TypedArray;
25import android.util.AttributeSet;
Alan Viverette50eb0252014-10-24 14:34:26 -070026import android.util.MathUtils;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070027
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070028import java.util.Calendar;
29import java.util.Locale;
30
Alan Viverette0ef59ac2015-03-23 13:13:25 -070031import libcore.icu.LocaleData;
32
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070033/**
34 * This displays a list of months in a calendar format with selectable days.
35 */
Alan Viverette0ef59ac2015-03-23 13:13:25 -070036class DayPickerView extends ViewPager {
37 private static final int DEFAULT_START_YEAR = 1900;
38 private static final int DEFAULT_END_YEAR = 2100;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070039
Alan Viverette0ef59ac2015-03-23 13:13:25 -070040 private final Calendar mSelectedDay = Calendar.getInstance();
41 private final Calendar mMinDate = Calendar.getInstance();
42 private final Calendar mMaxDate = Calendar.getInstance();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070043
Alan Viverette0ef59ac2015-03-23 13:13:25 -070044 private final DayPickerAdapter mAdapter;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070045
Alan Viverette0ef59ac2015-03-23 13:13:25 -070046 /** Temporary calendar used for date calculations. */
Alan Viverette46127402014-11-13 10:50:37 -080047 private Calendar mTempCalendar;
48
Alan Viverettee763c9b2014-11-06 15:22:31 -080049 private OnDaySelectedListener mOnDaySelectedListener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070050
Alan Viverettee763c9b2014-11-06 15:22:31 -080051 public DayPickerView(Context context) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070052 this(context, null);
53 }
54
55 public DayPickerView(Context context, AttributeSet attrs) {
56 this(context, attrs, R.attr.calendarViewStyle);
57 }
58
59 public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
60 this(context, attrs, defStyleAttr, 0);
61 }
62
63 public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
64 super(context, attrs, defStyleAttr, defStyleRes);
65
66 final TypedArray a = context.obtainStyledAttributes(attrs,
67 R.styleable.CalendarView, defStyleAttr, defStyleRes);
68
69 final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
70 LocaleData.get(Locale.getDefault()).firstDayOfWeek);
71
72 final String minDate = a.getString(R.styleable.CalendarView_minDate);
73 final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
74
75 final int monthTextAppearanceResId = a.getResourceId(
76 R.styleable.CalendarView_monthTextAppearance,
77 R.style.TextAppearance_Material_Widget_Calendar_Month);
78 final int dayOfWeekTextAppearanceResId = a.getResourceId(
79 R.styleable.CalendarView_weekDayTextAppearance,
80 R.style.TextAppearance_Material_Widget_Calendar_DayOfWeek);
81 final int dayTextAppearanceResId = a.getResourceId(
82 R.styleable.CalendarView_dateTextAppearance,
83 R.style.TextAppearance_Material_Widget_Calendar_Day);
84
85 final ColorStateList daySelectorColor = a.getColorStateList(
86 R.styleable.CalendarView_daySelectorColor);
87
88 a.recycle();
89
90 // Set up adapter.
91 mAdapter = new DayPickerAdapter(context);
92 mAdapter.setMonthTextAppearance(monthTextAppearanceResId);
93 mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId);
94 mAdapter.setDayTextAppearance(dayTextAppearanceResId);
95 mAdapter.setDaySelectorColor(daySelectorColor);
Alan Viverette50eb0252014-10-24 14:34:26 -070096
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070097 setAdapter(mAdapter);
Alan Viverettee763c9b2014-11-06 15:22:31 -080098
Alan Viverette0ef59ac2015-03-23 13:13:25 -070099 // Set up min and max dates.
100 final Calendar tempDate = Calendar.getInstance();
101 if (!CalendarView.parseDate(minDate, tempDate)) {
102 tempDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
103 }
104 final long minDateMillis = tempDate.getTimeInMillis();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800105
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700106 if (!CalendarView.parseDate(maxDate, tempDate)) {
107 tempDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
108 }
109 final long maxDateMillis = tempDate.getTimeInMillis();
110
111 if (maxDateMillis < minDateMillis) {
112 throw new IllegalArgumentException("maxDate must be >= minDate");
113 }
114
115 final long setDateMillis = MathUtils.constrain(
116 System.currentTimeMillis(), minDateMillis, maxDateMillis);
117
118 setFirstDayOfWeek(firstDayOfWeek);
119 setMinDate(minDateMillis);
120 setMaxDate(maxDateMillis);
121 setDate(setDateMillis, false);
122
123 // Proxy selection callbacks to our own listener.
124 mAdapter.setOnDaySelectedListener(new DayPickerAdapter.OnDaySelectedListener() {
125 @Override
126 public void onDaySelected(DayPickerAdapter adapter, Calendar day) {
127 if (mOnDaySelectedListener != null) {
128 mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
129 }
130 }
131 });
132 }
133
134 public void setDayOfWeekTextAppearance(int resId) {
135 mAdapter.setDayOfWeekTextAppearance(resId);
136 }
137
138 public int getDayOfWeekTextAppearance() {
139 return mAdapter.getDayOfWeekTextAppearance();
140 }
141
142 public void setDayTextAppearance(int resId) {
143 mAdapter.setDayTextAppearance(resId);
144 }
145
146 public int getDayTextAppearance() {
147 return mAdapter.getDayTextAppearance();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800148 }
149
Alan Viverette46127402014-11-13 10:50:37 -0800150 /**
151 * Sets the currently selected date to the specified timestamp. Jumps
152 * immediately to the new date. To animate to the new date, use
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700153 * {@link #setDate(long, boolean)}.
Alan Viverette46127402014-11-13 10:50:37 -0800154 *
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700155 * @param timeInMillis the target day in milliseconds
Alan Viverette46127402014-11-13 10:50:37 -0800156 */
157 public void setDate(long timeInMillis) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700158 setDate(timeInMillis, false);
Alan Viverette46127402014-11-13 10:50:37 -0800159 }
160
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700161 /**
162 * Sets the currently selected date to the specified timestamp. Jumps
163 * immediately to the new date, optionally animating the transition.
164 *
165 * @param timeInMillis the target day in milliseconds
166 * @param animate whether to smooth scroll to the new position
167 */
168 public void setDate(long timeInMillis, boolean animate) {
169 setDate(timeInMillis, animate, true);
170 }
171
172 /**
173 * Moves to the month containing the specified day, optionally setting the
174 * day as selected.
175 *
176 * @param timeInMillis the target day in milliseconds
177 * @param animate whether to smooth scroll to the new position
178 * @param setSelected whether to set the specified day as selected
179 */
180 private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
181 // Set the selected day
182 if (setSelected) {
183 mSelectedDay.setTimeInMillis(timeInMillis);
184 }
185
186 final int position = getPositionFromDay(timeInMillis);
187 if (position != getCurrentItem()) {
188 setCurrentItem(position, animate);
189 }
Alan Viverette46127402014-11-13 10:50:37 -0800190 }
191
192 public long getDate() {
193 return mSelectedDay.getTimeInMillis();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800194 }
195
196 public void setFirstDayOfWeek(int firstDayOfWeek) {
197 mAdapter.setFirstDayOfWeek(firstDayOfWeek);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700198 }
199
Alan Viverette46127402014-11-13 10:50:37 -0800200 public int getFirstDayOfWeek() {
201 return mAdapter.getFirstDayOfWeek();
202 }
Alan Viverette50eb0252014-10-24 14:34:26 -0700203
Alan Viverette46127402014-11-13 10:50:37 -0800204 public void setMinDate(long timeInMillis) {
205 mMinDate.setTimeInMillis(timeInMillis);
206 onRangeChanged();
207 }
208
209 public long getMinDate() {
210 return mMinDate.getTimeInMillis();
211 }
212
213 public void setMaxDate(long timeInMillis) {
214 mMaxDate.setTimeInMillis(timeInMillis);
215 onRangeChanged();
216 }
217
218 public long getMaxDate() {
219 return mMaxDate.getTimeInMillis();
220 }
221
222 /**
223 * Handles changes to date range.
224 */
225 public void onRangeChanged() {
Alan Viverette50eb0252014-10-24 14:34:26 -0700226 mAdapter.setRange(mMinDate, mMaxDate);
227
Alan Viverette5ecbfeb2014-11-03 18:31:36 -0800228 // Changing the min/max date changes the selection position since we
Alan Viverette46127402014-11-13 10:50:37 -0800229 // don't really have stable IDs. Jumps immediately to the new position.
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700230 setDate(mSelectedDay.getTimeInMillis(), false, false);
Alan Viverette50eb0252014-10-24 14:34:26 -0700231 }
232
233 /**
Alan Viverettee763c9b2014-11-06 15:22:31 -0800234 * Sets the listener to call when the user selects a day.
Alan Viverette50eb0252014-10-24 14:34:26 -0700235 *
Alan Viverettee763c9b2014-11-06 15:22:31 -0800236 * @param listener The listener to call.
Alan Viverette50eb0252014-10-24 14:34:26 -0700237 */
Alan Viverettee763c9b2014-11-06 15:22:31 -0800238 public void setOnDaySelectedListener(OnDaySelectedListener listener) {
239 mOnDaySelectedListener = listener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700240 }
241
Alan Viverette50eb0252014-10-24 14:34:26 -0700242 private int getDiffMonths(Calendar start, Calendar end) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700243 final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700244 return end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700245 }
246
Alan Viverette46127402014-11-13 10:50:37 -0800247 private int getPositionFromDay(long timeInMillis) {
Alan Viverette50eb0252014-10-24 14:34:26 -0700248 final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate);
Alan Viverette46127402014-11-13 10:50:37 -0800249 final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis));
Alan Viverette50eb0252014-10-24 14:34:26 -0700250 return MathUtils.constrain(diffMonth, 0, diffMonthMax);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700251 }
252
Alan Viverette46127402014-11-13 10:50:37 -0800253 private Calendar getTempCalendarForTime(long timeInMillis) {
254 if (mTempCalendar == null) {
255 mTempCalendar = Calendar.getInstance();
256 }
257 mTempCalendar.setTimeInMillis(timeInMillis);
258 return mTempCalendar;
259 }
260
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700261 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700262 * Gets the position of the view that is most prominently displayed within the list view.
263 */
264 public int getMostVisiblePosition() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700265 return getCurrentItem();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700266 }
Alan Viverettee763c9b2014-11-06 15:22:31 -0800267
268 public interface OnDaySelectedListener {
269 public void onDaySelected(DayPickerView view, Calendar day);
270 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700271}