1bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio/* 2bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Copyright (C) 2014 The Android Open Source Project 3bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 4bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License"); 5bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * you may not use this file except in compliance with the License. 6bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * You may obtain a copy of the License at 7bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 8bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * http://www.apache.org/licenses/LICENSE-2.0 9bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 10bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Unless required by applicable law or agreed to in writing, software 11bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS, 12bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * See the License for the specific language governing permissions and 14bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * limitations under the License. 15bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 16bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 17bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Megliopackage android.widget; 18bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 19e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.annotation.Nullable; 20bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.Context; 21bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.ColorStateList; 22bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.Resources; 230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.content.res.TypedArray; 24bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Canvas; 25bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint; 26bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Align; 27bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Style; 28bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Rect; 29bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Typeface; 30c3e5a82691ac9094189a000430eab939e9020b2bAlan Viveretteimport android.icu.text.DisplayContext; 31a7f827bab3df423d2a3b527853de3e9ac9e378fdDeepanshu Guptaimport android.icu.text.SimpleDateFormat; 3268763be41cffbcb4883a0a6293fc1aa9e17a3957Alan Viveretteimport android.icu.util.Calendar; 33bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.os.Bundle; 345dc973cb03889c58988703b58aefbd2397fb02c3Alan Viveretteimport android.text.TextPaint; 35bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.text.format.DateFormat; 36bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.util.AttributeSet; 37ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray; 385c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viveretteimport android.util.MathUtils; 39c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viveretteimport android.util.StateSet; 40e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.view.KeyEvent; 41bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.MotionEvent; 4299441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport android.view.PointerIcon; 43bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.View; 44e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.view.ViewParent; 45bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityEvent; 46bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo; 470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 48bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 4999441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport com.android.internal.R; 5099441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport com.android.internal.widget.ExploreByTouchHelper; 51bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 52d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viveretteimport libcore.icu.LocaleData; 53d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette 5499441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport java.text.NumberFormat; 5599441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport java.util.Locale; 5699441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikas 57bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio/** 58bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * A calendar-like view displaying a specified month and the appropriate selectable day numbers 59bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * within the specified month. 60bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 61bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioclass SimpleMonthView extends View { 620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private static final int DAYS_IN_WEEK = 7; 630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private static final int MAX_WEEKS_IN_MONTH = 6; 64bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 65bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static final int DEFAULT_SELECTED_DAY = -1; 66bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static final int DEFAULT_WEEK_START = Calendar.SUNDAY; 67bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 68e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private static final String MONTH_YEAR_FORMAT = "MMMMy"; 69c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 70e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private static final int SELECTED_HIGHLIGHT_ALPHA = 0xB0; 71e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 725dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette private final TextPaint mMonthPaint = new TextPaint(); 735dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette private final TextPaint mDayOfWeekPaint = new TextPaint(); 745dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette private final TextPaint mDayPaint = new TextPaint(); 750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private final Paint mDaySelectorPaint = new Paint(); 760ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private final Paint mDayHighlightPaint = new Paint(); 77e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private final Paint mDayHighlightSelectorPaint = new Paint(); 785dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette 79d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette /** Array of single-character weekday labels ordered by column index. */ 80e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private final String[] mDayOfWeekLabels = new String[7]; 81e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 82e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private final Calendar mCalendar; 83e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private final Locale mLocale; 84bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private final MonthViewTouchHelper mTouchHelper; 86fd2dd208d9b0957657589b26c14c48b8e2b7f387Alan Viverette 871ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase private final NumberFormat mDayFormatter; 88bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 89f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Desired dimensions. 90f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredMonthHeight; 91f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredDayOfWeekHeight; 92f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredDayHeight; 93f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredCellWidth; 94f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredDaySelectorRadius; 9560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 96e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private String mMonthYearLabel; 97bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 98bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mMonth; 99bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mYear; 100bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 101f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Dimensions as laid out. 102f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mMonthHeight; 103f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mDayOfWeekHeight; 104f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mDayHeight; 105f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mCellWidth; 106f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mDaySelectorRadius; 107f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 1080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mPaddedWidth; 1090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mPaddedHeight; 110bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The day of month for the selected day, or -1 if no day is selected. */ 112c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette private int mActivatedDay = -1; 113bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 1150ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * The day of month for today, or -1 if the today is not in the current 1160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * month. 1170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 118bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mToday = DEFAULT_SELECTED_DAY; 119bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 120d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette /** The first day of the week (ex. Calendar.SUNDAY) indexed from one. */ 121bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mWeekStart = DEFAULT_WEEK_START; 122bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The number of days (ex. 28) in the current month. */ 1240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mDaysInMonth; 125bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 1270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * The day of week (ex. Calendar.SUNDAY) for the first day of the current 1280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * month. 1290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 1300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mDayOfWeekStart; 131bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The day of month for the first (inclusive) enabled day. */ 133bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mEnabledDayStart = 1; 134bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The day of month for the last (inclusive) enabled day. */ 136bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mEnabledDayEnd = 31; 137bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** Optional listener for handling day click actions. */ 139bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private OnDayClickListener mOnDayClickListener; 140bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 141c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette private ColorStateList mDayTextColor; 142c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 143e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int mHighlightedDay = -1; 144e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int mPreviouslyHighlightedDay = -1; 145e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean mIsTouchHighlighted = false; 14660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 147bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public SimpleMonthView(Context context) { 148bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio this(context, null); 149bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 150bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 151bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public SimpleMonthView(Context context, AttributeSet attrs) { 152bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio this(context, attrs, R.attr.datePickerStyle); 153bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 154bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 15550eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) { 15650eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette this(context, attrs, defStyleAttr, 0); 15750eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette } 158bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 15950eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 16050eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette super(context, attrs, defStyleAttr, defStyleRes); 161bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 16250eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette final Resources res = context.getResources(); 163f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); 164f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); 165f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); 166f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); 16778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette mDesiredDaySelectorRadius = res.getDimensionPixelSize( 16878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette R.dimen.date_picker_day_selector_radius); 16960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 170bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // Set up accessibility components. 171bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper = new MonthViewTouchHelper(this); 172bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio setAccessibilityDelegate(mTouchHelper); 173bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 174bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 175e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mLocale = res.getConfiguration().locale; 176e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mCalendar = Calendar.getInstance(mLocale); 177e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 178e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mDayFormatter = NumberFormat.getIntegerInstance(mLocale); 179e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 180e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette updateMonthYearLabel(); 181e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette updateDayOfWeekLabels(); 1820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 1830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette initPaints(res); 1840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 1850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 186e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private void updateMonthYearLabel() { 187e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette final String format = DateFormat.getBestDateTimePattern(mLocale, MONTH_YEAR_FORMAT); 188e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette final SimpleDateFormat formatter = new SimpleDateFormat(format, mLocale); 189c3e5a82691ac9094189a000430eab939e9020b2bAlan Viverette formatter.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE); 190e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mMonthYearLabel = formatter.format(mCalendar.getTime()); 191e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 192e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 193e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private void updateDayOfWeekLabels() { 194d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette // Use tiny (e.g. single-character) weekday names from ICU. The indices 195d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette // for this list correspond to Calendar days, e.g. SUNDAY is index 1. 196d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette final String[] tinyWeekdayNames = LocaleData.get(mLocale).tinyWeekdayNames; 197d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette for (int i = 0; i < DAYS_IN_WEEK; i++) { 198d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette mDayOfWeekLabels[i] = tinyWeekdayNames[(mWeekStart + i - 1) % DAYS_IN_WEEK + 1]; 199e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 200e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 201e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 2020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 2030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Applies the specified text appearance resource to a paint, returning the 2040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * text color if one is set in the text appearance. 2050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 2060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param p the paint to modify 2070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param resId the resource ID of the text appearance 2080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @return the text color, if available 2090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 2100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private ColorStateList applyTextAppearance(Paint p, int resId) { 2110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final TypedArray ta = mContext.obtainStyledAttributes(null, 2120ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.styleable.TextAppearance, 0, resId); 2130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily); 2150ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (fontFamily != null) { 2160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette p.setTypeface(Typeface.create(fontFamily, 0)); 2170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette p.setTextSize(ta.getDimensionPixelSize( 2200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.styleable.TextAppearance_textSize, (int) p.getTextSize())); 2210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor); 2230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (textColor != null) { 2240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0); 2250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette p.setColor(enabledColor); 2260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette ta.recycle(); 2290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return textColor; 2310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 23378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette public int getMonthHeight() { 23478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return mMonthHeight; 23578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette } 23678bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette 23778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette public int getCellWidth() { 23878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return mCellWidth; 23978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette } 24078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette 2410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setMonthTextAppearance(int resId) { 24278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette applyTextAppearance(mMonthPaint, resId); 24360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 2440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 2450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setDayOfWeekTextAppearance(int resId) { 2480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette applyTextAppearance(mDayOfWeekPaint, resId); 2490ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 2500ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2510ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setDayTextAppearance(int resId) { 2530ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final ColorStateList textColor = applyTextAppearance(mDayPaint, resId); 2540ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (textColor != null) { 2550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayTextColor = textColor; 2560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2570ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 2590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 261c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette /** 262c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette * Sets up the text and style properties for painting. 263c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette */ 2640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void initPaints(Resources res) { 2650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String monthTypeface = res.getString(R.string.date_picker_month_typeface); 2660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface); 2670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String dayTypeface = res.getString(R.string.date_picker_day_typeface); 2680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int monthTextSize = res.getDimensionPixelSize( 2700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.dimen.date_picker_month_text_size); 2710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int dayOfWeekTextSize = res.getDimensionPixelSize( 2720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.dimen.date_picker_day_of_week_text_size); 2730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int dayTextSize = res.getDimensionPixelSize( 2740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.dimen.date_picker_day_text_size); 2750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 276c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setAntiAlias(true); 2770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mMonthPaint.setTextSize(monthTextSize); 2780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0)); 279c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setTextAlign(Align.CENTER); 280c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setStyle(Style.FILL); 281c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 282c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setAntiAlias(true); 2830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayOfWeekPaint.setTextSize(dayOfWeekTextSize); 2840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0)); 285c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setTextAlign(Align.CENTER); 286c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setStyle(Style.FILL); 287c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 2880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaySelectorPaint.setAntiAlias(true); 2890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaySelectorPaint.setStyle(Style.FILL); 2900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2910ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayHighlightPaint.setAntiAlias(true); 2920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayHighlightPaint.setStyle(Style.FILL); 293c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 294e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setAntiAlias(true); 295e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setStyle(Style.FILL); 296e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 297c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayPaint.setAntiAlias(true); 2980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayPaint.setTextSize(dayTextSize); 2990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayPaint.setTypeface(Typeface.create(dayTypeface, 0)); 300c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayPaint.setTextAlign(Align.CENTER); 301c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayPaint.setStyle(Style.FILL); 302bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 303bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 304c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette void setMonthTextColor(ColorStateList monthTextColor) { 305c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0); 306c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setColor(enabledColor); 307c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 308c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette } 309bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 310c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) { 311c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0); 312c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setColor(enabledColor); 313c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 314c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette } 315bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 316c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette void setDayTextColor(ColorStateList dayTextColor) { 317c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayTextColor = dayTextColor; 318c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 319c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette } 320bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 3210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette void setDaySelectorColor(ColorStateList dayBackgroundColor) { 3225dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette final int activatedColor = dayBackgroundColor.getColorForState( 323c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); 3240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaySelectorPaint.setColor(activatedColor); 325e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setColor(activatedColor); 326e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setAlpha(SELECTED_HIGHLIGHT_ALPHA); 327c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 328bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 329bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 3300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette void setDayHighlightColor(ColorStateList dayHighlightColor) { 3310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int pressedColor = dayHighlightColor.getColorForState( 3320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0); 3330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayHighlightPaint.setColor(pressedColor); 3340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 335bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 336bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 337bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public void setOnDayClickListener(OnDayClickListener listener) { 338bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mOnDayClickListener = listener; 339bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 340bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 341bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 342bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public boolean dispatchHoverEvent(MotionEvent event) { 343bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // First right-of-refusal goes the touch exploration helper. 3440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event); 345bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 346bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 347bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 348bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public boolean onTouchEvent(MotionEvent event) { 34960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int x = (int) (event.getX() + 0.5f); 35060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int y = (int) (event.getY() + 0.5f); 35160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 352ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int action = event.getAction(); 353ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette switch (action) { 3540ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_DOWN: 3550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_MOVE: 35678bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final int touchedItem = getDayAtLocation(x, y); 357e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mIsTouchHighlighted = true; 358e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay != touchedItem) { 359e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = touchedItem; 360e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mPreviouslyHighlightedDay = touchedItem; 3610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 362bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 363ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) { 364ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette // Touch something that's not an item, reject event. 365ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette return false; 366ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 367bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio break; 3680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 3690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_UP: 37078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final int clickedDay = getDayAtLocation(x, y); 37178bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette onDayClicked(clickedDay); 3720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Fall through. 3730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_CANCEL: 3740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Reset touched day on stream end. 375e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = -1; 376e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mIsTouchHighlighted = false; 3770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 3780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette break; 379bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 380bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return true; 381bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 382bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 383bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 384e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount public boolean onKeyDown(int keyCode, KeyEvent event) { 385e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // We need to handle focus change within the SimpleMonthView because we are simulating 386e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // multiple Views. The arrow keys will move between days until there is no space (no 387e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // day to the left, top, right, or bottom). Focus forward and back jumps out of the 388e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // SimpleMonthView, skipping over other SimpleMonthViews in the parent ViewPager 389e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // to the next focusable View in the hierarchy. 390e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount boolean focusChanged = false; 391e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount switch (event.getKeyCode()) { 392e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_LEFT: 393e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 394e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = moveOneDay(isLayoutRtl()); 395e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 396e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 397e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_RIGHT: 398e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 399e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = moveOneDay(!isLayoutRtl()); 400e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 401e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 402e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_UP: 403e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 404e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 405e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay > 7) { 406e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay -= 7; 407e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 408e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 409e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 410e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 411e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_DOWN: 412e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 413e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 414e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay <= mDaysInMonth - 7) { 415e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay += 7; 416e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 417e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 418e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 419e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 420e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_CENTER: 421e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_ENTER: 422e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay != -1) { 423e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount onDayClicked(mHighlightedDay); 424e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return true; 425e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 426e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 427e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_TAB: { 428e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int focusChangeDirection = 0; 429e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 430e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChangeDirection = View.FOCUS_FORWARD; 431e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 432e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChangeDirection = View.FOCUS_BACKWARD; 433e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 434e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (focusChangeDirection != 0) { 435e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final ViewParent parent = getParent(); 436e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // move out of the ViewPager next/previous 437e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount View nextFocus = this; 438e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount do { 439e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount nextFocus = nextFocus.focusSearch(focusChangeDirection); 440e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } while (nextFocus != null && nextFocus != this && 441e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount nextFocus.getParent() == parent); 442e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (nextFocus != null) { 443e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount nextFocus.requestFocus(); 444e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return true; 445e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 446e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 447e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 448e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 449e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 450e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (focusChanged) { 451e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount invalidate(); 452e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return true; 453e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 454e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return super.onKeyDown(keyCode, event); 455e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 456e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 457e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 458e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean moveOneDay(boolean positive) { 459e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 460e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount boolean focusChanged = false; 461e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (positive) { 462e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (!isLastDayOfWeek(mHighlightedDay) && mHighlightedDay < mDaysInMonth) { 463e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay++; 464e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 465e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 466e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 467e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (!isFirstDayOfWeek(mHighlightedDay) && mHighlightedDay > 1) { 468e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay--; 469e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 470e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 471e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 472e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return focusChanged; 473e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 474e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 475e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 476e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction, 477e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Nullable Rect previouslyFocusedRect) { 478e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (gainFocus) { 479e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // If we've gained focus through arrow keys, we should find the day closest 480e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // to the focus rect. If we've gained focus through forward/back, we should 481e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // focus on the selected day if there is one. 482e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int offset = findDayOffset(); 483e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount switch(direction) { 484e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_RIGHT: { 485e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int row = findClosestRow(previouslyFocusedRect); 486e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = row == 0 ? 1 : (row * DAYS_IN_WEEK) - offset + 1; 487e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 488e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 489e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_LEFT: { 490e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int row = findClosestRow(previouslyFocusedRect) + 1; 491e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = Math.min(mDaysInMonth, (row * DAYS_IN_WEEK) - offset); 492e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 493e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 494e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_DOWN: { 495e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int col = findClosestColumn(previouslyFocusedRect); 496e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int day = col - offset + 1; 497e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = day < 1 ? day + DAYS_IN_WEEK : day; 498e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 499e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 500e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_UP: { 501e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int col = findClosestColumn(previouslyFocusedRect); 502e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int maxWeeks = (offset + mDaysInMonth) / DAYS_IN_WEEK; 503e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int day = col - offset + (DAYS_IN_WEEK * maxWeeks) + 1; 504e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = day > mDaysInMonth ? day - DAYS_IN_WEEK : day; 505e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 506e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 507e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 508e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 509e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount invalidate(); 510e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 511e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 512e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 513e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 514e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount /** 515e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * Returns the row (0 indexed) closest to previouslyFocusedRect or center if null. 516e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount */ 517e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int findClosestRow(@Nullable Rect previouslyFocusedRect) { 518e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (previouslyFocusedRect == null) { 519e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return 3; 520e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 521e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int centerY = previouslyFocusedRect.centerY(); 522e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 523e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final TextPaint p = mDayPaint; 524e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int headerHeight = mMonthHeight + mDayOfWeekHeight; 525e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int rowHeight = mDayHeight; 526e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 527e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // Text is vertically centered within the row height. 528e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final float halfLineHeight = (p.ascent() + p.descent()) / 2f; 529e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int rowCenter = headerHeight + rowHeight / 2; 530e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 531e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount centerY -= rowCenter - halfLineHeight; 532e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int row = Math.round(centerY / (float) rowHeight); 533e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int maxDay = findDayOffset() + mDaysInMonth; 534e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int maxRows = (maxDay / DAYS_IN_WEEK) - ((maxDay % DAYS_IN_WEEK == 0) ? 1 : 0); 535e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 536e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount row = MathUtils.constrain(row, 0, maxRows); 537e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return row; 538e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 539e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 540e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 541e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount /** 542e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * Returns the column (0 indexed) closest to the previouslyFocusedRect or center if null. 543e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * The 0 index is related to the first day of the week. 544e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount */ 545e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int findClosestColumn(@Nullable Rect previouslyFocusedRect) { 546e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (previouslyFocusedRect == null) { 547e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return DAYS_IN_WEEK / 2; 548e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 549e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int centerX = previouslyFocusedRect.centerX() - mPaddingLeft; 550e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int columnFromLeft = 551e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount MathUtils.constrain(centerX / mCellWidth, 0, DAYS_IN_WEEK - 1); 552e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return isLayoutRtl() ? DAYS_IN_WEEK - columnFromLeft - 1: columnFromLeft; 553e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 554e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 555e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 556e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 557e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount public void getFocusedRect(Rect r) { 558e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay > 0) { 559e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount getBoundsForDay(mHighlightedDay, r); 560e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 561e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount super.getFocusedRect(r); 562e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 563e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 564e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 565e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 566e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount protected void onFocusLost() { 567e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (!mIsTouchHighlighted) { 568e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // Unhighlight a day. 569e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mPreviouslyHighlightedDay = mHighlightedDay; 570e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = -1; 571e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount invalidate(); 572e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 573e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount super.onFocusLost(); 574e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 575e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 576e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount /** 577e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * Ensure some day is highlighted. If a day isn't highlighted, it chooses the selected day, 578e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * if possible, or the first day of the month if not. 579e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount */ 580e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private void ensureFocusedDay() { 581e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay != -1) { 582e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return; 583e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 584e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mPreviouslyHighlightedDay != -1) { 585e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = mPreviouslyHighlightedDay; 586e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return; 587e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 588e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mActivatedDay != -1) { 589e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = mActivatedDay; 590e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return; 591e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 592e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = 1; 593e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 594e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 595e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean isFirstDayOfWeek(int day) { 596e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int offset = findDayOffset(); 597e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return (offset + day - 1) % DAYS_IN_WEEK == 0; 598e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 599e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 600e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean isLastDayOfWeek(int day) { 601e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int offset = findDayOffset(); 602e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return (offset + day) % DAYS_IN_WEEK == 0; 603e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 604e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 605e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 606bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onDraw(Canvas canvas) { 6070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int paddingLeft = getPaddingLeft(); 6080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int paddingTop = getPaddingTop(); 6090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette canvas.translate(paddingLeft, paddingTop); 6100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette drawMonth(canvas); 6120ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette drawDaysOfWeek(canvas); 613bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio drawDays(canvas); 6140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6150ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette canvas.translate(-paddingLeft, -paddingTop); 6160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void drawMonth(Canvas canvas) { 6190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final float x = mPaddedWidth / 2f; 6200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Vertically centered within the month header height. 6220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent(); 6230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final float y = (mMonthHeight - lineHeight) / 2f; 6240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 625e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette canvas.drawText(mMonthYearLabel, x, y, mMonthPaint); 626e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 627e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 628e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette public String getMonthYearLabel() { 629e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette return mMonthYearLabel; 6300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void drawDaysOfWeek(Canvas canvas) { 63360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final TextPaint p = mDayOfWeekPaint; 63460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int headerHeight = mMonthHeight; 63560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowHeight = mDayOfWeekHeight; 636f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int colWidth = mCellWidth; 63760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 63860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // Text is vertically centered within the day of week height. 63960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final float halfLineHeight = (p.ascent() + p.descent()) / 2f; 64060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowCenter = headerHeight + rowHeight / 2; 64160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 64260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette for (int col = 0; col < DAYS_IN_WEEK; col++) { 64360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int colCenter = colWidth * col + colWidth / 2; 644ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int colCenterRtl; 645ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 646ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = mPaddedWidth - colCenter; 647ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 648ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = colCenter; 649ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 650ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 651d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette final String label = mDayOfWeekLabels[col]; 652ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p); 6530ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6540ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 6570ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Draws the month days. 6580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 6590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void drawDays(Canvas canvas) { 66060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final TextPaint p = mDayPaint; 66160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int headerHeight = mMonthHeight + mDayOfWeekHeight; 66260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowHeight = mDayHeight; 663f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int colWidth = mCellWidth; 6640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 66560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // Text is vertically centered within the row height. 66660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final float halfLineHeight = (p.ascent() + p.descent()) / 2f; 66760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette int rowCenter = headerHeight + rowHeight / 2; 6680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 66960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) { 67060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int colCenter = colWidth * col + colWidth / 2; 671ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int colCenterRtl; 672ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 673ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = mPaddedWidth - colCenter; 674ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 675ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = colCenter; 676ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 677ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 6780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette int stateMask = 0; 6790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6805c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette final boolean isDayEnabled = isDayEnabled(day); 6815c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isDayEnabled) { 6820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette stateMask |= StateSet.VIEW_STATE_ENABLED; 6830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final boolean isDayActivated = mActivatedDay == day; 686e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final boolean isDayHighlighted = mHighlightedDay == day; 6870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (isDayActivated) { 6880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette stateMask |= StateSet.VIEW_STATE_ACTIVATED; 6890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Adjust the circle to be centered on the row. 691e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final Paint paint = isDayHighlighted ? mDayHighlightSelectorPaint : 692e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDaySelectorPaint; 693e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, paint); 694e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else if (isDayHighlighted) { 6950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette stateMask |= StateSet.VIEW_STATE_PRESSED; 6960ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6975c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isDayEnabled) { 6985c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette // Adjust the circle to be centered on the row. 6995c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette canvas.drawCircle(colCenterRtl, rowCenter, 7005c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette mDaySelectorRadius, mDayHighlightPaint); 7015c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 7020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final boolean isDayToday = mToday == day; 7050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int dayTextColor; 7060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (isDayToday && !isDayActivated) { 7070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette dayTextColor = mDaySelectorPaint.getColor(); 7080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } else { 7090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int[] stateSet = StateSet.get(stateMask); 7100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette dayTextColor = mDayTextColor.getColorForState(stateSet, 0); 7110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 71260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette p.setColor(dayTextColor); 7130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7141ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase canvas.drawText(mDayFormatter.format(day), colCenterRtl, rowCenter - halfLineHeight, p); 7150ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 71660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette col++; 7170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 71860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette if (col == DAYS_IN_WEEK) { 71960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette col = 0; 72060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette rowCenter += rowHeight; 7210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 723bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 724bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 7255c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette private boolean isDayEnabled(int day) { 7265c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette return day >= mEnabledDayStart && day <= mEnabledDayEnd; 7275c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 7285c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 7295c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette private boolean isValidDayOfMonth(int day) { 7305c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette return day >= 1 && day <= mDaysInMonth; 7315c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 7325c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 733518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette private static boolean isValidDayOfWeek(int day) { 7345878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY; 7355878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette } 7365878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette 7375878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette private static boolean isValidMonth(int month) { 7385878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette return month >= Calendar.JANUARY && month <= Calendar.DECEMBER; 73975b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio } 74075b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio 741bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 7420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Sets the selected day. 743bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 7440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param dayOfMonth the selected day of the month, or {@code -1} to clear 7450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * the selection 7460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 7470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setSelectedDay(int dayOfMonth) { 7480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mActivatedDay = dayOfMonth; 7490ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7500ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Invalidate cached accessibility information. 7510ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mTouchHelper.invalidateRoot(); 7520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 7530ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7540ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 7560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Sets the first day of the week. 7570ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 7580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param weekStart which day the week should start on, valid values are 7590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} 7600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 7610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setFirstDayOfWeek(int weekStart) { 7620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (isValidDayOfWeek(weekStart)) { 7630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mWeekStart = weekStart; 7640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } else { 7650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mWeekStart = mCalendar.getFirstDayOfWeek(); 7660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 768e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette updateDayOfWeekLabels(); 769e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 7700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Invalidate cached accessibility information. 7710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mTouchHelper.invalidateRoot(); 7720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 7730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 7760ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Sets all the parameters for displaying this week. 7770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * <p> 7780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Parameters have a default value and will only update if a new value is 7790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * included, except for focus month, which will always default to no focus 7800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * month if no value is passed in. The only required parameter is the week 7810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * start. 7820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 7830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param selectedDay the selected day of the month, or -1 for no selection 7840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param month the month 7850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param year the year 7860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param weekStart which day the week should start on, valid values are 7870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} 7880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param enabledDayStart the first enabled day 7890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param enabledDayEnd the last enabled day 790bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 79175b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart, 79275b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio int enabledDayEnd) { 793c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mActivatedDay = selectedDay; 794bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 7955878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette if (isValidMonth(month)) { 79675b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio mMonth = month; 79775b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio } 79875b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio mYear = year; 799bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 800bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mCalendar.set(Calendar.MONTH, mMonth); 801bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mCalendar.set(Calendar.YEAR, mYear); 802bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mCalendar.set(Calendar.DAY_OF_MONTH, 1); 803bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK); 804bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 805518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette if (isValidDayOfWeek(weekStart)) { 80675b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio mWeekStart = weekStart; 807bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } else { 808bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mWeekStart = mCalendar.getFirstDayOfWeek(); 809bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 810bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 8110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Figure out what day today is. 8120ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final Calendar today = Calendar.getInstance(); 8130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mToday = -1; 8140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaysInMonth = getDaysInMonth(mMonth, mYear); 8150ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette for (int i = 0; i < mDaysInMonth; i++) { 816bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio final int day = i + 1; 817bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio if (sameDay(day, today)) { 818bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mToday = day; 819bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 820bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 8210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 8225c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette mEnabledDayStart = MathUtils.constrain(enabledDayStart, 1, mDaysInMonth); 8235c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette mEnabledDayEnd = MathUtils.constrain(enabledDayEnd, mEnabledDayStart, mDaysInMonth); 8245c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 82508dbbcdc9b4176b4bd91904065c87f0dae29f2d4Alan Viverette updateMonthYearLabel(); 82608dbbcdc9b4176b4bd91904065c87f0dae29f2d4Alan Viverette updateDayOfWeekLabels(); 82708dbbcdc9b4176b4bd91904065c87f0dae29f2d4Alan Viverette 828bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // Invalidate cached accessibility information. 829bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper.invalidateRoot(); 83008dbbcdc9b4176b4bd91904065c87f0dae29f2d4Alan Viverette invalidate(); 831bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 832bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 833bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static int getDaysInMonth(int month, int year) { 834bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio switch (month) { 835bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.JANUARY: 836bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.MARCH: 837bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.MAY: 838bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.JULY: 839bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.AUGUST: 840bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.OCTOBER: 841bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.DECEMBER: 842bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return 31; 843bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.APRIL: 844bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.JUNE: 845bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.SEPTEMBER: 846bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.NOVEMBER: 847bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return 30; 848bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.FEBRUARY: 849bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return (year % 4 == 0) ? 29 : 28; 850bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio default: 851bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio throw new IllegalArgumentException("Invalid Month"); 852bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 853bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 854bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 8550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private boolean sameDay(int day, Calendar today) { 8560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH) 8570ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette && day == today.get(Calendar.DAY_OF_MONTH); 858bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 859bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 860bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 861bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 862816aa142023c75cf427401c21831193998bdf233Alan Viverette final int preferredHeight = mDesiredDayHeight * MAX_WEEKS_IN_MONTH 863816aa142023c75cf427401c21831193998bdf233Alan Viverette + mDesiredDayOfWeekHeight + mDesiredMonthHeight 864816aa142023c75cf427401c21831193998bdf233Alan Viverette + getPaddingTop() + getPaddingBottom(); 865f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK 8660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette + getPaddingStart() + getPaddingEnd(); 8670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); 8680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec); 8690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette setMeasuredDimension(resolvedWidth, resolvedHeight); 870bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 871bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 872bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 873ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { 874ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette super.onRtlPropertiesChanged(layoutDirection); 875ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 876ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette requestLayout(); 877ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 878ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 879ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette @Override 880f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 881f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette if (!changed) { 882f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette return; 883f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette } 884f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 885f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Let's initialize a completely reasonable number of variables. 886f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int w = right - left; 887f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int h = bottom - top; 888f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingLeft = getPaddingLeft(); 889f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingTop = getPaddingTop(); 890f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingRight = getPaddingRight(); 891f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingBottom = getPaddingBottom(); 892f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedRight = w - paddingRight; 893f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedBottom = h - paddingBottom; 894f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedWidth = paddedRight - paddingLeft; 895f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedHeight = paddedBottom - paddingTop; 896f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) { 897f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette return; 898f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette } 899f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 900f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mPaddedWidth = paddedWidth; 901f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mPaddedHeight = paddedHeight; 902f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 903f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // We may have been laid out smaller than our preferred size. If so, 904f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // scale all dimensions to fit. 905f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom; 906f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final float scaleH = paddedHeight / (float) measuredPaddedHeight; 907f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int monthHeight = (int) (mDesiredMonthHeight * scaleH); 90860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; 909f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mMonthHeight = monthHeight; 910f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH); 911f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDayHeight = (int) (mDesiredDayHeight * scaleH); 912f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mCellWidth = cellWidth; 913f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 914f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Compute the largest day selector radius that's still within the clip 915f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // bounds and desired selector radius. 916f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight); 917f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int maxSelectorHeight = mDayHeight / 2 + paddingBottom; 918f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius, 919f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette Math.min(maxSelectorWidth, maxSelectorHeight)); 92060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 921bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // Invalidate cached accessibility information. 922bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper.invalidateRoot(); 923bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 924bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 9250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int findDayOffset() { 9260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int offset = mDayOfWeekStart - mWeekStart; 9270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (mDayOfWeekStart < mWeekStart) { 9280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return offset + DAYS_IN_WEEK; 929bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 9300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return offset; 931bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 932bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 933bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 93478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * Calculates the day of the month at the specified touch position. Returns 93578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * the day of the month or -1 if the position wasn't in a valid day. 9360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 9370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param x the x position of the touch event 9380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param y the y position of the touch event 93978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @return the day of the month at (x, y), or -1 if the position wasn't in 94078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * a valid day 941bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 94278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette private int getDayAtLocation(int x, int y) { 94360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int paddedX = x - getPaddingLeft(); 9440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (paddedX < 0 || paddedX >= mPaddedWidth) { 9450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return -1; 9460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 947c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 9480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int headerHeight = mMonthHeight + mDayOfWeekHeight; 94960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int paddedY = y - getPaddingTop(); 9500ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (paddedY < headerHeight || paddedY >= mPaddedHeight) { 9510ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return -1; 9520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 953c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 954ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette // Adjust for RTL after applying padding. 955ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int paddedXRtl; 956ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 957ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette paddedXRtl = mPaddedWidth - paddedX; 958ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 959ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette paddedXRtl = paddedX; 960ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 961ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 9620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int row = (paddedY - headerHeight) / mDayHeight; 963ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth; 9640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int index = col + row * DAYS_IN_WEEK; 9650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int day = index + 1 - findDayOffset(); 9665c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (!isValidDayOfMonth(day)) { 9670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return -1; 968bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 969bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 9700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return day; 971bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 972bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 973bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 9740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Calculates the bounds of the specified day. 975bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 97678bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @param id the day of the month 9770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param outBounds the rect to populate with bounds 978bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 979698b751dc78f8a325c5b513cacd3335151f807eaKirill Grouchnikov public boolean getBoundsForDay(int id, Rect outBounds) { 9805c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (!isValidDayOfMonth(id)) { 9810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return false; 982bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 983bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 98460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int index = id - 1 + findDayOffset(); 98560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 986ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette // Compute left edge, taking into account RTL. 9870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int col = index % DAYS_IN_WEEK; 988f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int colWidth = mCellWidth; 989ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int left; 990ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 991ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette left = getWidth() - getPaddingRight() - (col + 1) * colWidth; 992ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 993ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette left = getPaddingLeft() + col * colWidth; 994ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 9950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 99660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // Compute top edge. 99760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int row = index / DAYS_IN_WEEK; 99860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowHeight = mDayHeight; 9990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int headerHeight = mMonthHeight + mDayOfWeekHeight; 100060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int top = getPaddingTop() + headerHeight + row * rowHeight; 10010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 100260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette outBounds.set(left, top, left + colWidth, top + rowHeight); 1003bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 100478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return true; 100560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 100660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 100760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette /** 1008bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Called when the user clicks on a day. Handles callbacks to the 1009bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * {@link OnDayClickListener} if one is set. 1010bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 10110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param day the day that was clicked 1012bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 101360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette private boolean onDayClicked(int day) { 10145c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (!isValidDayOfMonth(day) || !isDayEnabled(day)) { 101560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return false; 101660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 101760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 1018bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio if (mOnDayClickListener != null) { 101960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final Calendar date = Calendar.getInstance(); 1020bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio date.set(mYear, mMonth, day); 1021bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mOnDayClickListener.onDayClick(this, date); 1022bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1023bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1024bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // This is a no-op if accessibility is turned off. 1025bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED); 102660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return true; 102760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 102860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 102947f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev @Override 103047f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 103147f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev if (!isEnabled()) { 103247f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev return null; 103347f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev } 103447f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev // Add 0.5f to event coordinates to match the logic in onTouchEvent. 103547f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev final int x = (int) (event.getX() + 0.5f); 103647f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev final int y = (int) (event.getY() + 0.5f); 103747f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev final int dayUnderPointer = getDayAtLocation(x, y); 103847f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev if (dayUnderPointer >= 0) { 103947f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND); 104047f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev } 104147f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev return super.onResolvePointerIcon(event, pointerIndex); 104247f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev } 104347f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev 104460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette /** 1045bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Provides a virtual view hierarchy for interfacing with an accessibility 1046bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * service. 1047bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 1048bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private class MonthViewTouchHelper extends ExploreByTouchHelper { 1049bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static final String DATE_FORMAT = "dd MMMM yyyy"; 1050bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1051bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private final Rect mTempRect = new Rect(); 1052bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private final Calendar mTempCalendar = Calendar.getInstance(); 1053bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1054bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public MonthViewTouchHelper(View host) { 1055bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio super(host); 1056bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1057bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1058bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1059bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected int getVirtualViewAt(float x, float y) { 106078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final int day = getDayAtLocation((int) (x + 0.5f), (int) (y + 0.5f)); 10615c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (day != -1) { 1062bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return day; 1063bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1064bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return ExploreByTouchHelper.INVALID_ID; 1065bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1066bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1067bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1068ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void getVisibleVirtualViews(IntArray virtualViewIds) { 10690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette for (int day = 1; day <= mDaysInMonth; day++) { 1070bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio virtualViewIds.add(day); 1071bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1072bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1073bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1074bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1075bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { 107678bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette event.setContentDescription(getDayDescription(virtualViewId)); 1077bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1078bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1079bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1080bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { 108178bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect); 10820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 10830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (!hasBounds) { 10840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // The day is invalid, kill the node. 10850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mTempRect.setEmpty(); 10860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette node.setContentDescription(""); 10870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette node.setBoundsInParent(mTempRect); 10880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette node.setVisibleToUser(false); 10890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return; 10900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 1091bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 109278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette node.setText(getDayText(virtualViewId)); 109378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette node.setContentDescription(getDayDescription(virtualViewId)); 1094bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio node.setBoundsInParent(mTempRect); 10955c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 10965c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette final boolean isDayEnabled = isDayEnabled(virtualViewId); 10975c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isDayEnabled) { 10985c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette node.addAction(AccessibilityAction.ACTION_CLICK); 10995c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 11005c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 11015c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette node.setEnabled(isDayEnabled); 1102bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1103c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette if (virtualViewId == mActivatedDay) { 110460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // TODO: This should use activated once that's supported. 110560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette node.setChecked(true); 1106bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1107bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1108bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1109bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1110bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1111bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected boolean onPerformActionForVirtualView(int virtualViewId, int action, 1112bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio Bundle arguments) { 1113bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio switch (action) { 1114bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case AccessibilityNodeInfo.ACTION_CLICK: 111578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return onDayClicked(virtualViewId); 1116bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1117bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1118bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return false; 1119bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1120bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1121bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 112260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * Generates a description for a given virtual view. 1123bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 112478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @param id the day to generate a description for 112560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * @return a description of the virtual view 1126bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 112778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette private CharSequence getDayDescription(int id) { 11285c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isValidDayOfMonth(id)) { 112960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette mTempCalendar.set(mYear, mMonth, id); 113060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis()); 113160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 1132bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 113360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return ""; 113460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 113560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 113660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette /** 113760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * Generates displayed text for a given virtual view. 113860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * 113978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @param id the day to generate text for 114060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * @return the visible text of the virtual view 114160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette */ 114278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette private CharSequence getDayText(int id) { 11435c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isValidDayOfMonth(id)) { 11441ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase return mDayFormatter.format(id); 1145bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1146bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 114760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return null; 1148bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1149bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1150bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1151bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 1152bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Handles callbacks when the user clicks on a time object. 1153bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 1154bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public interface OnDayClickListener { 115578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette void onDayClick(SimpleMonthView view, Calendar day); 1156bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1157bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio} 1158