blob: ec2528f156d18d3bb891d1488b59213d936e1f52 [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.
Alan Viverette60b674e2015-03-25 13:00:42 -070091 mAdapter = new DayPickerAdapter(context,
92 R.layout.date_picker_month_item_material, R.id.month_view);
Alan Viverette0ef59ac2015-03-23 13:13:25 -070093 mAdapter.setMonthTextAppearance(monthTextAppearanceResId);
94 mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId);
95 mAdapter.setDayTextAppearance(dayTextAppearanceResId);
96 mAdapter.setDaySelectorColor(daySelectorColor);
Alan Viverette50eb0252014-10-24 14:34:26 -070097
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070098 setAdapter(mAdapter);
Alan Viverettee763c9b2014-11-06 15:22:31 -080099
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700100 // Set up min and max dates.
101 final Calendar tempDate = Calendar.getInstance();
102 if (!CalendarView.parseDate(minDate, tempDate)) {
103 tempDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
104 }
105 final long minDateMillis = tempDate.getTimeInMillis();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800106
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700107 if (!CalendarView.parseDate(maxDate, tempDate)) {
108 tempDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
109 }
110 final long maxDateMillis = tempDate.getTimeInMillis();
111
112 if (maxDateMillis < minDateMillis) {
113 throw new IllegalArgumentException("maxDate must be >= minDate");
114 }
115
116 final long setDateMillis = MathUtils.constrain(
117 System.currentTimeMillis(), minDateMillis, maxDateMillis);
118
119 setFirstDayOfWeek(firstDayOfWeek);
120 setMinDate(minDateMillis);
121 setMaxDate(maxDateMillis);
122 setDate(setDateMillis, false);
123
124 // Proxy selection callbacks to our own listener.
125 mAdapter.setOnDaySelectedListener(new DayPickerAdapter.OnDaySelectedListener() {
126 @Override
127 public void onDaySelected(DayPickerAdapter adapter, Calendar day) {
128 if (mOnDaySelectedListener != null) {
129 mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
130 }
131 }
Alan Viverette60b674e2015-03-25 13:00:42 -0700132
133 @Override
134 public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate) {
135 // ViewPager clamps input values, so we don't need to worry
136 // about passing invalid indices.
137 final int nextItem = getCurrentItem() + direction;
138 setCurrentItem(nextItem, animate);
139 }
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700140 });
141 }
142
143 public void setDayOfWeekTextAppearance(int resId) {
144 mAdapter.setDayOfWeekTextAppearance(resId);
145 }
146
147 public int getDayOfWeekTextAppearance() {
148 return mAdapter.getDayOfWeekTextAppearance();
149 }
150
151 public void setDayTextAppearance(int resId) {
152 mAdapter.setDayTextAppearance(resId);
153 }
154
155 public int getDayTextAppearance() {
156 return mAdapter.getDayTextAppearance();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800157 }
158
Alan Viverette46127402014-11-13 10:50:37 -0800159 /**
160 * Sets the currently selected date to the specified timestamp. Jumps
161 * immediately to the new date. To animate to the new date, use
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700162 * {@link #setDate(long, boolean)}.
Alan Viverette46127402014-11-13 10:50:37 -0800163 *
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700164 * @param timeInMillis the target day in milliseconds
Alan Viverette46127402014-11-13 10:50:37 -0800165 */
166 public void setDate(long timeInMillis) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700167 setDate(timeInMillis, false);
Alan Viverette46127402014-11-13 10:50:37 -0800168 }
169
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700170 /**
171 * Sets the currently selected date to the specified timestamp. Jumps
172 * immediately to the new date, optionally animating the transition.
173 *
174 * @param timeInMillis the target day in milliseconds
175 * @param animate whether to smooth scroll to the new position
176 */
177 public void setDate(long timeInMillis, boolean animate) {
178 setDate(timeInMillis, animate, true);
179 }
180
181 /**
182 * Moves to the month containing the specified day, optionally setting the
183 * day as selected.
184 *
185 * @param timeInMillis the target day in milliseconds
186 * @param animate whether to smooth scroll to the new position
187 * @param setSelected whether to set the specified day as selected
188 */
189 private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700190 if (setSelected) {
191 mSelectedDay.setTimeInMillis(timeInMillis);
192 }
193
194 final int position = getPositionFromDay(timeInMillis);
195 if (position != getCurrentItem()) {
196 setCurrentItem(position, animate);
197 }
Alan Viverettef63757b2015-04-01 17:14:45 -0700198
199 mTempCalendar.setTimeInMillis(timeInMillis);
200 mAdapter.setSelectedDay(mTempCalendar);
Alan Viverette46127402014-11-13 10:50:37 -0800201 }
202
203 public long getDate() {
204 return mSelectedDay.getTimeInMillis();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800205 }
206
207 public void setFirstDayOfWeek(int firstDayOfWeek) {
208 mAdapter.setFirstDayOfWeek(firstDayOfWeek);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700209 }
210
Alan Viverette46127402014-11-13 10:50:37 -0800211 public int getFirstDayOfWeek() {
212 return mAdapter.getFirstDayOfWeek();
213 }
Alan Viverette50eb0252014-10-24 14:34:26 -0700214
Alan Viverette46127402014-11-13 10:50:37 -0800215 public void setMinDate(long timeInMillis) {
216 mMinDate.setTimeInMillis(timeInMillis);
217 onRangeChanged();
218 }
219
220 public long getMinDate() {
221 return mMinDate.getTimeInMillis();
222 }
223
224 public void setMaxDate(long timeInMillis) {
225 mMaxDate.setTimeInMillis(timeInMillis);
226 onRangeChanged();
227 }
228
229 public long getMaxDate() {
230 return mMaxDate.getTimeInMillis();
231 }
232
233 /**
234 * Handles changes to date range.
235 */
236 public void onRangeChanged() {
Alan Viverette50eb0252014-10-24 14:34:26 -0700237 mAdapter.setRange(mMinDate, mMaxDate);
238
Alan Viverette5ecbfeb2014-11-03 18:31:36 -0800239 // Changing the min/max date changes the selection position since we
Alan Viverette46127402014-11-13 10:50:37 -0800240 // don't really have stable IDs. Jumps immediately to the new position.
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700241 setDate(mSelectedDay.getTimeInMillis(), false, false);
Alan Viverette50eb0252014-10-24 14:34:26 -0700242 }
243
244 /**
Alan Viverettee763c9b2014-11-06 15:22:31 -0800245 * Sets the listener to call when the user selects a day.
Alan Viverette50eb0252014-10-24 14:34:26 -0700246 *
Alan Viverettee763c9b2014-11-06 15:22:31 -0800247 * @param listener The listener to call.
Alan Viverette50eb0252014-10-24 14:34:26 -0700248 */
Alan Viverettee763c9b2014-11-06 15:22:31 -0800249 public void setOnDaySelectedListener(OnDaySelectedListener listener) {
250 mOnDaySelectedListener = listener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700251 }
252
Alan Viverette50eb0252014-10-24 14:34:26 -0700253 private int getDiffMonths(Calendar start, Calendar end) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700254 final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700255 return end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700256 }
257
Alan Viverette46127402014-11-13 10:50:37 -0800258 private int getPositionFromDay(long timeInMillis) {
Alan Viverette50eb0252014-10-24 14:34:26 -0700259 final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate);
Alan Viverette46127402014-11-13 10:50:37 -0800260 final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis));
Alan Viverette50eb0252014-10-24 14:34:26 -0700261 return MathUtils.constrain(diffMonth, 0, diffMonthMax);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700262 }
263
Alan Viverette46127402014-11-13 10:50:37 -0800264 private Calendar getTempCalendarForTime(long timeInMillis) {
265 if (mTempCalendar == null) {
266 mTempCalendar = Calendar.getInstance();
267 }
268 mTempCalendar.setTimeInMillis(timeInMillis);
269 return mTempCalendar;
270 }
271
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700272 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700273 * Gets the position of the view that is most prominently displayed within the list view.
274 */
275 public int getMostVisiblePosition() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700276 return getCurrentItem();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700277 }
Alan Viverettee763c9b2014-11-06 15:22:31 -0800278
279 public interface OnDaySelectedListener {
280 public void onDaySelected(DayPickerView view, Calendar day);
281 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700282}