blob: 3fb096c63c980d08a9255404bced1f82f6e75bf8 [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
19import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.Resources;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070022import android.content.res.TypedArray;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070023import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.Paint.Align;
26import android.graphics.Paint.Style;
27import android.graphics.Rect;
28import android.graphics.Typeface;
Alan Viverette60b674e2015-03-25 13:00:42 -070029import android.graphics.drawable.Drawable;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070030import android.os.Bundle;
Alan Viverette5dc973c2015-01-08 11:12:39 -080031import android.text.TextPaint;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070032import android.text.format.DateFormat;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070033import android.util.AttributeSet;
Alan Viveretteffb46bf2014-10-24 12:06:11 -070034import android.util.IntArray;
Alan Viverettec5b95c22015-01-07 13:57:12 -080035import android.util.StateSet;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070036import android.view.MotionEvent;
37import android.view.View;
38import android.view.accessibility.AccessibilityEvent;
39import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070040import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070041
42import com.android.internal.R;
43import com.android.internal.widget.ExploreByTouchHelper;
44
Alan Viverettefd2dd2082014-08-19 18:11:54 -070045import java.text.SimpleDateFormat;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070046import java.util.Calendar;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070047import java.util.Locale;
48
49/**
50 * A calendar-like view displaying a specified month and the appropriate selectable day numbers
51 * within the specified month.
52 */
53class SimpleMonthView extends View {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070054 private static final int DAYS_IN_WEEK = 7;
55 private static final int MAX_WEEKS_IN_MONTH = 6;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070056
57 private static final int DEFAULT_SELECTED_DAY = -1;
58 private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070059
Alan Viverette0ef59ac2015-03-23 13:13:25 -070060 private static final String DEFAULT_TITLE_FORMAT = "MMMMy";
61 private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
Alan Viverettec5b95c22015-01-07 13:57:12 -080062
Alan Viverette60b674e2015-03-25 13:00:42 -070063 /** Virtual view ID for previous button. */
64 private static final int ITEM_ID_PREV = 0x101;
65
66 /** Virtual view ID for next button. */
67 private static final int ITEM_ID_NEXT = 0x100;
68
Alan Viverette5dc973c2015-01-08 11:12:39 -080069 private final TextPaint mMonthPaint = new TextPaint();
70 private final TextPaint mDayOfWeekPaint = new TextPaint();
71 private final TextPaint mDayPaint = new TextPaint();
Alan Viverette0ef59ac2015-03-23 13:13:25 -070072 private final Paint mDaySelectorPaint = new Paint();
73 private final Paint mDayHighlightPaint = new Paint();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070074
75 private final Calendar mCalendar = Calendar.getInstance();
Alan Viverette60b674e2015-03-25 13:00:42 -070076 private final Calendar mDayOfWeekLabelCalendar = Calendar.getInstance();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070077
78 private final MonthViewTouchHelper mTouchHelper;
79
Alan Viverette0ef59ac2015-03-23 13:13:25 -070080 private final SimpleDateFormat mTitleFormatter;
81 private final SimpleDateFormat mDayOfWeekFormatter;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070082
Alan Viverette60b674e2015-03-25 13:00:42 -070083 private final int mMonthHeight;
84 private final int mDayOfWeekHeight;
85 private final int mDayHeight;
86 private final int mCellWidth;
87 private final int mDaySelectorRadius;
88
89 // Next/previous drawables.
90 private final Drawable mPrevDrawable;
91 private final Drawable mNextDrawable;
92 private final Rect mPrevHitArea;
93 private final Rect mNextHitArea;
94 private final CharSequence mPrevContentDesc;
95 private final CharSequence mNextContentDesc;
96
Alan Viverette0ef59ac2015-03-23 13:13:25 -070097 private CharSequence mTitle;
98
99 private int mMonth;
100 private int mYear;
101
102 private int mPaddedWidth;
103 private int mPaddedHeight;
104
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700105 /** The day of month for the selected day, or -1 if no day is selected. */
106 private int mActivatedDay = -1;
107
108 /**
109 * The day of month for today, or -1 if the today is not in the current
110 * month.
111 */
112 private int mToday = DEFAULT_SELECTED_DAY;
113
114 /** The first day of the week (ex. Calendar.SUNDAY). */
115 private int mWeekStart = DEFAULT_WEEK_START;
116
117 /** The number of days (ex. 28) in the current month. */
118 private int mDaysInMonth;
119
120 /**
121 * The day of week (ex. Calendar.SUNDAY) for the first day of the current
122 * month.
123 */
124 private int mDayOfWeekStart;
125
126 /** The day of month for the first (inclusive) enabled day. */
127 private int mEnabledDayStart = 1;
128
129 /** The day of month for the last (inclusive) enabled day. */
130 private int mEnabledDayEnd = 31;
131
132 /** The number of week rows needed to display the current month. */
133 private int mNumWeeks = MAX_WEEKS_IN_MONTH;
134
135 /** Optional listener for handling day click actions. */
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700136 private OnDayClickListener mOnDayClickListener;
137
Alan Viverettec5b95c22015-01-07 13:57:12 -0800138 private ColorStateList mDayTextColor;
139
Alan Viverette60b674e2015-03-25 13:00:42 -0700140 private int mTouchedItem = -1;
141
142 private boolean mPrevEnabled;
143 private boolean mNextEnabled;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700144
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700145 public SimpleMonthView(Context context) {
146 this(context, null);
147 }
148
149 public SimpleMonthView(Context context, AttributeSet attrs) {
150 this(context, attrs, R.attr.datePickerStyle);
151 }
152
Alan Viverette50eb0252014-10-24 14:34:26 -0700153 public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) {
154 this(context, attrs, defStyleAttr, 0);
155 }
156
157 public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
158 super(context, attrs, defStyleAttr, defStyleRes);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700159
160 final Resources res = context.getResources();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700161 mMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height);
162 mDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height);
163 mDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height);
164 mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
165 mDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700166
Alan Viverette60b674e2015-03-25 13:00:42 -0700167 mPrevDrawable = context.getDrawable(R.drawable.ic_chevron_left);
168 mNextDrawable = context.getDrawable(R.drawable.ic_chevron_right);
169 mPrevHitArea = mPrevDrawable != null ? new Rect() : null;
170 mNextHitArea = mNextDrawable != null ? new Rect() : null;
171 mPrevContentDesc = res.getText(R.string.date_picker_prev_month_button);
172 mNextContentDesc = res.getText(R.string.date_picker_next_month_button);
173
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700174 // Set up accessibility components.
175 mTouchHelper = new MonthViewTouchHelper(this);
176 setAccessibilityDelegate(mTouchHelper);
177 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700178
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700179 final Locale locale = res.getConfiguration().locale;
180 final String titleFormat = DateFormat.getBestDateTimePattern(locale, DEFAULT_TITLE_FORMAT);
181 mTitleFormatter = new SimpleDateFormat(titleFormat, locale);
182 mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale);
183
184 setClickable(true);
185 initPaints(res);
186 }
187
Alan Viverette60b674e2015-03-25 13:00:42 -0700188 public void setNextEnabled(boolean enabled) {
189 mNextEnabled = enabled;
190 mTouchHelper.invalidateRoot();
191 invalidate();
192 }
193
194 public void setPrevEnabled(boolean enabled) {
195 mPrevEnabled = enabled;
196 mTouchHelper.invalidateRoot();
197 invalidate();
198 }
199
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700200 /**
201 * Applies the specified text appearance resource to a paint, returning the
202 * text color if one is set in the text appearance.
203 *
204 * @param p the paint to modify
205 * @param resId the resource ID of the text appearance
206 * @return the text color, if available
207 */
208 private ColorStateList applyTextAppearance(Paint p, int resId) {
209 final TypedArray ta = mContext.obtainStyledAttributes(null,
210 R.styleable.TextAppearance, 0, resId);
211
212 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
213 if (fontFamily != null) {
214 p.setTypeface(Typeface.create(fontFamily, 0));
215 }
216
217 p.setTextSize(ta.getDimensionPixelSize(
218 R.styleable.TextAppearance_textSize, (int) p.getTextSize()));
219
220 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
221 if (textColor != null) {
222 final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0);
223 p.setColor(enabledColor);
224 }
225
226 ta.recycle();
227
228 return textColor;
229 }
230
231 public void setMonthTextAppearance(int resId) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700232 final ColorStateList monthColor = applyTextAppearance(mMonthPaint, resId);
233 if (monthColor != null) {
234 if (mPrevDrawable != null) {
235 mPrevDrawable.setTintList(monthColor);
236 }
237 if (mNextDrawable != null) {
238 mNextDrawable.setTintList(monthColor);
239 }
240 }
241
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700242 invalidate();
243 }
244
245 public void setDayOfWeekTextAppearance(int resId) {
246 applyTextAppearance(mDayOfWeekPaint, resId);
247 invalidate();
248 }
249
250 public void setDayTextAppearance(int resId) {
251 final ColorStateList textColor = applyTextAppearance(mDayPaint, resId);
252 if (textColor != null) {
253 mDayTextColor = textColor;
254 }
255
256 invalidate();
257 }
258
259 public CharSequence getTitle() {
260 if (mTitle == null) {
261 mTitle = mTitleFormatter.format(mCalendar.getTime());
262 }
263 return mTitle;
Alan Viverettec5b95c22015-01-07 13:57:12 -0800264 }
265
266 /**
267 * Sets up the text and style properties for painting.
268 */
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700269 private void initPaints(Resources res) {
270 final String monthTypeface = res.getString(R.string.date_picker_month_typeface);
271 final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface);
272 final String dayTypeface = res.getString(R.string.date_picker_day_typeface);
273
274 final int monthTextSize = res.getDimensionPixelSize(
275 R.dimen.date_picker_month_text_size);
276 final int dayOfWeekTextSize = res.getDimensionPixelSize(
277 R.dimen.date_picker_day_of_week_text_size);
278 final int dayTextSize = res.getDimensionPixelSize(
279 R.dimen.date_picker_day_text_size);
280
Alan Viverettec5b95c22015-01-07 13:57:12 -0800281 mMonthPaint.setAntiAlias(true);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700282 mMonthPaint.setTextSize(monthTextSize);
283 mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0));
Alan Viverettec5b95c22015-01-07 13:57:12 -0800284 mMonthPaint.setTextAlign(Align.CENTER);
285 mMonthPaint.setStyle(Style.FILL);
286
287 mDayOfWeekPaint.setAntiAlias(true);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700288 mDayOfWeekPaint.setTextSize(dayOfWeekTextSize);
289 mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0));
Alan Viverettec5b95c22015-01-07 13:57:12 -0800290 mDayOfWeekPaint.setTextAlign(Align.CENTER);
291 mDayOfWeekPaint.setStyle(Style.FILL);
292
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700293 mDaySelectorPaint.setAntiAlias(true);
294 mDaySelectorPaint.setStyle(Style.FILL);
295
296 mDayHighlightPaint.setAntiAlias(true);
297 mDayHighlightPaint.setStyle(Style.FILL);
Alan Viverettec5b95c22015-01-07 13:57:12 -0800298
299 mDayPaint.setAntiAlias(true);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700300 mDayPaint.setTextSize(dayTextSize);
301 mDayPaint.setTypeface(Typeface.create(dayTypeface, 0));
Alan Viverettec5b95c22015-01-07 13:57:12 -0800302 mDayPaint.setTextAlign(Align.CENTER);
303 mDayPaint.setStyle(Style.FILL);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700304 }
305
Alan Viverettec5b95c22015-01-07 13:57:12 -0800306 void setMonthTextColor(ColorStateList monthTextColor) {
307 final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0);
308 mMonthPaint.setColor(enabledColor);
309 invalidate();
310 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700311
Alan Viverettec5b95c22015-01-07 13:57:12 -0800312 void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) {
313 final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0);
314 mDayOfWeekPaint.setColor(enabledColor);
315 invalidate();
316 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700317
Alan Viverettec5b95c22015-01-07 13:57:12 -0800318 void setDayTextColor(ColorStateList dayTextColor) {
319 mDayTextColor = dayTextColor;
320 invalidate();
321 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700322
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700323 void setDaySelectorColor(ColorStateList dayBackgroundColor) {
Alan Viverette5dc973c2015-01-08 11:12:39 -0800324 final int activatedColor = dayBackgroundColor.getColorForState(
Alan Viverettec5b95c22015-01-07 13:57:12 -0800325 StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700326 mDaySelectorPaint.setColor(activatedColor);
Alan Viverettec5b95c22015-01-07 13:57:12 -0800327 invalidate();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700328 }
329
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700330 void setDayHighlightColor(ColorStateList dayHighlightColor) {
331 final int pressedColor = dayHighlightColor.getColorForState(
332 StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0);
333 mDayHighlightPaint.setColor(pressedColor);
334 invalidate();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700335 }
336
337 public void setOnDayClickListener(OnDayClickListener listener) {
338 mOnDayClickListener = listener;
339 }
340
341 @Override
342 public boolean dispatchHoverEvent(MotionEvent event) {
343 // First right-of-refusal goes the touch exploration helper.
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700344 return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700345 }
346
347 @Override
348 public boolean onTouchEvent(MotionEvent event) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700349 final int x = (int) (event.getX() + 0.5f);
350 final int y = (int) (event.getY() + 0.5f);
351
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700352 switch (event.getAction()) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700353 case MotionEvent.ACTION_DOWN:
354 case MotionEvent.ACTION_MOVE:
Alan Viverette60b674e2015-03-25 13:00:42 -0700355 final int touchedItem = getItemAtLocation(x, y);
356 if (mTouchedItem != touchedItem) {
357 mTouchedItem = touchedItem;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700358 invalidate();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700359 }
360 break;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700361
362 case MotionEvent.ACTION_UP:
Alan Viverette60b674e2015-03-25 13:00:42 -0700363 final int clickedItem = getItemAtLocation(x, y);
364 onItemClicked(clickedItem, true);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700365 // Fall through.
366 case MotionEvent.ACTION_CANCEL:
367 // Reset touched day on stream end.
Alan Viverette60b674e2015-03-25 13:00:42 -0700368 mTouchedItem = -1;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700369 invalidate();
370 break;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700371 }
372 return true;
373 }
374
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700375 @Override
376 protected void onDraw(Canvas canvas) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700377 final int paddingLeft = getPaddingLeft();
378 final int paddingTop = getPaddingTop();
379 canvas.translate(paddingLeft, paddingTop);
380
381 drawMonth(canvas);
382 drawDaysOfWeek(canvas);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700383 drawDays(canvas);
Alan Viverette60b674e2015-03-25 13:00:42 -0700384 drawButtons(canvas);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700385
386 canvas.translate(-paddingLeft, -paddingTop);
387 }
388
389 private void drawMonth(Canvas canvas) {
390 final float x = mPaddedWidth / 2f;
391
392 // Vertically centered within the month header height.
393 final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent();
394 final float y = (mMonthHeight - lineHeight) / 2f;
395
396 canvas.drawText(getTitle().toString(), x, y, mMonthPaint);
397 }
398
399 private void drawDaysOfWeek(Canvas canvas) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700400 final TextPaint p = mDayOfWeekPaint;
401 final int headerHeight = mMonthHeight;
402 final int rowHeight = mDayOfWeekHeight;
403 final int colWidth = mPaddedWidth / DAYS_IN_WEEK;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700404
Alan Viverette60b674e2015-03-25 13:00:42 -0700405 // Text is vertically centered within the day of week height.
406 final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
407 final int rowCenter = headerHeight + rowHeight / 2;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700408
Alan Viverette60b674e2015-03-25 13:00:42 -0700409 for (int col = 0; col < DAYS_IN_WEEK; col++) {
410 final int colCenter = colWidth * col + colWidth / 2;
411 final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
412 final String label = getDayOfWeekLabel(dayOfWeek);
413 canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700414 }
415 }
416
Alan Viverette60b674e2015-03-25 13:00:42 -0700417 private String getDayOfWeekLabel(int dayOfWeek) {
418 mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
419 return mDayOfWeekFormatter.format(mDayOfWeekLabelCalendar.getTime());
420 }
421
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700422 /**
423 * Draws the month days.
424 */
425 private void drawDays(Canvas canvas) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700426 final TextPaint p = mDayPaint;
427 final int headerHeight = mMonthHeight + mDayOfWeekHeight;
428 final int rowHeight = mDayHeight;
429 final int colWidth = mPaddedWidth / DAYS_IN_WEEK;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700430
Alan Viverette60b674e2015-03-25 13:00:42 -0700431 // Text is vertically centered within the row height.
432 final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
433 int rowCenter = headerHeight + rowHeight / 2;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700434
Alan Viverette60b674e2015-03-25 13:00:42 -0700435 for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
436 final int colCenter = colWidth * col + colWidth / 2;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700437 int stateMask = 0;
438
439 if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
440 stateMask |= StateSet.VIEW_STATE_ENABLED;
441 }
442
443 final boolean isDayActivated = mActivatedDay == day;
444 if (isDayActivated) {
445 stateMask |= StateSet.VIEW_STATE_ACTIVATED;
446
447 // Adjust the circle to be centered on the row.
Alan Viverette60b674e2015-03-25 13:00:42 -0700448 canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
449 } else if (mTouchedItem == day) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700450 stateMask |= StateSet.VIEW_STATE_PRESSED;
451
452 // Adjust the circle to be centered on the row.
Alan Viverette60b674e2015-03-25 13:00:42 -0700453 canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700454 }
455
456 final boolean isDayToday = mToday == day;
457 final int dayTextColor;
458 if (isDayToday && !isDayActivated) {
459 dayTextColor = mDaySelectorPaint.getColor();
460 } else {
461 final int[] stateSet = StateSet.get(stateMask);
462 dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
463 }
Alan Viverette60b674e2015-03-25 13:00:42 -0700464 p.setColor(dayTextColor);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700465
Alan Viverette60b674e2015-03-25 13:00:42 -0700466 canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700467
Alan Viverette60b674e2015-03-25 13:00:42 -0700468 col++;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700469
Alan Viverette60b674e2015-03-25 13:00:42 -0700470 if (col == DAYS_IN_WEEK) {
471 col = 0;
472 rowCenter += rowHeight;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700473 }
474 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700475 }
476
Alan Viverette60b674e2015-03-25 13:00:42 -0700477 private void drawButtons(Canvas canvas) {
478 if (mPrevEnabled && mPrevDrawable != null) {
479 mPrevDrawable.draw(canvas);
480 }
481
482 if (mNextEnabled && mNextDrawable != null) {
483 mNextDrawable.draw(canvas);
484 }
485 }
486
Alan Viverette518ff0d2014-08-15 14:20:35 -0700487 private static boolean isValidDayOfWeek(int day) {
Alan Viverette58780762014-09-10 17:09:13 -0700488 return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY;
489 }
490
491 private static boolean isValidMonth(int month) {
492 return month >= Calendar.JANUARY && month <= Calendar.DECEMBER;
Fabrice Di Meglio75b12152014-07-25 15:26:18 -0700493 }
494
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700495 /**
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700496 * Sets the selected day.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700497 *
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700498 * @param dayOfMonth the selected day of the month, or {@code -1} to clear
499 * the selection
500 */
501 public void setSelectedDay(int dayOfMonth) {
502 mActivatedDay = dayOfMonth;
503
504 // Invalidate cached accessibility information.
505 mTouchHelper.invalidateRoot();
506 invalidate();
507 }
508
509 /**
510 * Sets the first day of the week.
511 *
512 * @param weekStart which day the week should start on, valid values are
513 * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
514 */
515 public void setFirstDayOfWeek(int weekStart) {
516 if (isValidDayOfWeek(weekStart)) {
517 mWeekStart = weekStart;
518 } else {
519 mWeekStart = mCalendar.getFirstDayOfWeek();
520 }
521
522 // Invalidate cached accessibility information.
523 mTouchHelper.invalidateRoot();
524 invalidate();
525 }
526
527 /**
528 * Sets all the parameters for displaying this week.
529 * <p>
530 * Parameters have a default value and will only update if a new value is
531 * included, except for focus month, which will always default to no focus
532 * month if no value is passed in. The only required parameter is the week
533 * start.
534 *
535 * @param selectedDay the selected day of the month, or -1 for no selection
536 * @param month the month
537 * @param year the year
538 * @param weekStart which day the week should start on, valid values are
539 * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
540 * @param enabledDayStart the first enabled day
541 * @param enabledDayEnd the last enabled day
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700542 */
Fabrice Di Meglio75b12152014-07-25 15:26:18 -0700543 void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
544 int enabledDayEnd) {
Alan Viverettec5b95c22015-01-07 13:57:12 -0800545 mActivatedDay = selectedDay;
Fabrice Di Meglio75b12152014-07-25 15:26:18 -0700546
Alan Viverette58780762014-09-10 17:09:13 -0700547 if (isValidMonth(month)) {
Fabrice Di Meglio75b12152014-07-25 15:26:18 -0700548 mMonth = month;
549 }
550 mYear = year;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700551
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700552 mCalendar.set(Calendar.MONTH, mMonth);
553 mCalendar.set(Calendar.YEAR, mYear);
554 mCalendar.set(Calendar.DAY_OF_MONTH, 1);
555 mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
556
Alan Viverette518ff0d2014-08-15 14:20:35 -0700557 if (isValidDayOfWeek(weekStart)) {
Fabrice Di Meglio75b12152014-07-25 15:26:18 -0700558 mWeekStart = weekStart;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700559 } else {
560 mWeekStart = mCalendar.getFirstDayOfWeek();
561 }
562
Fabrice Di Meglio75b12152014-07-25 15:26:18 -0700563 if (enabledDayStart > 0 && enabledDayEnd < 32) {
564 mEnabledDayStart = enabledDayStart;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700565 }
Fabrice Di Meglio75b12152014-07-25 15:26:18 -0700566 if (enabledDayEnd > 0 && enabledDayEnd < 32 && enabledDayEnd >= enabledDayStart) {
567 mEnabledDayEnd = enabledDayEnd;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700568 }
569
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700570 // Figure out what day today is.
571 final Calendar today = Calendar.getInstance();
572 mToday = -1;
573 mDaysInMonth = getDaysInMonth(mMonth, mYear);
574 for (int i = 0; i < mDaysInMonth; i++) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700575 final int day = i + 1;
576 if (sameDay(day, today)) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700577 mToday = day;
578 }
579 }
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700580 mNumWeeks = calculateNumRows();
581
582 // Invalidate the old title.
583 mTitle = null;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700584
585 // Invalidate cached accessibility information.
586 mTouchHelper.invalidateRoot();
587 }
588
589 private static int getDaysInMonth(int month, int year) {
590 switch (month) {
591 case Calendar.JANUARY:
592 case Calendar.MARCH:
593 case Calendar.MAY:
594 case Calendar.JULY:
595 case Calendar.AUGUST:
596 case Calendar.OCTOBER:
597 case Calendar.DECEMBER:
598 return 31;
599 case Calendar.APRIL:
600 case Calendar.JUNE:
601 case Calendar.SEPTEMBER:
602 case Calendar.NOVEMBER:
603 return 30;
604 case Calendar.FEBRUARY:
605 return (year % 4 == 0) ? 29 : 28;
606 default:
607 throw new IllegalArgumentException("Invalid Month");
608 }
609 }
610
611 public void reuse() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700612 mNumWeeks = MAX_WEEKS_IN_MONTH;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700613 requestLayout();
614 }
615
616 private int calculateNumRows() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700617 final int offset = findDayOffset();
618 final int dividend = (offset + mDaysInMonth) / DAYS_IN_WEEK;
619 final int remainder = (offset + mDaysInMonth) % DAYS_IN_WEEK;
620 return dividend + (remainder > 0 ? 1 : 0);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700621 }
622
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700623 private boolean sameDay(int day, Calendar today) {
624 return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH)
625 && day == today.get(Calendar.DAY_OF_MONTH);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700626 }
627
628 @Override
629 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700630 final int preferredHeight = mDayHeight * mNumWeeks + mDayOfWeekHeight + mMonthHeight
631 + getPaddingTop() + getPaddingBottom();
632 final int preferredWidth = mCellWidth * DAYS_IN_WEEK
633 + getPaddingStart() + getPaddingEnd();
634 final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec);
635 final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec);
636 setMeasuredDimension(resolvedWidth, resolvedHeight);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700637 }
638
639 @Override
640 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700641 final int paddedLeft = getPaddingLeft();
642 final int paddedTop = getPaddingTop();
643 final int paddedRight = w - getPaddingRight();
644 final int paddedBottom = h - getPaddingBottom();
645 mPaddedWidth = paddedRight - paddedLeft;
646 mPaddedHeight = paddedBottom - paddedTop;
647
648 final int monthHeight = mMonthHeight;
649 final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
650
651 // Vertically center the previous/next drawables within the month
652 // header, horizontally center within the day cell, then expand the
653 // hit area to ensure it's at least 48x48dp.
654 final Drawable prevDrawable = mPrevDrawable;
655 if (prevDrawable != null) {
656 final int dW = prevDrawable.getIntrinsicWidth();
657 final int dH = prevDrawable.getIntrinsicHeight();
658 final int iconTop = (monthHeight - dH) / 2;
659 final int iconLeft = (cellWidth - dW) / 2;
660
661 // Button bounds don't include padding, but hit area does.
662 prevDrawable.setBounds(iconLeft, iconTop, iconLeft + dW, iconTop + dH);
663 mPrevHitArea.set(0, 0, paddedLeft + cellWidth, paddedTop + monthHeight);
664 }
665
666 final Drawable nextDrawable = mNextDrawable;
667 if (nextDrawable != null) {
668 final int dW = nextDrawable.getIntrinsicWidth();
669 final int dH = nextDrawable.getIntrinsicHeight();
670 final int iconTop = (monthHeight - dH) / 2;
671 final int iconRight = mPaddedWidth - (cellWidth - dW) / 2;
672
673 // Button bounds don't include padding, but hit area does.
674 nextDrawable.setBounds(iconRight - dW, iconTop, iconRight, iconTop + dH);
675 mNextHitArea.set(paddedRight - cellWidth, 0, w, paddedTop + monthHeight);
676 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700677
678 // Invalidate cached accessibility information.
679 mTouchHelper.invalidateRoot();
680 }
681
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700682 private int findDayOffset() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700683 final int offset = mDayOfWeekStart - mWeekStart;
684 if (mDayOfWeekStart < mWeekStart) {
685 return offset + DAYS_IN_WEEK;
686 }
687 return offset;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700688 }
689
690 /**
Alan Viverette60b674e2015-03-25 13:00:42 -0700691 * Calculates the day of the month or item identifier at the specified
692 * touch position. Returns the day of the month or -1 if the position
693 * wasn't in a valid day.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700694 *
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700695 * @param x the x position of the touch event
696 * @param y the y position of the touch event
Alan Viverette60b674e2015-03-25 13:00:42 -0700697 * @return the day of the month at (x, y), an item identifier, or -1 if the
698 * position wasn't in a valid day or item
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700699 */
Alan Viverette60b674e2015-03-25 13:00:42 -0700700 private int getItemAtLocation(int x, int y) {
701 if (mNextEnabled && mNextDrawable != null && mNextHitArea.contains(x, y)) {
702 return ITEM_ID_NEXT;
703 } else if (mPrevEnabled && mPrevDrawable != null && mPrevHitArea.contains(x, y)) {
704 return ITEM_ID_PREV;
705 }
706
707 final int paddedX = x - getPaddingLeft();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700708 if (paddedX < 0 || paddedX >= mPaddedWidth) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700709 return -1;
710 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700711
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700712 final int headerHeight = mMonthHeight + mDayOfWeekHeight;
Alan Viverette60b674e2015-03-25 13:00:42 -0700713 final int paddedY = y - getPaddingTop();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700714 if (paddedY < headerHeight || paddedY >= mPaddedHeight) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700715 return -1;
716 }
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700717
718 final int row = (paddedY - headerHeight) / mDayHeight;
719 final int col = (paddedX * DAYS_IN_WEEK) / mPaddedWidth;
720 final int index = col + row * DAYS_IN_WEEK;
721 final int day = index + 1 - findDayOffset();
722 if (day < 1 || day > mDaysInMonth) {
723 return -1;
724 }
725
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700726 return day;
727 }
728
729 /**
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700730 * Calculates the bounds of the specified day.
731 *
Alan Viverette60b674e2015-03-25 13:00:42 -0700732 * @param id the day of the month, or an item identifier
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700733 * @param outBounds the rect to populate with bounds
734 */
Alan Viverette60b674e2015-03-25 13:00:42 -0700735 private boolean getBoundsForItem(int id, Rect outBounds) {
736 if (mNextEnabled && id == ITEM_ID_NEXT) {
737 if (mNextDrawable != null) {
738 outBounds.set(mNextHitArea);
739 return true;
740 }
741 } else if (mPrevEnabled && id == ITEM_ID_PREV) {
742 if (mPrevDrawable != null) {
743 outBounds.set(mPrevHitArea);
744 return true;
745 }
746 }
747
748 if (id < 1 || id > mDaysInMonth) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700749 return false;
750 }
751
Alan Viverette60b674e2015-03-25 13:00:42 -0700752 final int index = id - 1 + findDayOffset();
753
754 // Compute left edge.
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700755 final int col = index % DAYS_IN_WEEK;
Alan Viverette60b674e2015-03-25 13:00:42 -0700756 final int colWidth = mPaddedWidth / DAYS_IN_WEEK;
757 final int left = getPaddingLeft() + col * colWidth;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700758
Alan Viverette60b674e2015-03-25 13:00:42 -0700759 // Compute top edge.
760 final int row = index / DAYS_IN_WEEK;
761 final int rowHeight = mDayHeight;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700762 final int headerHeight = mMonthHeight + mDayOfWeekHeight;
Alan Viverette60b674e2015-03-25 13:00:42 -0700763 final int top = getPaddingTop() + headerHeight + row * rowHeight;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700764
Alan Viverette60b674e2015-03-25 13:00:42 -0700765 outBounds.set(left, top, left + colWidth, top + rowHeight);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700766 return true;
767 }
768
769 /**
Alan Viverette60b674e2015-03-25 13:00:42 -0700770 * Called when an item is clicked.
771 *
772 * @param id the day number or item identifier
773 */
774 private boolean onItemClicked(int id, boolean animate) {
775 return onNavigationClicked(id, animate) || onDayClicked(id);
776 }
777
778 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700779 * Called when the user clicks on a day. Handles callbacks to the
780 * {@link OnDayClickListener} if one is set.
781 *
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700782 * @param day the day that was clicked
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700783 */
Alan Viverette60b674e2015-03-25 13:00:42 -0700784 private boolean onDayClicked(int day) {
785 if (day < 0 || day > mDaysInMonth) {
786 return false;
787 }
788
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700789 if (mOnDayClickListener != null) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700790 final Calendar date = Calendar.getInstance();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700791 date.set(mYear, mMonth, day);
792 mOnDayClickListener.onDayClick(this, date);
793 }
794
795 // This is a no-op if accessibility is turned off.
796 mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
Alan Viverette60b674e2015-03-25 13:00:42 -0700797 return true;
798 }
799
800 /**
801 * Called when the user clicks on a navigation button. Handles callbacks to
802 * the {@link OnDayClickListener} if one is set.
803 *
804 * @param id the item identifier
805 */
806 private boolean onNavigationClicked(int id, boolean animate) {
807 final int direction;
808 if (id == ITEM_ID_NEXT) {
809 direction = 1;
810 } else if (id == ITEM_ID_PREV) {
811 direction = -1;
812 } else {
813 return false;
814 }
815
816 if (mOnDayClickListener != null) {
817 mOnDayClickListener.onNavigationClick(this, direction, animate);
818 }
819
820 // This is a no-op if accessibility is turned off.
821 mTouchHelper.sendEventForVirtualView(id, AccessibilityEvent.TYPE_VIEW_CLICKED);
822 return true;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700823 }
824
825 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700826 * Provides a virtual view hierarchy for interfacing with an accessibility
827 * service.
828 */
829 private class MonthViewTouchHelper extends ExploreByTouchHelper {
830 private static final String DATE_FORMAT = "dd MMMM yyyy";
831
832 private final Rect mTempRect = new Rect();
833 private final Calendar mTempCalendar = Calendar.getInstance();
834
835 public MonthViewTouchHelper(View host) {
836 super(host);
837 }
838
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700839 @Override
840 protected int getVirtualViewAt(float x, float y) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700841 final int day = getItemAtLocation((int) (x + 0.5f), (int) (y + 0.5f));
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700842 if (day >= 0) {
843 return day;
844 }
845 return ExploreByTouchHelper.INVALID_ID;
846 }
847
848 @Override
Alan Viveretteffb46bf2014-10-24 12:06:11 -0700849 protected void getVisibleVirtualViews(IntArray virtualViewIds) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700850 if (mNextEnabled && mNextDrawable != null) {
851 virtualViewIds.add(ITEM_ID_PREV);
852 }
853
854 if (mPrevEnabled && mPrevDrawable != null) {
855 virtualViewIds.add(ITEM_ID_NEXT);
856 }
857
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700858 for (int day = 1; day <= mDaysInMonth; day++) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700859 virtualViewIds.add(day);
860 }
861 }
862
863 @Override
864 protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
865 event.setContentDescription(getItemDescription(virtualViewId));
866 }
867
868 @Override
869 protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700870 final boolean hasBounds = getBoundsForItem(virtualViewId, mTempRect);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700871
872 if (!hasBounds) {
873 // The day is invalid, kill the node.
874 mTempRect.setEmpty();
875 node.setContentDescription("");
876 node.setBoundsInParent(mTempRect);
877 node.setVisibleToUser(false);
878 return;
879 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700880
Alan Viverette60b674e2015-03-25 13:00:42 -0700881 node.setText(getItemText(virtualViewId));
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700882 node.setContentDescription(getItemDescription(virtualViewId));
883 node.setBoundsInParent(mTempRect);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700884 node.addAction(AccessibilityAction.ACTION_CLICK);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700885
Alan Viverettec5b95c22015-01-07 13:57:12 -0800886 if (virtualViewId == mActivatedDay) {
Alan Viverette60b674e2015-03-25 13:00:42 -0700887 // TODO: This should use activated once that's supported.
888 node.setChecked(true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700889 }
890
891 }
892
893 @Override
894 protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
895 Bundle arguments) {
896 switch (action) {
897 case AccessibilityNodeInfo.ACTION_CLICK:
Alan Viverette60b674e2015-03-25 13:00:42 -0700898 return onItemClicked(virtualViewId, false);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700899 }
900
901 return false;
902 }
903
904 /**
Alan Viverette60b674e2015-03-25 13:00:42 -0700905 * Generates a description for a given virtual view.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700906 *
Alan Viverette60b674e2015-03-25 13:00:42 -0700907 * @param id the day or item identifier to generate a description for
908 * @return a description of the virtual view
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700909 */
Alan Viverette60b674e2015-03-25 13:00:42 -0700910 private CharSequence getItemDescription(int id) {
911 if (id == ITEM_ID_NEXT) {
912 return mNextContentDesc;
913 } else if (id == ITEM_ID_PREV) {
914 return mPrevContentDesc;
915 } else if (id >= 1 && id <= mDaysInMonth) {
916 mTempCalendar.set(mYear, mMonth, id);
917 return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis());
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700918 }
919
Alan Viverette60b674e2015-03-25 13:00:42 -0700920 return "";
921 }
922
923 /**
924 * Generates displayed text for a given virtual view.
925 *
926 * @param id the day or item identifier to generate text for
927 * @return the visible text of the virtual view
928 */
929 private CharSequence getItemText(int id) {
930 if (id == ITEM_ID_NEXT || id == ITEM_ID_PREV) {
931 return null;
932 } else if (id >= 1 && id <= mDaysInMonth) {
933 return Integer.toString(id);
934 }
935
936 return null;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700937 }
938 }
939
940 /**
941 * Handles callbacks when the user clicks on a time object.
942 */
943 public interface OnDayClickListener {
944 public void onDayClick(SimpleMonthView view, Calendar day);
Alan Viverette60b674e2015-03-25 13:00:42 -0700945 public void onNavigationClick(SimpleMonthView view, int direction, boolean animate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700946 }
947}