blob: c6b4d7e3aaf3a3327870e0ce079b487f1dda1fe0 [file] [log] [blame]
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -07001/*
Alan Viverette78bf1d32015-04-17 10:39:22 -07002 * Copyright (C) 2015 The Android Open Source Project
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -07003 *
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.R;
Alan Viverette78bf1d32015-04-17 10:39:22 -070020import com.android.internal.widget.ViewPager;
21import com.android.internal.widget.ViewPager.OnPageChangeListener;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070022
Alan Viverette78bf1d32015-04-17 10:39:22 -070023import android.annotation.Nullable;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070024import android.content.Context;
25import android.content.res.ColorStateList;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070026import android.content.res.TypedArray;
27import android.util.AttributeSet;
Alan Viverette50eb0252014-10-24 14:34:26 -070028import android.util.MathUtils;
Alan Viverette78bf1d32015-04-17 10:39:22 -070029import android.view.LayoutInflater;
Alan Viverette816aa142015-04-10 15:41:10 -070030import android.view.View;
Alan Viverette78bf1d32015-04-17 10:39:22 -070031import android.view.ViewGroup;
32import android.view.accessibility.AccessibilityManager;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070033
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070034import java.util.Calendar;
35import java.util.Locale;
36
Alan Viverette0ef59ac2015-03-23 13:13:25 -070037import libcore.icu.LocaleData;
38
Alan Viverette78bf1d32015-04-17 10:39:22 -070039class DayPickerView extends ViewGroup {
40 private static final int DEFAULT_LAYOUT = R.layout.day_picker_content_material;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070041 private static final int DEFAULT_START_YEAR = 1900;
42 private static final int DEFAULT_END_YEAR = 2100;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070043
Alan Viverette78bf1d32015-04-17 10:39:22 -070044 private static final int[] ATTRS_TEXT_COLOR = new int[] { R.attr.textColor };
45
Alan Viverette0ef59ac2015-03-23 13:13:25 -070046 private final Calendar mSelectedDay = Calendar.getInstance();
47 private final Calendar mMinDate = Calendar.getInstance();
48 private final Calendar mMaxDate = Calendar.getInstance();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070049
Alan Viverette78bf1d32015-04-17 10:39:22 -070050 private final AccessibilityManager mAccessibilityManager;
Alan Viverette816aa142015-04-10 15:41:10 -070051
Alan Viverette78bf1d32015-04-17 10:39:22 -070052 private final ViewPager mViewPager;
53 private final ImageButton mPrevButton;
54 private final ImageButton mNextButton;
55
56 private final DayPickerPagerAdapter mAdapter;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070057
Alan Viverette0ef59ac2015-03-23 13:13:25 -070058 /** Temporary calendar used for date calculations. */
Alan Viverette46127402014-11-13 10:50:37 -080059 private Calendar mTempCalendar;
60
Alan Viverettee763c9b2014-11-06 15:22:31 -080061 private OnDaySelectedListener mOnDaySelectedListener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070062
Alan Viverettee763c9b2014-11-06 15:22:31 -080063 public DayPickerView(Context context) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070064 this(context, null);
65 }
66
Alan Viverette78bf1d32015-04-17 10:39:22 -070067 public DayPickerView(Context context, @Nullable AttributeSet attrs) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070068 this(context, attrs, R.attr.calendarViewStyle);
69 }
70
Alan Viverette78bf1d32015-04-17 10:39:22 -070071 public DayPickerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070072 this(context, attrs, defStyleAttr, 0);
73 }
74
Alan Viverette78bf1d32015-04-17 10:39:22 -070075 public DayPickerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
76 int defStyleRes) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070077 super(context, attrs, defStyleAttr, defStyleRes);
78
Alan Viverette78bf1d32015-04-17 10:39:22 -070079 mAccessibilityManager = (AccessibilityManager) context.getSystemService(
80 Context.ACCESSIBILITY_SERVICE);
81
Alan Viverette0ef59ac2015-03-23 13:13:25 -070082 final TypedArray a = context.obtainStyledAttributes(attrs,
83 R.styleable.CalendarView, defStyleAttr, defStyleRes);
84
85 final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
86 LocaleData.get(Locale.getDefault()).firstDayOfWeek);
87
88 final String minDate = a.getString(R.styleable.CalendarView_minDate);
89 final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
90
91 final int monthTextAppearanceResId = a.getResourceId(
92 R.styleable.CalendarView_monthTextAppearance,
93 R.style.TextAppearance_Material_Widget_Calendar_Month);
94 final int dayOfWeekTextAppearanceResId = a.getResourceId(
95 R.styleable.CalendarView_weekDayTextAppearance,
96 R.style.TextAppearance_Material_Widget_Calendar_DayOfWeek);
97 final int dayTextAppearanceResId = a.getResourceId(
98 R.styleable.CalendarView_dateTextAppearance,
99 R.style.TextAppearance_Material_Widget_Calendar_Day);
100
101 final ColorStateList daySelectorColor = a.getColorStateList(
102 R.styleable.CalendarView_daySelectorColor);
103
104 a.recycle();
105
106 // Set up adapter.
Alan Viverette78bf1d32015-04-17 10:39:22 -0700107 mAdapter = new DayPickerPagerAdapter(context,
Alan Viverette60b674e2015-03-25 13:00:42 -0700108 R.layout.date_picker_month_item_material, R.id.month_view);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700109 mAdapter.setMonthTextAppearance(monthTextAppearanceResId);
110 mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId);
111 mAdapter.setDayTextAppearance(dayTextAppearanceResId);
112 mAdapter.setDaySelectorColor(daySelectorColor);
Alan Viverette50eb0252014-10-24 14:34:26 -0700113
Alan Viverette78bf1d32015-04-17 10:39:22 -0700114 final LayoutInflater inflater = LayoutInflater.from(context);
115 final ViewGroup content = (ViewGroup) inflater.inflate(DEFAULT_LAYOUT, this, false);
116
117 // Transfer all children from content to here.
118 while (content.getChildCount() > 0) {
119 final View child = content.getChildAt(0);
120 content.removeViewAt(0);
121 addView(child);
122 }
123
124 mPrevButton = (ImageButton) findViewById(R.id.prev);
125 mPrevButton.setOnClickListener(mOnClickListener);
126
127 mNextButton = (ImageButton) findViewById(R.id.next);
128 mNextButton.setOnClickListener(mOnClickListener);
129
130 mViewPager = (ViewPager) findViewById(R.id.day_picker_view_pager);
131 mViewPager.setAdapter(mAdapter);
132 mViewPager.setOnPageChangeListener(mOnPageChangedListener);
133
134 // Proxy the month text color into the previous and next buttons.
135 if (monthTextAppearanceResId != 0) {
136 final TypedArray ta = mContext.obtainStyledAttributes(null,
137 ATTRS_TEXT_COLOR, 0, monthTextAppearanceResId);
138 final ColorStateList monthColor = ta.getColorStateList(0);
139 if (monthColor != null) {
140 mPrevButton.setImageTintList(monthColor);
141 mNextButton.setImageTintList(monthColor);
142 }
143 ta.recycle();
144 }
Alan Viverettee763c9b2014-11-06 15:22:31 -0800145
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700146 // Set up min and max dates.
147 final Calendar tempDate = Calendar.getInstance();
148 if (!CalendarView.parseDate(minDate, tempDate)) {
149 tempDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
150 }
151 final long minDateMillis = tempDate.getTimeInMillis();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800152
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700153 if (!CalendarView.parseDate(maxDate, tempDate)) {
154 tempDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
155 }
156 final long maxDateMillis = tempDate.getTimeInMillis();
157
158 if (maxDateMillis < minDateMillis) {
159 throw new IllegalArgumentException("maxDate must be >= minDate");
160 }
161
162 final long setDateMillis = MathUtils.constrain(
163 System.currentTimeMillis(), minDateMillis, maxDateMillis);
164
165 setFirstDayOfWeek(firstDayOfWeek);
166 setMinDate(minDateMillis);
167 setMaxDate(maxDateMillis);
168 setDate(setDateMillis, false);
169
170 // Proxy selection callbacks to our own listener.
Alan Viverette78bf1d32015-04-17 10:39:22 -0700171 mAdapter.setOnDaySelectedListener(new DayPickerPagerAdapter.OnDaySelectedListener() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700172 @Override
Alan Viverette78bf1d32015-04-17 10:39:22 -0700173 public void onDaySelected(DayPickerPagerAdapter adapter, Calendar day) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700174 if (mOnDaySelectedListener != null) {
175 mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
176 }
177 }
178 });
179 }
180
Alan Viverette816aa142015-04-10 15:41:10 -0700181 @Override
182 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Alan Viverette78bf1d32015-04-17 10:39:22 -0700183 final ViewPager viewPager = mViewPager;
184 measureChild(viewPager, widthMeasureSpec, heightMeasureSpec);
Alan Viverette816aa142015-04-10 15:41:10 -0700185
Alan Viverette78bf1d32015-04-17 10:39:22 -0700186 final int measuredWidthAndState = viewPager.getMeasuredWidthAndState();
187 final int measuredHeightAndState = viewPager.getMeasuredHeightAndState();
188 setMeasuredDimension(measuredWidthAndState, measuredHeightAndState);
Alan Viverette816aa142015-04-10 15:41:10 -0700189
Alan Viverette78bf1d32015-04-17 10:39:22 -0700190 final int pagerWidth = viewPager.getMeasuredWidth();
191 final int pagerHeight = viewPager.getMeasuredHeight();
192 final int buttonWidthSpec = MeasureSpec.makeMeasureSpec(pagerWidth, MeasureSpec.AT_MOST);
193 final int buttonHeightSpec = MeasureSpec.makeMeasureSpec(pagerHeight, MeasureSpec.AT_MOST);
194 mPrevButton.measure(buttonWidthSpec, buttonHeightSpec);
195 mNextButton.measure(buttonWidthSpec, buttonHeightSpec);
196 }
Alan Viverette816aa142015-04-10 15:41:10 -0700197
Alan Viverette78bf1d32015-04-17 10:39:22 -0700198 @Override
199 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
200 final ImageButton leftButton = mPrevButton;
201 final ImageButton rightButton = mNextButton;
Alan Viverette816aa142015-04-10 15:41:10 -0700202
Alan Viverette78bf1d32015-04-17 10:39:22 -0700203 final int width = right - left;
204 final int height = bottom - top;
205 mViewPager.layout(0, 0, width, height);
206
207 if (mViewPager.getChildCount() < 1) {
208 leftButton.setVisibility(View.INVISIBLE);
209 rightButton.setVisibility(View.INVISIBLE);
210 return;
Alan Viverette816aa142015-04-10 15:41:10 -0700211 }
212
Alan Viverette78bf1d32015-04-17 10:39:22 -0700213 final SimpleMonthView monthView = (SimpleMonthView) mViewPager.getChildAt(0);
214 final int monthHeight = monthView.getMonthHeight();
215 final int cellWidth = monthView.getCellWidth();
Alan Viverette816aa142015-04-10 15:41:10 -0700216
Alan Viverette78bf1d32015-04-17 10:39:22 -0700217 // Vertically center the previous/next buttons within the month
218 // header, horizontally center within the day cell.
219 final int leftDW = leftButton.getMeasuredWidth();
220 final int leftDH = leftButton.getMeasuredHeight();
221 final int leftIconTop = monthView.getPaddingTop() + (monthHeight - leftDH) / 2;
222 final int leftIconLeft = monthView.getPaddingLeft() + (cellWidth - leftDW) / 2;
223 leftButton.layout(leftIconLeft, leftIconTop, leftIconLeft + leftDW, leftIconTop + leftDH);
224 leftButton.setVisibility(View.VISIBLE);
Alan Viverette816aa142015-04-10 15:41:10 -0700225
Alan Viverette78bf1d32015-04-17 10:39:22 -0700226 final int rightDW = rightButton.getMeasuredWidth();
227 final int rightDH = rightButton.getMeasuredHeight();
228 final int rightIconTop = monthView.getPaddingTop() + (monthHeight - rightDH) / 2;
229 final int rightIconRight = width - monthView.getPaddingRight() - (cellWidth - rightDW) / 2;
230 rightButton.layout(rightIconRight - rightDW, rightIconTop,
231 rightIconRight, rightIconTop + rightDH);
232 rightButton.setVisibility(View.VISIBLE);
Alan Viverette816aa142015-04-10 15:41:10 -0700233 }
234
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700235 public void setDayOfWeekTextAppearance(int resId) {
236 mAdapter.setDayOfWeekTextAppearance(resId);
237 }
238
239 public int getDayOfWeekTextAppearance() {
240 return mAdapter.getDayOfWeekTextAppearance();
241 }
242
243 public void setDayTextAppearance(int resId) {
244 mAdapter.setDayTextAppearance(resId);
245 }
246
247 public int getDayTextAppearance() {
248 return mAdapter.getDayTextAppearance();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800249 }
250
Alan Viverette46127402014-11-13 10:50:37 -0800251 /**
252 * Sets the currently selected date to the specified timestamp. Jumps
253 * immediately to the new date. To animate to the new date, use
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700254 * {@link #setDate(long, boolean)}.
Alan Viverette46127402014-11-13 10:50:37 -0800255 *
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700256 * @param timeInMillis the target day in milliseconds
Alan Viverette46127402014-11-13 10:50:37 -0800257 */
258 public void setDate(long timeInMillis) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700259 setDate(timeInMillis, false);
Alan Viverette46127402014-11-13 10:50:37 -0800260 }
261
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700262 /**
263 * Sets the currently selected date to the specified timestamp. Jumps
264 * immediately to the new date, optionally animating the transition.
265 *
266 * @param timeInMillis the target day in milliseconds
267 * @param animate whether to smooth scroll to the new position
268 */
269 public void setDate(long timeInMillis, boolean animate) {
270 setDate(timeInMillis, animate, true);
271 }
272
273 /**
274 * Moves to the month containing the specified day, optionally setting the
275 * day as selected.
276 *
277 * @param timeInMillis the target day in milliseconds
278 * @param animate whether to smooth scroll to the new position
279 * @param setSelected whether to set the specified day as selected
280 */
281 private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700282 if (setSelected) {
283 mSelectedDay.setTimeInMillis(timeInMillis);
284 }
285
286 final int position = getPositionFromDay(timeInMillis);
Alan Viverette78bf1d32015-04-17 10:39:22 -0700287 if (position != mViewPager.getCurrentItem()) {
288 mViewPager.setCurrentItem(position, animate);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700289 }
Alan Viverettef63757b2015-04-01 17:14:45 -0700290
291 mTempCalendar.setTimeInMillis(timeInMillis);
292 mAdapter.setSelectedDay(mTempCalendar);
Alan Viverette46127402014-11-13 10:50:37 -0800293 }
294
295 public long getDate() {
296 return mSelectedDay.getTimeInMillis();
Alan Viverettee763c9b2014-11-06 15:22:31 -0800297 }
298
299 public void setFirstDayOfWeek(int firstDayOfWeek) {
300 mAdapter.setFirstDayOfWeek(firstDayOfWeek);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700301 }
302
Alan Viverette46127402014-11-13 10:50:37 -0800303 public int getFirstDayOfWeek() {
304 return mAdapter.getFirstDayOfWeek();
305 }
Alan Viverette50eb0252014-10-24 14:34:26 -0700306
Alan Viverette46127402014-11-13 10:50:37 -0800307 public void setMinDate(long timeInMillis) {
308 mMinDate.setTimeInMillis(timeInMillis);
309 onRangeChanged();
310 }
311
312 public long getMinDate() {
313 return mMinDate.getTimeInMillis();
314 }
315
316 public void setMaxDate(long timeInMillis) {
317 mMaxDate.setTimeInMillis(timeInMillis);
318 onRangeChanged();
319 }
320
321 public long getMaxDate() {
322 return mMaxDate.getTimeInMillis();
323 }
324
325 /**
326 * Handles changes to date range.
327 */
328 public void onRangeChanged() {
Alan Viverette50eb0252014-10-24 14:34:26 -0700329 mAdapter.setRange(mMinDate, mMaxDate);
330
Alan Viverette5ecbfeb2014-11-03 18:31:36 -0800331 // Changing the min/max date changes the selection position since we
Alan Viverette46127402014-11-13 10:50:37 -0800332 // don't really have stable IDs. Jumps immediately to the new position.
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700333 setDate(mSelectedDay.getTimeInMillis(), false, false);
Alan Viverette50eb0252014-10-24 14:34:26 -0700334 }
335
336 /**
Alan Viverettee763c9b2014-11-06 15:22:31 -0800337 * Sets the listener to call when the user selects a day.
Alan Viverette50eb0252014-10-24 14:34:26 -0700338 *
Alan Viverettee763c9b2014-11-06 15:22:31 -0800339 * @param listener The listener to call.
Alan Viverette50eb0252014-10-24 14:34:26 -0700340 */
Alan Viverettee763c9b2014-11-06 15:22:31 -0800341 public void setOnDaySelectedListener(OnDaySelectedListener listener) {
342 mOnDaySelectedListener = listener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700343 }
344
Alan Viverette50eb0252014-10-24 14:34:26 -0700345 private int getDiffMonths(Calendar start, Calendar end) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700346 final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700347 return end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700348 }
349
Alan Viverette46127402014-11-13 10:50:37 -0800350 private int getPositionFromDay(long timeInMillis) {
Alan Viverette50eb0252014-10-24 14:34:26 -0700351 final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate);
Alan Viverette46127402014-11-13 10:50:37 -0800352 final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis));
Alan Viverette50eb0252014-10-24 14:34:26 -0700353 return MathUtils.constrain(diffMonth, 0, diffMonthMax);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700354 }
355
Alan Viverette46127402014-11-13 10:50:37 -0800356 private Calendar getTempCalendarForTime(long timeInMillis) {
357 if (mTempCalendar == null) {
358 mTempCalendar = Calendar.getInstance();
359 }
360 mTempCalendar.setTimeInMillis(timeInMillis);
361 return mTempCalendar;
362 }
363
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700364 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700365 * Gets the position of the view that is most prominently displayed within the list view.
366 */
367 public int getMostVisiblePosition() {
Alan Viverette78bf1d32015-04-17 10:39:22 -0700368 return mViewPager.getCurrentItem();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700369 }
Alan Viverettee763c9b2014-11-06 15:22:31 -0800370
Alan Viverette78bf1d32015-04-17 10:39:22 -0700371 public void setPosition(int position) {
372 mViewPager.setCurrentItem(position, false);
373 }
374
375 private final OnPageChangeListener mOnPageChangedListener = new OnPageChangeListener() {
376 @Override
377 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
378 final float alpha = Math.abs(0.5f - positionOffset) * 2.0f;
379 mPrevButton.setAlpha(alpha);
380 mNextButton.setAlpha(alpha);
381 }
382
383 @Override
384 public void onPageScrollStateChanged(int state) {}
385
386 @Override
387 public void onPageSelected(int position) {
388 mPrevButton.setVisibility(
389 position > 0 ? View.VISIBLE : View.INVISIBLE);
390 mNextButton.setVisibility(
391 position < (mAdapter.getCount() - 1) ? View.VISIBLE : View.INVISIBLE);
392 }
393 };
394
395 private final OnClickListener mOnClickListener = new OnClickListener() {
396 @Override
397 public void onClick(View v) {
398 final int direction;
399 if (v == mPrevButton) {
400 direction = -1;
401 } else if (v == mNextButton) {
402 direction = 1;
403 } else {
404 return;
405 }
406
407 // Animation is expensive for accessibility services since it sends
408 // lots of scroll and content change events.
409 final boolean animate = !mAccessibilityManager.isEnabled();
410
411 // ViewPager clamps input values, so we don't need to worry
412 // about passing invalid indices.
413 final int nextItem = mViewPager.getCurrentItem() + direction;
414 mViewPager.setCurrentItem(nextItem, animate);
415 }
416 };
417
Alan Viverettee763c9b2014-11-06 15:22:31 -0800418 public interface OnDaySelectedListener {
Alan Viverette78bf1d32015-04-17 10:39:22 -0700419 void onDaySelected(DayPickerView view, Calendar day);
Alan Viverettee763c9b2014-11-06 15:22:31 -0800420 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700421}