SimpleMonthView.java revision d5c85c824bfe343144af68ede9e86f12d5bba1f7
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 19d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viveretteimport com.android.internal.R; 20d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viveretteimport com.android.internal.widget.ExploreByTouchHelper; 21d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette 22e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.annotation.Nullable; 23bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.Context; 24bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.ColorStateList; 25bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.Resources; 260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.content.res.TypedArray; 27bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Canvas; 28bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint; 29bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Align; 30bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Style; 31bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Rect; 32bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Typeface; 33a7f827bab3df423d2a3b527853de3e9ac9e378fdDeepanshu Guptaimport android.icu.text.SimpleDateFormat; 34bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.os.Bundle; 355dc973cb03889c58988703b58aefbd2397fb02c3Alan Viveretteimport android.text.TextPaint; 36bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.text.format.DateFormat; 37bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.util.AttributeSet; 38ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray; 39da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viveretteimport android.util.Log; 405c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viveretteimport android.util.MathUtils; 41c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viveretteimport android.util.StateSet; 42e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.view.KeyEvent; 43bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.MotionEvent; 44bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.View; 45e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.view.ViewParent; 46bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityEvent; 47bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo; 480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 49bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 501ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haaseimport java.text.NumberFormat; 51e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viveretteimport java.util.Arrays; 52bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Calendar; 53bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Locale; 54bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 55d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viveretteimport libcore.icu.LocaleData; 56d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette 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 { 62e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private static final String LOG_TAG = "SimpleMonthView"; 63e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private static final int DAYS_IN_WEEK = 7; 650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private static final int MAX_WEEKS_IN_MONTH = 6; 66bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 67bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static final int DEFAULT_SELECTED_DAY = -1; 68bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static final int DEFAULT_WEEK_START = Calendar.SUNDAY; 69bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 70e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private static final String MONTH_YEAR_FORMAT = "MMMMy"; 71c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 72e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private static final int SELECTED_HIGHLIGHT_ALPHA = 0xB0; 73e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 74da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette /** Temporary until we figure out why the date gets messed up. */ 75da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette private static final boolean DEBUG_WRONG_DATE = true; 76da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette 775dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette private final TextPaint mMonthPaint = new TextPaint(); 785dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette private final TextPaint mDayOfWeekPaint = new TextPaint(); 795dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette private final TextPaint mDayPaint = new TextPaint(); 800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private final Paint mDaySelectorPaint = new Paint(); 810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private final Paint mDayHighlightPaint = new Paint(); 82e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private final Paint mDayHighlightSelectorPaint = new Paint(); 835dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette 84d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette /** Array of single-character weekday labels ordered by column index. */ 85e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private final String[] mDayOfWeekLabels = new String[7]; 86e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 87e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private final Calendar mCalendar; 88e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private final Locale mLocale; 89bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private final MonthViewTouchHelper mTouchHelper; 91fd2dd208d9b0957657589b26c14c48b8e2b7f387Alan Viverette 921ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase private final NumberFormat mDayFormatter; 93bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 94f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Desired dimensions. 95f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredMonthHeight; 96f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredDayOfWeekHeight; 97f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredDayHeight; 98f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredCellWidth; 99f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private final int mDesiredDaySelectorRadius; 10060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 101e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private String mMonthYearLabel; 102bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 103bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mMonth; 104bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mYear; 105bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 106f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Dimensions as laid out. 107f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mMonthHeight; 108f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mDayOfWeekHeight; 109f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mDayHeight; 110f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mCellWidth; 111f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette private int mDaySelectorRadius; 112f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 1130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mPaddedWidth; 1140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mPaddedHeight; 115bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The day of month for the selected day, or -1 if no day is selected. */ 117c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette private int mActivatedDay = -1; 118bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 1200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * The day of month for today, or -1 if the today is not in the current 1210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * month. 1220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 123bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mToday = DEFAULT_SELECTED_DAY; 124bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 125d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette /** The first day of the week (ex. Calendar.SUNDAY) indexed from one. */ 126bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mWeekStart = DEFAULT_WEEK_START; 127bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The number of days (ex. 28) in the current month. */ 1290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mDaysInMonth; 130bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 1320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * The day of week (ex. Calendar.SUNDAY) for the first day of the current 1330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * month. 1340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 1350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int mDayOfWeekStart; 136bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The day of month for the first (inclusive) enabled day. */ 138bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mEnabledDayStart = 1; 139bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** The day of month for the last (inclusive) enabled day. */ 141bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private int mEnabledDayEnd = 31; 142bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1430ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** Optional listener for handling day click actions. */ 144bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private OnDayClickListener mOnDayClickListener; 145bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 146c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette private ColorStateList mDayTextColor; 147c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 148e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int mHighlightedDay = -1; 149e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int mPreviouslyHighlightedDay = -1; 150e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean mIsTouchHighlighted = false; 15160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 152bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public SimpleMonthView(Context context) { 153bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio this(context, null); 154bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 155bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 156bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public SimpleMonthView(Context context, AttributeSet attrs) { 157bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio this(context, attrs, R.attr.datePickerStyle); 158bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 159bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 16050eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) { 16150eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette this(context, attrs, defStyleAttr, 0); 16250eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette } 163bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 16450eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 16550eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette super(context, attrs, defStyleAttr, defStyleRes); 166bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 16750eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette final Resources res = context.getResources(); 168f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); 169f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); 170f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); 171f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); 17278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette mDesiredDaySelectorRadius = res.getDimensionPixelSize( 17378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette R.dimen.date_picker_day_selector_radius); 17460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 175bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // Set up accessibility components. 176bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper = new MonthViewTouchHelper(this); 177bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio setAccessibilityDelegate(mTouchHelper); 178bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 179bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 180e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mLocale = res.getConfiguration().locale; 181e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mCalendar = Calendar.getInstance(mLocale); 182e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 183e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mDayFormatter = NumberFormat.getIntegerInstance(mLocale); 184e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 185e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette updateMonthYearLabel(); 186e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette updateDayOfWeekLabels(); 1870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 1880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette initPaints(res); 1890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 1900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 191e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private void updateMonthYearLabel() { 192e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette final String format = DateFormat.getBestDateTimePattern(mLocale, MONTH_YEAR_FORMAT); 193e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette final SimpleDateFormat formatter = new SimpleDateFormat(format, mLocale); 194e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette mMonthYearLabel = formatter.format(mCalendar.getTime()); 195e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 196e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 197e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette private void updateDayOfWeekLabels() { 198da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette if (DEBUG_WRONG_DATE) { 199da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette Log.d(LOG_TAG, "enter updateDayOfWeekLabels()", new Exception()); 200da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette Log.d(LOG_TAG, "mLocale => " + mLocale); 201da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette Log.d(LOG_TAG, "mWeekStart => " + mWeekStart); 202da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette } 203da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette 204d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette // Use tiny (e.g. single-character) weekday names from ICU. The indices 205d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette // for this list correspond to Calendar days, e.g. SUNDAY is index 1. 206d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette final String[] tinyWeekdayNames = LocaleData.get(mLocale).tinyWeekdayNames; 207d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette for (int i = 0; i < DAYS_IN_WEEK; i++) { 208d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette mDayOfWeekLabels[i] = tinyWeekdayNames[(mWeekStart + i - 1) % DAYS_IN_WEEK + 1]; 209e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 210da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette 211da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette if (DEBUG_WRONG_DATE) { 212da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette Log.d(LOG_TAG, "mDayOfWeekLabels <= " + Arrays.toString(mDayOfWeekLabels)); 213da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette } 214e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 215e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 2160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 2170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Applies the specified text appearance resource to a paint, returning the 2180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * text color if one is set in the text appearance. 2190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 2200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param p the paint to modify 2210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param resId the resource ID of the text appearance 2220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @return the text color, if available 2230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 2240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private ColorStateList applyTextAppearance(Paint p, int resId) { 2250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final TypedArray ta = mContext.obtainStyledAttributes(null, 2260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.styleable.TextAppearance, 0, resId); 2270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily); 2290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (fontFamily != null) { 2300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette p.setTypeface(Typeface.create(fontFamily, 0)); 2310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette p.setTextSize(ta.getDimensionPixelSize( 2340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.styleable.TextAppearance_textSize, (int) p.getTextSize())); 2350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor); 2370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (textColor != null) { 2380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0); 2390ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette p.setColor(enabledColor); 2400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette ta.recycle(); 2430ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return textColor; 2450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 24778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette public int getMonthHeight() { 24878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return mMonthHeight; 24978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette } 25078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette 25178bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette public int getCellWidth() { 25278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return mCellWidth; 25378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette } 25478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette 2550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setMonthTextAppearance(int resId) { 25678bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette applyTextAppearance(mMonthPaint, resId); 25760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 2580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 2590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setDayOfWeekTextAppearance(int resId) { 2620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette applyTextAppearance(mDayOfWeekPaint, resId); 2630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 2640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setDayTextAppearance(int resId) { 2670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final ColorStateList textColor = applyTextAppearance(mDayPaint, resId); 2680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (textColor != null) { 2690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayTextColor = textColor; 2700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 2730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 2740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 275c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette /** 276c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette * Sets up the text and style properties for painting. 277c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette */ 2780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void initPaints(Resources res) { 2790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String monthTypeface = res.getString(R.string.date_picker_month_typeface); 2800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface); 2810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final String dayTypeface = res.getString(R.string.date_picker_day_typeface); 2820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 2830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int monthTextSize = res.getDimensionPixelSize( 2840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.dimen.date_picker_month_text_size); 2850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int dayOfWeekTextSize = res.getDimensionPixelSize( 2860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.dimen.date_picker_day_of_week_text_size); 2870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int dayTextSize = res.getDimensionPixelSize( 2880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette R.dimen.date_picker_day_text_size); 2890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 290c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setAntiAlias(true); 2910ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mMonthPaint.setTextSize(monthTextSize); 2920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0)); 293c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setTextAlign(Align.CENTER); 294c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setStyle(Style.FILL); 295c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 296c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setAntiAlias(true); 2970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayOfWeekPaint.setTextSize(dayOfWeekTextSize); 2980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0)); 299c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setTextAlign(Align.CENTER); 300c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setStyle(Style.FILL); 301c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 3020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaySelectorPaint.setAntiAlias(true); 3030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaySelectorPaint.setStyle(Style.FILL); 3040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 3050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayHighlightPaint.setAntiAlias(true); 3060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayHighlightPaint.setStyle(Style.FILL); 307c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 308e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setAntiAlias(true); 309e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setStyle(Style.FILL); 310e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 311c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayPaint.setAntiAlias(true); 3120ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayPaint.setTextSize(dayTextSize); 3130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayPaint.setTypeface(Typeface.create(dayTypeface, 0)); 314c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayPaint.setTextAlign(Align.CENTER); 315c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayPaint.setStyle(Style.FILL); 316bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 317bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 318c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette void setMonthTextColor(ColorStateList monthTextColor) { 319c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0); 320c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mMonthPaint.setColor(enabledColor); 321c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 322c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette } 323bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 324c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) { 325c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0); 326c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayOfWeekPaint.setColor(enabledColor); 327c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 328c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette } 329bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 330c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette void setDayTextColor(ColorStateList dayTextColor) { 331c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mDayTextColor = dayTextColor; 332c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 333c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette } 334bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 3350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette void setDaySelectorColor(ColorStateList dayBackgroundColor) { 3365dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette final int activatedColor = dayBackgroundColor.getColorForState( 337c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); 3380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaySelectorPaint.setColor(activatedColor); 339e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setColor(activatedColor); 340e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDayHighlightSelectorPaint.setAlpha(SELECTED_HIGHLIGHT_ALPHA); 341c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette invalidate(); 342bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 343bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 3440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette void setDayHighlightColor(ColorStateList dayHighlightColor) { 3450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int pressedColor = dayHighlightColor.getColorForState( 3460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0); 3470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDayHighlightPaint.setColor(pressedColor); 3480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 349bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 350bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 351bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public void setOnDayClickListener(OnDayClickListener listener) { 352bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mOnDayClickListener = listener; 353bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 354bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 355bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 356bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public boolean dispatchHoverEvent(MotionEvent event) { 357bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // First right-of-refusal goes the touch exploration helper. 3580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event); 359bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 360bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 361bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 362bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public boolean onTouchEvent(MotionEvent event) { 36360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int x = (int) (event.getX() + 0.5f); 36460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int y = (int) (event.getY() + 0.5f); 36560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 366ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int action = event.getAction(); 367ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette switch (action) { 3680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_DOWN: 3690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_MOVE: 37078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final int touchedItem = getDayAtLocation(x, y); 371e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mIsTouchHighlighted = true; 372e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay != touchedItem) { 373e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = touchedItem; 374e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mPreviouslyHighlightedDay = touchedItem; 3750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 376bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 377ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) { 378ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette // Touch something that's not an item, reject event. 379ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette return false; 380ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 381bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio break; 3820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 3830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_UP: 38478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final int clickedDay = getDayAtLocation(x, y); 38578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette onDayClicked(clickedDay); 3860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Fall through. 3870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette case MotionEvent.ACTION_CANCEL: 3880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Reset touched day on stream end. 389e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = -1; 390e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mIsTouchHighlighted = false; 3910ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 3920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette break; 393bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 394bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return true; 395bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 396bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 397bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 398e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount public boolean onKeyDown(int keyCode, KeyEvent event) { 399e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // We need to handle focus change within the SimpleMonthView because we are simulating 400e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // multiple Views. The arrow keys will move between days until there is no space (no 401e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // day to the left, top, right, or bottom). Focus forward and back jumps out of the 402e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // SimpleMonthView, skipping over other SimpleMonthViews in the parent ViewPager 403e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // to the next focusable View in the hierarchy. 404e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount boolean focusChanged = false; 405e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount switch (event.getKeyCode()) { 406e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_LEFT: 407e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 408e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = moveOneDay(isLayoutRtl()); 409e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 410e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 411e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_RIGHT: 412e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 413e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = moveOneDay(!isLayoutRtl()); 414e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 415e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 416e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_UP: 417e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 418e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 419e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay > 7) { 420e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay -= 7; 421e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 422e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 423e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 424e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 425e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_DOWN: 426e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 427e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 428e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay <= mDaysInMonth - 7) { 429e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay += 7; 430e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 431e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 432e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 433e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 434e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_DPAD_CENTER: 435e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_ENTER: 436e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay != -1) { 437e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount onDayClicked(mHighlightedDay); 438e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return true; 439e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 440e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 441e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case KeyEvent.KEYCODE_TAB: { 442e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int focusChangeDirection = 0; 443e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (event.hasNoModifiers()) { 444e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChangeDirection = View.FOCUS_FORWARD; 445e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 446e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChangeDirection = View.FOCUS_BACKWARD; 447e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 448e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (focusChangeDirection != 0) { 449e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final ViewParent parent = getParent(); 450e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // move out of the ViewPager next/previous 451e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount View nextFocus = this; 452e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount do { 453e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount nextFocus = nextFocus.focusSearch(focusChangeDirection); 454e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } while (nextFocus != null && nextFocus != this && 455e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount nextFocus.getParent() == parent); 456e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (nextFocus != null) { 457e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount nextFocus.requestFocus(); 458e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return true; 459e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 460e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 461e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 462e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 463e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 464e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (focusChanged) { 465e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount invalidate(); 466e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return true; 467e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 468e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return super.onKeyDown(keyCode, event); 469e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 470e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 471e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 472e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean moveOneDay(boolean positive) { 473e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 474e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount boolean focusChanged = false; 475e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (positive) { 476e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (!isLastDayOfWeek(mHighlightedDay) && mHighlightedDay < mDaysInMonth) { 477e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay++; 478e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 479e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 480e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 481e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (!isFirstDayOfWeek(mHighlightedDay) && mHighlightedDay > 1) { 482e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay--; 483e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount focusChanged = true; 484e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 485e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 486e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return focusChanged; 487e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 488e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 489e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 490e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction, 491e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Nullable Rect previouslyFocusedRect) { 492e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (gainFocus) { 493e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // If we've gained focus through arrow keys, we should find the day closest 494e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // to the focus rect. If we've gained focus through forward/back, we should 495e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // focus on the selected day if there is one. 496e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int offset = findDayOffset(); 497e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount switch(direction) { 498e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_RIGHT: { 499e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int row = findClosestRow(previouslyFocusedRect); 500e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = row == 0 ? 1 : (row * DAYS_IN_WEEK) - offset + 1; 501e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 502e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 503e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_LEFT: { 504e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int row = findClosestRow(previouslyFocusedRect) + 1; 505e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = Math.min(mDaysInMonth, (row * DAYS_IN_WEEK) - offset); 506e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 507e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 508e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_DOWN: { 509e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int col = findClosestColumn(previouslyFocusedRect); 510e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int day = col - offset + 1; 511e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = day < 1 ? day + DAYS_IN_WEEK : day; 512e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 513e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 514e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount case View.FOCUS_UP: { 515e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int col = findClosestColumn(previouslyFocusedRect); 516e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int maxWeeks = (offset + mDaysInMonth) / DAYS_IN_WEEK; 517e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int day = col - offset + (DAYS_IN_WEEK * maxWeeks) + 1; 518e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = day > mDaysInMonth ? day - DAYS_IN_WEEK : day; 519e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount break; 520e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 521e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 522e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount ensureFocusedDay(); 523e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount invalidate(); 524e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 525e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 526e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 527e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 528e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount /** 529e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * Returns the row (0 indexed) closest to previouslyFocusedRect or center if null. 530e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount */ 531e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int findClosestRow(@Nullable Rect previouslyFocusedRect) { 532e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (previouslyFocusedRect == null) { 533e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return 3; 534e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 535e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int centerY = previouslyFocusedRect.centerY(); 536e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 537e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final TextPaint p = mDayPaint; 538e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int headerHeight = mMonthHeight + mDayOfWeekHeight; 539e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int rowHeight = mDayHeight; 540e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 541e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // Text is vertically centered within the row height. 542e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final float halfLineHeight = (p.ascent() + p.descent()) / 2f; 543e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int rowCenter = headerHeight + rowHeight / 2; 544e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 545e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount centerY -= rowCenter - halfLineHeight; 546e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int row = Math.round(centerY / (float) rowHeight); 547e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int maxDay = findDayOffset() + mDaysInMonth; 548e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int maxRows = (maxDay / DAYS_IN_WEEK) - ((maxDay % DAYS_IN_WEEK == 0) ? 1 : 0); 549e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 550e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount row = MathUtils.constrain(row, 0, maxRows); 551e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return row; 552e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 553e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 554e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 555e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount /** 556e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * Returns the column (0 indexed) closest to the previouslyFocusedRect or center if null. 557e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * The 0 index is related to the first day of the week. 558e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount */ 559e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private int findClosestColumn(@Nullable Rect previouslyFocusedRect) { 560e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (previouslyFocusedRect == null) { 561e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return DAYS_IN_WEEK / 2; 562e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 563e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount int centerX = previouslyFocusedRect.centerX() - mPaddingLeft; 564e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int columnFromLeft = 565e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount MathUtils.constrain(centerX / mCellWidth, 0, DAYS_IN_WEEK - 1); 566e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return isLayoutRtl() ? DAYS_IN_WEEK - columnFromLeft - 1: columnFromLeft; 567e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 568e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 569e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 570e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 571e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount public void getFocusedRect(Rect r) { 572e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay > 0) { 573e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount getBoundsForDay(mHighlightedDay, r); 574e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else { 575e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount super.getFocusedRect(r); 576e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 577e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 578e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 579e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 580e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount protected void onFocusLost() { 581e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (!mIsTouchHighlighted) { 582e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount // Unhighlight a day. 583e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mPreviouslyHighlightedDay = mHighlightedDay; 584e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = -1; 585e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount invalidate(); 586e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 587e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount super.onFocusLost(); 588e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 589e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 590e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount /** 591e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * Ensure some day is highlighted. If a day isn't highlighted, it chooses the selected day, 592e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount * if possible, or the first day of the month if not. 593e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount */ 594e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private void ensureFocusedDay() { 595e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mHighlightedDay != -1) { 596e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return; 597e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 598e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mPreviouslyHighlightedDay != -1) { 599e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = mPreviouslyHighlightedDay; 600e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return; 601e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 602e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount if (mActivatedDay != -1) { 603e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = mActivatedDay; 604e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return; 605e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 606e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mHighlightedDay = 1; 607e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 608e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 609e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean isFirstDayOfWeek(int day) { 610e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int offset = findDayOffset(); 611e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return (offset + day - 1) % DAYS_IN_WEEK == 0; 612e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 613e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 614e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount private boolean isLastDayOfWeek(int day) { 615e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final int offset = findDayOffset(); 616e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount return (offset + day) % DAYS_IN_WEEK == 0; 617e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } 618e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount 619e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount @Override 620bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onDraw(Canvas canvas) { 6210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int paddingLeft = getPaddingLeft(); 6220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int paddingTop = getPaddingTop(); 6230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette canvas.translate(paddingLeft, paddingTop); 6240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette drawMonth(canvas); 6260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette drawDaysOfWeek(canvas); 627bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio drawDays(canvas); 6280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette canvas.translate(-paddingLeft, -paddingTop); 6300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void drawMonth(Canvas canvas) { 6330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final float x = mPaddedWidth / 2f; 6340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Vertically centered within the month header height. 6360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent(); 6370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final float y = (mMonthHeight - lineHeight) / 2f; 6380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 639e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette canvas.drawText(mMonthYearLabel, x, y, mMonthPaint); 640e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette } 641e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 642e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette public String getMonthYearLabel() { 643e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette return mMonthYearLabel; 6440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void drawDaysOfWeek(Canvas canvas) { 64760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final TextPaint p = mDayOfWeekPaint; 64860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int headerHeight = mMonthHeight; 64960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowHeight = mDayOfWeekHeight; 650f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int colWidth = mCellWidth; 65160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 65260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // Text is vertically centered within the day of week height. 65360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final float halfLineHeight = (p.ascent() + p.descent()) / 2f; 65460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowCenter = headerHeight + rowHeight / 2; 65560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 65660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette for (int col = 0; col < DAYS_IN_WEEK; col++) { 65760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int colCenter = colWidth * col + colWidth / 2; 658ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int colCenterRtl; 659ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 660ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = mPaddedWidth - colCenter; 661ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 662ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = colCenter; 663ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 664ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 665d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette final String label = mDayOfWeekLabels[col]; 666ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p); 6670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 6710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Draws the month days. 6720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 6730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private void drawDays(Canvas canvas) { 67460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final TextPaint p = mDayPaint; 67560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int headerHeight = mMonthHeight + mDayOfWeekHeight; 67660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowHeight = mDayHeight; 677f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int colWidth = mCellWidth; 6780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 67960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // Text is vertically centered within the row height. 68060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final float halfLineHeight = (p.ascent() + p.descent()) / 2f; 68160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette int rowCenter = headerHeight + rowHeight / 2; 6820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 68360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) { 68460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int colCenter = colWidth * col + colWidth / 2; 685ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int colCenterRtl; 686ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 687ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = mPaddedWidth - colCenter; 688ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 689ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette colCenterRtl = colCenter; 690ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 691ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 6920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette int stateMask = 0; 6930ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6945c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette final boolean isDayEnabled = isDayEnabled(day); 6955c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isDayEnabled) { 6960ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette stateMask |= StateSet.VIEW_STATE_ENABLED; 6970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 6980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 6990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final boolean isDayActivated = mActivatedDay == day; 700e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final boolean isDayHighlighted = mHighlightedDay == day; 7010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (isDayActivated) { 7020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette stateMask |= StateSet.VIEW_STATE_ACTIVATED; 7030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Adjust the circle to be centered on the row. 705e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount final Paint paint = isDayHighlighted ? mDayHighlightSelectorPaint : 706e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount mDaySelectorPaint; 707e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, paint); 708e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount } else if (isDayHighlighted) { 7090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette stateMask |= StateSet.VIEW_STATE_PRESSED; 7100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7115c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isDayEnabled) { 7125c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette // Adjust the circle to be centered on the row. 7135c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette canvas.drawCircle(colCenterRtl, rowCenter, 7145c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette mDaySelectorRadius, mDayHighlightPaint); 7155c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 7160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final boolean isDayToday = mToday == day; 7190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int dayTextColor; 7200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (isDayToday && !isDayActivated) { 7210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette dayTextColor = mDaySelectorPaint.getColor(); 7220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } else { 7230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int[] stateSet = StateSet.get(stateMask); 7240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette dayTextColor = mDayTextColor.getColorForState(stateSet, 0); 7250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 72660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette p.setColor(dayTextColor); 7270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7281ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase canvas.drawText(mDayFormatter.format(day), colCenterRtl, rowCenter - halfLineHeight, p); 7290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 73060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette col++; 7310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 73260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette if (col == DAYS_IN_WEEK) { 73360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette col = 0; 73460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette rowCenter += rowHeight; 7350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 737bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 738bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 7395c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette private boolean isDayEnabled(int day) { 7405c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette return day >= mEnabledDayStart && day <= mEnabledDayEnd; 7415c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 7425c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 7435c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette private boolean isValidDayOfMonth(int day) { 7445c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette return day >= 1 && day <= mDaysInMonth; 7455c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 7465c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 747518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette private static boolean isValidDayOfWeek(int day) { 7485878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY; 7495878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette } 7505878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette 7515878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette private static boolean isValidMonth(int month) { 7525878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette return month >= Calendar.JANUARY && month <= Calendar.DECEMBER; 75375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio } 75475b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio 755bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 7560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Sets the selected day. 757bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 7580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param dayOfMonth the selected day of the month, or {@code -1} to clear 7590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * the selection 7600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 7610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setSelectedDay(int dayOfMonth) { 7620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mActivatedDay = dayOfMonth; 7630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Invalidate cached accessibility information. 7650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mTouchHelper.invalidateRoot(); 7660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 7670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 7700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Sets the first day of the week. 7710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 7720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param weekStart which day the week should start on, valid values are 7730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} 7740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette */ 7750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette public void setFirstDayOfWeek(int weekStart) { 776da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette if (DEBUG_WRONG_DATE) { 777da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette Log.d(LOG_TAG, "enter setFirstDayOfWeek(" + weekStart + ")", new Exception()); 778da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette } 779da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette 7800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (isValidDayOfWeek(weekStart)) { 7810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mWeekStart = weekStart; 7820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } else { 7830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mWeekStart = mCalendar.getFirstDayOfWeek(); 7840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 786da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette if (DEBUG_WRONG_DATE) { 787da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette Log.d(LOG_TAG, "mWeekStart <=" + mWeekStart); 788da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette } 789da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette 790e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette updateDayOfWeekLabels(); 791e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 7920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Invalidate cached accessibility information. 7930ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mTouchHelper.invalidateRoot(); 7940ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette invalidate(); 7950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 7960ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 7970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette /** 7980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Sets all the parameters for displaying this week. 7990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * <p> 8000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Parameters have a default value and will only update if a new value is 8010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * included, except for focus month, which will always default to no focus 8020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * month if no value is passed in. The only required parameter is the week 8030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * start. 8040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 8050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param selectedDay the selected day of the month, or -1 for no selection 8060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param month the month 8070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param year the year 8080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param weekStart which day the week should start on, valid values are 8090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} 8100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param enabledDayStart the first enabled day 8110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param enabledDayEnd the last enabled day 812bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 81375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart, 81475b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio int enabledDayEnd) { 815d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette if (DEBUG_WRONG_DATE) { 816d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette Log.d(LOG_TAG, "setMonthParams(" + selectedDay + ", " + month + ", " + year + ", " 817d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette + weekStart + ", " + enabledDayStart + ", " + enabledDayEnd + ")"); 818d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette } 819d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette 820c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette mActivatedDay = selectedDay; 821bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 8225878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette if (isValidMonth(month)) { 82375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio mMonth = month; 82475b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio } 82575b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio mYear = year; 826bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 827bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mCalendar.set(Calendar.MONTH, mMonth); 828bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mCalendar.set(Calendar.YEAR, mYear); 829bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mCalendar.set(Calendar.DAY_OF_MONTH, 1); 830bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK); 831bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 832518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette if (isValidDayOfWeek(weekStart)) { 83375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio mWeekStart = weekStart; 834bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } else { 835bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mWeekStart = mCalendar.getFirstDayOfWeek(); 836bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 837bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 8380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // Figure out what day today is. 8390ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final Calendar today = Calendar.getInstance(); 8400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mToday = -1; 8410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mDaysInMonth = getDaysInMonth(mMonth, mYear); 8420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette for (int i = 0; i < mDaysInMonth; i++) { 843bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio final int day = i + 1; 844bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio if (sameDay(day, today)) { 845bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mToday = day; 846bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 847bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 8480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 8495c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette mEnabledDayStart = MathUtils.constrain(enabledDayStart, 1, mDaysInMonth); 8505c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette mEnabledDayEnd = MathUtils.constrain(enabledDayEnd, mEnabledDayStart, mDaysInMonth); 8515c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 852bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // Invalidate cached accessibility information. 853bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper.invalidateRoot(); 854e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette 855e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette updateMonthYearLabel(); 856d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette 857d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette if (DEBUG_WRONG_DATE) { 858d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette Log.d(LOG_TAG, "mMonth = " + mMonth); 859d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette Log.d(LOG_TAG, "mDayOfWeekStart = " + mDayOfWeekStart); 860d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette Log.d(LOG_TAG, "mWeekStart = " + mWeekStart); 861d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette Log.d(LOG_TAG, "mDaysInMonth = " + mDaysInMonth); 862d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette Log.d(LOG_TAG, "mToday = " + mToday); 863d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette } 864bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 865bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 866bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static int getDaysInMonth(int month, int year) { 867bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio switch (month) { 868bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.JANUARY: 869bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.MARCH: 870bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.MAY: 871bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.JULY: 872bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.AUGUST: 873bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.OCTOBER: 874bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.DECEMBER: 875bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return 31; 876bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.APRIL: 877bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.JUNE: 878bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.SEPTEMBER: 879bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.NOVEMBER: 880bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return 30; 881bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case Calendar.FEBRUARY: 882bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return (year % 4 == 0) ? 29 : 28; 883bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio default: 884bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio throw new IllegalArgumentException("Invalid Month"); 885bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 886bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 887bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 8880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private boolean sameDay(int day, Calendar today) { 8890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH) 8900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette && day == today.get(Calendar.DAY_OF_MONTH); 891bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 892bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 893bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 894bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 895816aa142023c75cf427401c21831193998bdf233Alan Viverette final int preferredHeight = mDesiredDayHeight * MAX_WEEKS_IN_MONTH 896816aa142023c75cf427401c21831193998bdf233Alan Viverette + mDesiredDayOfWeekHeight + mDesiredMonthHeight 897816aa142023c75cf427401c21831193998bdf233Alan Viverette + getPaddingTop() + getPaddingBottom(); 898f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK 8990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette + getPaddingStart() + getPaddingEnd(); 9000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); 9010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec); 9020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette setMeasuredDimension(resolvedWidth, resolvedHeight); 903bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 904bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 905bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 906ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { 907ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette super.onRtlPropertiesChanged(layoutDirection); 908ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 909ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette requestLayout(); 910ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 911ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 912ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette @Override 913f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 914f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette if (!changed) { 915f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette return; 916f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette } 917f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 918f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Let's initialize a completely reasonable number of variables. 919f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int w = right - left; 920f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int h = bottom - top; 921f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingLeft = getPaddingLeft(); 922f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingTop = getPaddingTop(); 923f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingRight = getPaddingRight(); 924f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddingBottom = getPaddingBottom(); 925f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedRight = w - paddingRight; 926f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedBottom = h - paddingBottom; 927f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedWidth = paddedRight - paddingLeft; 928f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int paddedHeight = paddedBottom - paddingTop; 929f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) { 930f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette return; 931f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette } 932f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 933f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mPaddedWidth = paddedWidth; 934f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mPaddedHeight = paddedHeight; 935f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 936f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // We may have been laid out smaller than our preferred size. If so, 937f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // scale all dimensions to fit. 938f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom; 939f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final float scaleH = paddedHeight / (float) measuredPaddedHeight; 940f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int monthHeight = (int) (mDesiredMonthHeight * scaleH); 94160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; 942f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mMonthHeight = monthHeight; 943f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH); 944f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDayHeight = (int) (mDesiredDayHeight * scaleH); 945f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mCellWidth = cellWidth; 946f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette 947f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // Compute the largest day selector radius that's still within the clip 948f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette // bounds and desired selector radius. 949f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight); 950f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int maxSelectorHeight = mDayHeight / 2 + paddingBottom; 951f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius, 952f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette Math.min(maxSelectorWidth, maxSelectorHeight)); 95360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 954bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // Invalidate cached accessibility information. 955bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper.invalidateRoot(); 956bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 957bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 9580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette private int findDayOffset() { 9590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int offset = mDayOfWeekStart - mWeekStart; 9600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (mDayOfWeekStart < mWeekStart) { 9610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return offset + DAYS_IN_WEEK; 962bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 9630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return offset; 964bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 965bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 966bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 96778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * Calculates the day of the month at the specified touch position. Returns 96878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * the day of the month or -1 if the position wasn't in a valid day. 9690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * 9700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param x the x position of the touch event 9710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param y the y position of the touch event 97278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @return the day of the month at (x, y), or -1 if the position wasn't in 97378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * a valid day 974bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 97578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette private int getDayAtLocation(int x, int y) { 97660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int paddedX = x - getPaddingLeft(); 9770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (paddedX < 0 || paddedX >= mPaddedWidth) { 9780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return -1; 9790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 980c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 9810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int headerHeight = mMonthHeight + mDayOfWeekHeight; 98260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int paddedY = y - getPaddingTop(); 9830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (paddedY < headerHeight || paddedY >= mPaddedHeight) { 9840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return -1; 9850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 986c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette 987ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette // Adjust for RTL after applying padding. 988ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int paddedXRtl; 989ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 990ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette paddedXRtl = mPaddedWidth - paddedX; 991ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 992ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette paddedXRtl = paddedX; 993ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 994ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette 9950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int row = (paddedY - headerHeight) / mDayHeight; 996ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth; 9970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int index = col + row * DAYS_IN_WEEK; 9980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int day = index + 1 - findDayOffset(); 9995c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (!isValidDayOfMonth(day)) { 10000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return -1; 1001bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1002bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 10030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return day; 1004bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1005bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1006bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 10070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * Calculates the bounds of the specified day. 1008bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 100978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @param id the day of the month 10100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param outBounds the rect to populate with bounds 1011bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 101278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette private boolean getBoundsForDay(int id, Rect outBounds) { 10135c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (!isValidDayOfMonth(id)) { 10140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return false; 1015bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1016bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 101760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int index = id - 1 + findDayOffset(); 101860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 1019ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette // Compute left edge, taking into account RTL. 10200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int col = index % DAYS_IN_WEEK; 1021f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette final int colWidth = mCellWidth; 1022ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette final int left; 1023ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette if (isLayoutRtl()) { 1024ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette left = getWidth() - getPaddingRight() - (col + 1) * colWidth; 1025ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } else { 1026ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette left = getPaddingLeft() + col * colWidth; 1027ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette } 10280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 102960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // Compute top edge. 103060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int row = index / DAYS_IN_WEEK; 103160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int rowHeight = mDayHeight; 10320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette final int headerHeight = mMonthHeight + mDayOfWeekHeight; 103360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final int top = getPaddingTop() + headerHeight + row * rowHeight; 10340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 103560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette outBounds.set(left, top, left + colWidth, top + rowHeight); 1036bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 103778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return true; 103860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 103960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 104060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette /** 1041bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Called when the user clicks on a day. Handles callbacks to the 1042bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * {@link OnDayClickListener} if one is set. 1043bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 10440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette * @param day the day that was clicked 1045bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 104660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette private boolean onDayClicked(int day) { 10475c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (!isValidDayOfMonth(day) || !isDayEnabled(day)) { 104860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return false; 104960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 105060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 1051bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio if (mOnDayClickListener != null) { 105260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette final Calendar date = Calendar.getInstance(); 1053bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio date.set(mYear, mMonth, day); 1054bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mOnDayClickListener.onDayClick(this, date); 1055bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1056bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1057bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio // This is a no-op if accessibility is turned off. 1058bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED); 105960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return true; 106060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 106160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 106260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette /** 1063bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Provides a virtual view hierarchy for interfacing with an accessibility 1064bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * service. 1065bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 1066bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private class MonthViewTouchHelper extends ExploreByTouchHelper { 1067bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private static final String DATE_FORMAT = "dd MMMM yyyy"; 1068bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1069bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private final Rect mTempRect = new Rect(); 1070bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio private final Calendar mTempCalendar = Calendar.getInstance(); 1071bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1072bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public MonthViewTouchHelper(View host) { 1073bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio super(host); 1074bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1075bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1076bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1077bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected int getVirtualViewAt(float x, float y) { 107878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final int day = getDayAtLocation((int) (x + 0.5f), (int) (y + 0.5f)); 10795c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (day != -1) { 1080bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return day; 1081bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1082bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return ExploreByTouchHelper.INVALID_ID; 1083bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1084bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1085bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1086ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void getVisibleVirtualViews(IntArray virtualViewIds) { 10870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette for (int day = 1; day <= mDaysInMonth; day++) { 1088bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio virtualViewIds.add(day); 1089bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1090bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1091bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1092bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1093bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { 109478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette event.setContentDescription(getDayDescription(virtualViewId)); 1095bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1096bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1097bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1098bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { 109978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect); 11000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette 11010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette if (!hasBounds) { 11020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette // The day is invalid, kill the node. 11030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette mTempRect.setEmpty(); 11040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette node.setContentDescription(""); 11050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette node.setBoundsInParent(mTempRect); 11060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette node.setVisibleToUser(false); 11070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette return; 11080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette } 1109bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 111078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette node.setText(getDayText(virtualViewId)); 111178bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette node.setContentDescription(getDayDescription(virtualViewId)); 1112bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio node.setBoundsInParent(mTempRect); 11135c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 11145c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette final boolean isDayEnabled = isDayEnabled(virtualViewId); 11155c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isDayEnabled) { 11165c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette node.addAction(AccessibilityAction.ACTION_CLICK); 11175c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette } 11185c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette 11195c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette node.setEnabled(isDayEnabled); 1120bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1121c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette if (virtualViewId == mActivatedDay) { 112260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette // TODO: This should use activated once that's supported. 112360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette node.setChecked(true); 1124bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1125bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1126bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1127bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1128bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio @Override 1129bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio protected boolean onPerformActionForVirtualView(int virtualViewId, int action, 1130bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio Bundle arguments) { 1131bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio switch (action) { 1132bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio case AccessibilityNodeInfo.ACTION_CLICK: 113378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette return onDayClicked(virtualViewId); 1134bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1135bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1136bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio return false; 1137bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1138bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1139bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 114060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * Generates a description for a given virtual view. 1141bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * 114278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @param id the day to generate a description for 114360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * @return a description of the virtual view 1144bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 114578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette private CharSequence getDayDescription(int id) { 11465c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isValidDayOfMonth(id)) { 114760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette mTempCalendar.set(mYear, mMonth, id); 114860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis()); 114960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 1150bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 115160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return ""; 115260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette } 115360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette 115460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette /** 115560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * Generates displayed text for a given virtual view. 115660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * 115778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette * @param id the day to generate text for 115860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette * @return the visible text of the virtual view 115960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette */ 116078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette private CharSequence getDayText(int id) { 11615c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette if (isValidDayOfMonth(id)) { 11621ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase return mDayFormatter.format(id); 1163bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1164bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 116560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette return null; 1166bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1167bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1168bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio 1169bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio /** 1170bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Handles callbacks when the user clicks on a time object. 1171bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */ 1172bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio public interface OnDayClickListener { 117378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette void onDayClick(SimpleMonthView view, Calendar day); 1174bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio } 1175bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio} 1176