13e9818e0267619fecebd55095ab26c53eff92e93James Kung/* 23e9818e0267619fecebd55095ab26c53eff92e93James Kung * Copyright (C) 2013 The Android Open Source Project 33e9818e0267619fecebd55095ab26c53eff92e93James Kung * 43e9818e0267619fecebd55095ab26c53eff92e93James Kung * Licensed under the Apache License, Version 2.0 (the "License"); 53e9818e0267619fecebd55095ab26c53eff92e93James Kung * you may not use this file except in compliance with the License. 63e9818e0267619fecebd55095ab26c53eff92e93James Kung * You may obtain a copy of the License at 73e9818e0267619fecebd55095ab26c53eff92e93James Kung * 83e9818e0267619fecebd55095ab26c53eff92e93James Kung * http://www.apache.org/licenses/LICENSE-2.0 93e9818e0267619fecebd55095ab26c53eff92e93James Kung * 103e9818e0267619fecebd55095ab26c53eff92e93James Kung * Unless required by applicable law or agreed to in writing, software 113e9818e0267619fecebd55095ab26c53eff92e93James Kung * distributed under the License is distributed on an "AS IS" BASIS, 123e9818e0267619fecebd55095ab26c53eff92e93James Kung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133e9818e0267619fecebd55095ab26c53eff92e93James Kung * See the License for the specific language governing permissions and 143e9818e0267619fecebd55095ab26c53eff92e93James Kung * limitations under the License. 153e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 163e9818e0267619fecebd55095ab26c53eff92e93James Kung 173e9818e0267619fecebd55095ab26c53eff92e93James Kungpackage com.android.datetimepicker.date; 183e9818e0267619fecebd55095ab26c53eff92e93James Kung 19cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport android.annotation.SuppressLint; 203e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.content.Context; 21a35f6b580aefd7bf1c7e92306e13dacd44316714Alan Viveretteimport android.os.Build; 22cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport android.os.Bundle; 233e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.os.Handler; 24385d36f15358e7d035b2e89e395588eb7952ebc2James Kungimport android.util.AttributeSet; 253e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.util.Log; 263e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.view.View; 273e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.view.ViewConfiguration; 28cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport android.view.accessibility.AccessibilityEvent; 29cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport android.view.accessibility.AccessibilityNodeInfo; 303e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.widget.AbsListView; 313e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.widget.AbsListView.OnScrollListener; 323e9818e0267619fecebd55095ab26c53eff92e93James Kungimport android.widget.ListView; 333e9818e0267619fecebd55095ab26c53eff92e93James Kung 34cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport com.android.datetimepicker.Utils; 352e00aa34c051111529290cf23c6ba940c2c0c142James Kungimport com.android.datetimepicker.date.DatePickerDialog.OnDateChangedListener; 36e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport com.android.datetimepicker.date.MonthAdapter.CalendarDay; 373e9818e0267619fecebd55095ab26c53eff92e93James Kung 38cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport java.text.SimpleDateFormat; 39cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport java.util.Calendar; 40cb3f2522609186db6239ad154af275957118295cSam Blitzsteinimport java.util.Locale; 41cb3f2522609186db6239ad154af275957118295cSam Blitzstein 423e9818e0267619fecebd55095ab26c53eff92e93James Kung/** 433e9818e0267619fecebd55095ab26c53eff92e93James Kung * This displays a list of months in a calendar format with selectable days. 443e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 45e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungpublic abstract class DayPickerView extends ListView implements OnScrollListener, 46e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung OnDateChangedListener { 473e9818e0267619fecebd55095ab26c53eff92e93James Kung 483e9818e0267619fecebd55095ab26c53eff92e93James Kung private static final String TAG = "MonthFragment"; 493e9818e0267619fecebd55095ab26c53eff92e93James Kung 503e9818e0267619fecebd55095ab26c53eff92e93James Kung // Affects when the month selection will change while scrolling up 513e9818e0267619fecebd55095ab26c53eff92e93James Kung protected static final int SCROLL_HYST_WEEKS = 2; 523e9818e0267619fecebd55095ab26c53eff92e93James Kung // How long the GoTo fling animation should last 533e9818e0267619fecebd55095ab26c53eff92e93James Kung protected static final int GOTO_SCROLL_DURATION = 250; 543e9818e0267619fecebd55095ab26c53eff92e93James Kung // How long to wait after receiving an onScrollStateChanged notification 553e9818e0267619fecebd55095ab26c53eff92e93James Kung // before acting on it 563e9818e0267619fecebd55095ab26c53eff92e93James Kung protected static final int SCROLL_CHANGE_DELAY = 40; 573e9818e0267619fecebd55095ab26c53eff92e93James Kung // The number of days to display in each week 583e9818e0267619fecebd55095ab26c53eff92e93James Kung public static final int DAYS_PER_WEEK = 7; 593e9818e0267619fecebd55095ab26c53eff92e93James Kung public static int LIST_TOP_OFFSET = -1; // so that the top line will be 603e9818e0267619fecebd55095ab26c53eff92e93James Kung // under the separator 613e9818e0267619fecebd55095ab26c53eff92e93James Kung // You can override these numbers to get a different appearance 623e9818e0267619fecebd55095ab26c53eff92e93James Kung protected int mNumWeeks = 6; 633e9818e0267619fecebd55095ab26c53eff92e93James Kung protected boolean mShowWeekNumber = false; 643e9818e0267619fecebd55095ab26c53eff92e93James Kung protected int mDaysPerWeek = 7; 65cb3f2522609186db6239ad154af275957118295cSam Blitzstein private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault()); 663e9818e0267619fecebd55095ab26c53eff92e93James Kung 673e9818e0267619fecebd55095ab26c53eff92e93James Kung // These affect the scroll speed and feel 683e9818e0267619fecebd55095ab26c53eff92e93James Kung protected float mFriction = 1.0f; 693e9818e0267619fecebd55095ab26c53eff92e93James Kung 703e9818e0267619fecebd55095ab26c53eff92e93James Kung protected Context mContext; 713e9818e0267619fecebd55095ab26c53eff92e93James Kung protected Handler mHandler; 723e9818e0267619fecebd55095ab26c53eff92e93James Kung 733e9818e0267619fecebd55095ab26c53eff92e93James Kung // highlighted time 743e9818e0267619fecebd55095ab26c53eff92e93James Kung protected CalendarDay mSelectedDay = new CalendarDay(); 75e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung protected MonthAdapter mAdapter; 763e9818e0267619fecebd55095ab26c53eff92e93James Kung 773e9818e0267619fecebd55095ab26c53eff92e93James Kung protected CalendarDay mTempDay = new CalendarDay(); 783e9818e0267619fecebd55095ab26c53eff92e93James Kung 793e9818e0267619fecebd55095ab26c53eff92e93James Kung // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0). 803e9818e0267619fecebd55095ab26c53eff92e93James Kung protected int mFirstDayOfWeek; 813e9818e0267619fecebd55095ab26c53eff92e93James Kung // The last name announced by accessibility 823e9818e0267619fecebd55095ab26c53eff92e93James Kung protected CharSequence mPrevMonthName; 833e9818e0267619fecebd55095ab26c53eff92e93James Kung // which month should be displayed/highlighted [0-11] 843e9818e0267619fecebd55095ab26c53eff92e93James Kung protected int mCurrentMonthDisplayed; 853e9818e0267619fecebd55095ab26c53eff92e93James Kung // used for tracking during a scroll 863e9818e0267619fecebd55095ab26c53eff92e93James Kung protected long mPreviousScrollPosition; 873e9818e0267619fecebd55095ab26c53eff92e93James Kung // used for tracking what state listview is in 883e9818e0267619fecebd55095ab26c53eff92e93James Kung protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; 893e9818e0267619fecebd55095ab26c53eff92e93James Kung // used for tracking what state listview is in 903e9818e0267619fecebd55095ab26c53eff92e93James Kung protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; 913e9818e0267619fecebd55095ab26c53eff92e93James Kung 92385d36f15358e7d035b2e89e395588eb7952ebc2James Kung private DatePickerController mController; 93cb3f2522609186db6239ad154af275957118295cSam Blitzstein private boolean mPerformingScroll; 943e9818e0267619fecebd55095ab26c53eff92e93James Kung 95385d36f15358e7d035b2e89e395588eb7952ebc2James Kung public DayPickerView(Context context, AttributeSet attrs) { 96385d36f15358e7d035b2e89e395588eb7952ebc2James Kung super(context, attrs); 97385d36f15358e7d035b2e89e395588eb7952ebc2James Kung init(context); 98385d36f15358e7d035b2e89e395588eb7952ebc2James Kung } 99385d36f15358e7d035b2e89e395588eb7952ebc2James Kung 1003e9818e0267619fecebd55095ab26c53eff92e93James Kung public DayPickerView(Context context, DatePickerController controller) { 1013e9818e0267619fecebd55095ab26c53eff92e93James Kung super(context); 102385d36f15358e7d035b2e89e395588eb7952ebc2James Kung init(context); 103385d36f15358e7d035b2e89e395588eb7952ebc2James Kung setController(controller); 104385d36f15358e7d035b2e89e395588eb7952ebc2James Kung } 105385d36f15358e7d035b2e89e395588eb7952ebc2James Kung 106385d36f15358e7d035b2e89e395588eb7952ebc2James Kung public void setController(DatePickerController controller) { 1073e9818e0267619fecebd55095ab26c53eff92e93James Kung mController = controller; 1082e00aa34c051111529290cf23c6ba940c2c0c142James Kung mController.registerOnDateChangedListener(this); 109e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung refreshAdapter(); 1102e00aa34c051111529290cf23c6ba940c2c0c142James Kung onDateChanged(); 1113e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1123e9818e0267619fecebd55095ab26c53eff92e93James Kung 1133e9818e0267619fecebd55095ab26c53eff92e93James Kung public void init(Context context) { 114385d36f15358e7d035b2e89e395588eb7952ebc2James Kung mHandler = new Handler(); 115385d36f15358e7d035b2e89e395588eb7952ebc2James Kung setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 116385d36f15358e7d035b2e89e395588eb7952ebc2James Kung setDrawSelectorOnTop(false); 117385d36f15358e7d035b2e89e395588eb7952ebc2James Kung 1183e9818e0267619fecebd55095ab26c53eff92e93James Kung mContext = context; 1193e9818e0267619fecebd55095ab26c53eff92e93James Kung setUpListView(); 1203e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1213e9818e0267619fecebd55095ab26c53eff92e93James Kung 1223e9818e0267619fecebd55095ab26c53eff92e93James Kung public void onChange() { 123e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung refreshAdapter(); 1243e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1253e9818e0267619fecebd55095ab26c53eff92e93James Kung 1263e9818e0267619fecebd55095ab26c53eff92e93James Kung /** 1273e9818e0267619fecebd55095ab26c53eff92e93James Kung * Creates a new adapter if necessary and sets up its parameters. Override 1283e9818e0267619fecebd55095ab26c53eff92e93James Kung * this method to provide a custom adapter. 1293e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 130e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung protected void refreshAdapter() { 1313e9818e0267619fecebd55095ab26c53eff92e93James Kung if (mAdapter == null) { 132e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung mAdapter = createMonthAdapter(getContext(), mController); 1333e9818e0267619fecebd55095ab26c53eff92e93James Kung } else { 1343e9818e0267619fecebd55095ab26c53eff92e93James Kung mAdapter.setSelectedDay(mSelectedDay); 1353e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1363e9818e0267619fecebd55095ab26c53eff92e93James Kung // refresh the view with the new parameters 137e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung setAdapter(mAdapter); 1383e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1393e9818e0267619fecebd55095ab26c53eff92e93James Kung 140e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung public abstract MonthAdapter createMonthAdapter(Context context, 141e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung DatePickerController controller); 142e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung 1433e9818e0267619fecebd55095ab26c53eff92e93James Kung /* 1443e9818e0267619fecebd55095ab26c53eff92e93James Kung * Sets all the required fields for the list view. Override this method to 1453e9818e0267619fecebd55095ab26c53eff92e93James Kung * set a different list view behavior. 1463e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 1473e9818e0267619fecebd55095ab26c53eff92e93James Kung protected void setUpListView() { 1483e9818e0267619fecebd55095ab26c53eff92e93James Kung // Transparent background on scroll 1493e9818e0267619fecebd55095ab26c53eff92e93James Kung setCacheColorHint(0); 1503e9818e0267619fecebd55095ab26c53eff92e93James Kung // No dividers 1513e9818e0267619fecebd55095ab26c53eff92e93James Kung setDivider(null); 1523e9818e0267619fecebd55095ab26c53eff92e93James Kung // Items are clickable 1533e9818e0267619fecebd55095ab26c53eff92e93James Kung setItemsCanFocus(true); 1543e9818e0267619fecebd55095ab26c53eff92e93James Kung // The thumb gets in the way, so disable it 1553e9818e0267619fecebd55095ab26c53eff92e93James Kung setFastScrollEnabled(false); 1563e9818e0267619fecebd55095ab26c53eff92e93James Kung setVerticalScrollBarEnabled(false); 1573e9818e0267619fecebd55095ab26c53eff92e93James Kung setOnScrollListener(this); 1583e9818e0267619fecebd55095ab26c53eff92e93James Kung setFadingEdgeLength(0); 1593e9818e0267619fecebd55095ab26c53eff92e93James Kung // Make the scrolling behavior nicer 1603e9818e0267619fecebd55095ab26c53eff92e93James Kung setFriction(ViewConfiguration.getScrollFriction() * mFriction); 1613e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1623e9818e0267619fecebd55095ab26c53eff92e93James Kung 1633e9818e0267619fecebd55095ab26c53eff92e93James Kung /** 1643e9818e0267619fecebd55095ab26c53eff92e93James Kung * This moves to the specified time in the view. If the time is not already 1653e9818e0267619fecebd55095ab26c53eff92e93James Kung * in range it will move the list so that the first of the month containing 1663e9818e0267619fecebd55095ab26c53eff92e93James Kung * the time is at the top of the view. If the new time is already in view 1673e9818e0267619fecebd55095ab26c53eff92e93James Kung * the list will not be scrolled unless forceScroll is true. This time may 1683e9818e0267619fecebd55095ab26c53eff92e93James Kung * optionally be highlighted as selected as well. 1693e9818e0267619fecebd55095ab26c53eff92e93James Kung * 1703e9818e0267619fecebd55095ab26c53eff92e93James Kung * @param time The time to move to 1713e9818e0267619fecebd55095ab26c53eff92e93James Kung * @param animate Whether to scroll to the given time or just redraw at the 1723e9818e0267619fecebd55095ab26c53eff92e93James Kung * new location 1733e9818e0267619fecebd55095ab26c53eff92e93James Kung * @param setSelected Whether to set the given time as selected 1743e9818e0267619fecebd55095ab26c53eff92e93James Kung * @param forceScroll Whether to recenter even if the time is already 1753e9818e0267619fecebd55095ab26c53eff92e93James Kung * visible 1763e9818e0267619fecebd55095ab26c53eff92e93James Kung * @return Whether or not the view animated to the new location 1773e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 1783e9818e0267619fecebd55095ab26c53eff92e93James Kung public boolean goTo(CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) { 1793e9818e0267619fecebd55095ab26c53eff92e93James Kung 1803e9818e0267619fecebd55095ab26c53eff92e93James Kung // Set the selected day 1813e9818e0267619fecebd55095ab26c53eff92e93James Kung if (setSelected) { 1823e9818e0267619fecebd55095ab26c53eff92e93James Kung mSelectedDay.set(day); 1833e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1843e9818e0267619fecebd55095ab26c53eff92e93James Kung 1853e9818e0267619fecebd55095ab26c53eff92e93James Kung mTempDay.set(day); 1862e00aa34c051111529290cf23c6ba940c2c0c142James Kung final int position = (day.year - mController.getMinYear()) 187e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * MonthAdapter.MONTHS_IN_YEAR + day.month; 1883e9818e0267619fecebd55095ab26c53eff92e93James Kung 1893e9818e0267619fecebd55095ab26c53eff92e93James Kung View child; 1903e9818e0267619fecebd55095ab26c53eff92e93James Kung int i = 0; 1913e9818e0267619fecebd55095ab26c53eff92e93James Kung int top = 0; 1923e9818e0267619fecebd55095ab26c53eff92e93James Kung // Find a child that's completely in the view 1933e9818e0267619fecebd55095ab26c53eff92e93James Kung do { 1943e9818e0267619fecebd55095ab26c53eff92e93James Kung child = getChildAt(i++); 1953e9818e0267619fecebd55095ab26c53eff92e93James Kung if (child == null) { 1963e9818e0267619fecebd55095ab26c53eff92e93James Kung break; 1973e9818e0267619fecebd55095ab26c53eff92e93James Kung } 1983e9818e0267619fecebd55095ab26c53eff92e93James Kung top = child.getTop(); 1993e9818e0267619fecebd55095ab26c53eff92e93James Kung if (Log.isLoggable(TAG, Log.DEBUG)) { 2003e9818e0267619fecebd55095ab26c53eff92e93James Kung Log.d(TAG, "child at " + (i - 1) + " has top " + top); 2013e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2023e9818e0267619fecebd55095ab26c53eff92e93James Kung } while (top < 0); 2033e9818e0267619fecebd55095ab26c53eff92e93James Kung 2043e9818e0267619fecebd55095ab26c53eff92e93James Kung // Compute the first and last position visible 2053e9818e0267619fecebd55095ab26c53eff92e93James Kung int selectedPosition; 2063e9818e0267619fecebd55095ab26c53eff92e93James Kung if (child != null) { 2073e9818e0267619fecebd55095ab26c53eff92e93James Kung selectedPosition = getPositionForView(child); 2083e9818e0267619fecebd55095ab26c53eff92e93James Kung } else { 2093e9818e0267619fecebd55095ab26c53eff92e93James Kung selectedPosition = 0; 2103e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2113e9818e0267619fecebd55095ab26c53eff92e93James Kung 2123e9818e0267619fecebd55095ab26c53eff92e93James Kung if (setSelected) { 2133e9818e0267619fecebd55095ab26c53eff92e93James Kung mAdapter.setSelectedDay(mSelectedDay); 2143e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2153e9818e0267619fecebd55095ab26c53eff92e93James Kung 2163e9818e0267619fecebd55095ab26c53eff92e93James Kung if (Log.isLoggable(TAG, Log.DEBUG)) { 2173e9818e0267619fecebd55095ab26c53eff92e93James Kung Log.d(TAG, "GoTo position " + position); 2183e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2193e9818e0267619fecebd55095ab26c53eff92e93James Kung // Check if the selected day is now outside of our visible range 2203e9818e0267619fecebd55095ab26c53eff92e93James Kung // and if so scroll to the month that contains it 2213e9818e0267619fecebd55095ab26c53eff92e93James Kung if (position != selectedPosition || forceScroll) { 2223e9818e0267619fecebd55095ab26c53eff92e93James Kung setMonthDisplayed(mTempDay); 2233e9818e0267619fecebd55095ab26c53eff92e93James Kung mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; 2243e9818e0267619fecebd55095ab26c53eff92e93James Kung if (animate) { 2253e9818e0267619fecebd55095ab26c53eff92e93James Kung smoothScrollToPositionFromTop( 2263e9818e0267619fecebd55095ab26c53eff92e93James Kung position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION); 2273e9818e0267619fecebd55095ab26c53eff92e93James Kung return true; 2283e9818e0267619fecebd55095ab26c53eff92e93James Kung } else { 2292e00aa34c051111529290cf23c6ba940c2c0c142James Kung postSetSelection(position); 2303e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2313e9818e0267619fecebd55095ab26c53eff92e93James Kung } else if (setSelected) { 2323e9818e0267619fecebd55095ab26c53eff92e93James Kung setMonthDisplayed(mSelectedDay); 2333e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2343e9818e0267619fecebd55095ab26c53eff92e93James Kung return false; 2353e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2363e9818e0267619fecebd55095ab26c53eff92e93James Kung 2372e00aa34c051111529290cf23c6ba940c2c0c142James Kung public void postSetSelection(final int position) { 2382e00aa34c051111529290cf23c6ba940c2c0c142James Kung clearFocus(); 2392e00aa34c051111529290cf23c6ba940c2c0c142James Kung post(new Runnable() { 2402e00aa34c051111529290cf23c6ba940c2c0c142James Kung 2412e00aa34c051111529290cf23c6ba940c2c0c142James Kung @Override 2422e00aa34c051111529290cf23c6ba940c2c0c142James Kung public void run() { 2432e00aa34c051111529290cf23c6ba940c2c0c142James Kung setSelection(position); 2442e00aa34c051111529290cf23c6ba940c2c0c142James Kung } 2452e00aa34c051111529290cf23c6ba940c2c0c142James Kung }); 2462e00aa34c051111529290cf23c6ba940c2c0c142James Kung onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE); 2472e00aa34c051111529290cf23c6ba940c2c0c142James Kung } 2482e00aa34c051111529290cf23c6ba940c2c0c142James Kung 2493e9818e0267619fecebd55095ab26c53eff92e93James Kung /** 2503e9818e0267619fecebd55095ab26c53eff92e93James Kung * Updates the title and selected month if the view has moved to a new 2513e9818e0267619fecebd55095ab26c53eff92e93James Kung * month. 2523e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 2533e9818e0267619fecebd55095ab26c53eff92e93James Kung @Override 2543e9818e0267619fecebd55095ab26c53eff92e93James Kung public void onScroll( 2553e9818e0267619fecebd55095ab26c53eff92e93James Kung AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 256e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung MonthView child = (MonthView) view.getChildAt(0); 2573e9818e0267619fecebd55095ab26c53eff92e93James Kung if (child == null) { 2583e9818e0267619fecebd55095ab26c53eff92e93James Kung return; 2593e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2603e9818e0267619fecebd55095ab26c53eff92e93James Kung 2613e9818e0267619fecebd55095ab26c53eff92e93James Kung // Figure out where we are 2623e9818e0267619fecebd55095ab26c53eff92e93James Kung long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); 2633e9818e0267619fecebd55095ab26c53eff92e93James Kung mPreviousScrollPosition = currScroll; 2643e9818e0267619fecebd55095ab26c53eff92e93James Kung mPreviousScrollState = mCurrentScrollState; 2653e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2663e9818e0267619fecebd55095ab26c53eff92e93James Kung 2673e9818e0267619fecebd55095ab26c53eff92e93James Kung /** 2683e9818e0267619fecebd55095ab26c53eff92e93James Kung * Sets the month displayed at the top of this view based on time. Override 2693e9818e0267619fecebd55095ab26c53eff92e93James Kung * to add custom events when the title is changed. 2703e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 2713e9818e0267619fecebd55095ab26c53eff92e93James Kung protected void setMonthDisplayed(CalendarDay date) { 2723e9818e0267619fecebd55095ab26c53eff92e93James Kung mCurrentMonthDisplayed = date.month; 2733e9818e0267619fecebd55095ab26c53eff92e93James Kung invalidateViews(); 2743e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2753e9818e0267619fecebd55095ab26c53eff92e93James Kung 2763e9818e0267619fecebd55095ab26c53eff92e93James Kung @Override 2773e9818e0267619fecebd55095ab26c53eff92e93James Kung public void onScrollStateChanged(AbsListView view, int scrollState) { 2783e9818e0267619fecebd55095ab26c53eff92e93James Kung // use a post to prevent re-entering onScrollStateChanged before it 2793e9818e0267619fecebd55095ab26c53eff92e93James Kung // exits 2803e9818e0267619fecebd55095ab26c53eff92e93James Kung mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); 2813e9818e0267619fecebd55095ab26c53eff92e93James Kung } 2823e9818e0267619fecebd55095ab26c53eff92e93James Kung 2833e9818e0267619fecebd55095ab26c53eff92e93James Kung protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); 2843e9818e0267619fecebd55095ab26c53eff92e93James Kung 2853e9818e0267619fecebd55095ab26c53eff92e93James Kung protected class ScrollStateRunnable implements Runnable { 2863e9818e0267619fecebd55095ab26c53eff92e93James Kung private int mNewState; 2873e9818e0267619fecebd55095ab26c53eff92e93James Kung 2883e9818e0267619fecebd55095ab26c53eff92e93James Kung /** 2893e9818e0267619fecebd55095ab26c53eff92e93James Kung * Sets up the runnable with a short delay in case the scroll state 2903e9818e0267619fecebd55095ab26c53eff92e93James Kung * immediately changes again. 2913e9818e0267619fecebd55095ab26c53eff92e93James Kung * 2923e9818e0267619fecebd55095ab26c53eff92e93James Kung * @param view The list view that changed state 2933e9818e0267619fecebd55095ab26c53eff92e93James Kung * @param scrollState The new state it changed to 2943e9818e0267619fecebd55095ab26c53eff92e93James Kung */ 2953e9818e0267619fecebd55095ab26c53eff92e93James Kung public void doScrollStateChange(AbsListView view, int scrollState) { 2963e9818e0267619fecebd55095ab26c53eff92e93James Kung mHandler.removeCallbacks(this); 2973e9818e0267619fecebd55095ab26c53eff92e93James Kung mNewState = scrollState; 2983e9818e0267619fecebd55095ab26c53eff92e93James Kung mHandler.postDelayed(this, SCROLL_CHANGE_DELAY); 2993e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3003e9818e0267619fecebd55095ab26c53eff92e93James Kung 3013e9818e0267619fecebd55095ab26c53eff92e93James Kung @Override 3023e9818e0267619fecebd55095ab26c53eff92e93James Kung public void run() { 3033e9818e0267619fecebd55095ab26c53eff92e93James Kung mCurrentScrollState = mNewState; 3043e9818e0267619fecebd55095ab26c53eff92e93James Kung if (Log.isLoggable(TAG, Log.DEBUG)) { 3053e9818e0267619fecebd55095ab26c53eff92e93James Kung Log.d(TAG, 3063e9818e0267619fecebd55095ab26c53eff92e93James Kung "new scroll state: " + mNewState + " old state: " + mPreviousScrollState); 3073e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3083e9818e0267619fecebd55095ab26c53eff92e93James Kung // Fix the position after a scroll or a fling ends 3093e9818e0267619fecebd55095ab26c53eff92e93James Kung if (mNewState == OnScrollListener.SCROLL_STATE_IDLE 3103e9818e0267619fecebd55095ab26c53eff92e93James Kung && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE 3113e9818e0267619fecebd55095ab26c53eff92e93James Kung && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 3123e9818e0267619fecebd55095ab26c53eff92e93James Kung mPreviousScrollState = mNewState; 3133e9818e0267619fecebd55095ab26c53eff92e93James Kung int i = 0; 3143e9818e0267619fecebd55095ab26c53eff92e93James Kung View child = getChildAt(i); 3153e9818e0267619fecebd55095ab26c53eff92e93James Kung while (child != null && child.getBottom() <= 0) { 3163e9818e0267619fecebd55095ab26c53eff92e93James Kung child = getChildAt(++i); 3173e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3183e9818e0267619fecebd55095ab26c53eff92e93James Kung if (child == null) { 3193e9818e0267619fecebd55095ab26c53eff92e93James Kung // The view is no longer visible, just return 3203e9818e0267619fecebd55095ab26c53eff92e93James Kung return; 3213e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3223e9818e0267619fecebd55095ab26c53eff92e93James Kung int firstPosition = getFirstVisiblePosition(); 3233e9818e0267619fecebd55095ab26c53eff92e93James Kung int lastPosition = getLastVisiblePosition(); 3243e9818e0267619fecebd55095ab26c53eff92e93James Kung boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1; 3253e9818e0267619fecebd55095ab26c53eff92e93James Kung final int top = child.getTop(); 3263e9818e0267619fecebd55095ab26c53eff92e93James Kung final int bottom = child.getBottom(); 3273e9818e0267619fecebd55095ab26c53eff92e93James Kung final int midpoint = getHeight() / 2; 3283e9818e0267619fecebd55095ab26c53eff92e93James Kung if (scroll && top < LIST_TOP_OFFSET) { 3293e9818e0267619fecebd55095ab26c53eff92e93James Kung if (bottom > midpoint) { 3303e9818e0267619fecebd55095ab26c53eff92e93James Kung smoothScrollBy(top, GOTO_SCROLL_DURATION); 3313e9818e0267619fecebd55095ab26c53eff92e93James Kung } else { 3323e9818e0267619fecebd55095ab26c53eff92e93James Kung smoothScrollBy(bottom, GOTO_SCROLL_DURATION); 3333e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3343e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3353e9818e0267619fecebd55095ab26c53eff92e93James Kung } else { 3363e9818e0267619fecebd55095ab26c53eff92e93James Kung mPreviousScrollState = mNewState; 3373e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3383e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3393e9818e0267619fecebd55095ab26c53eff92e93James Kung } 3402e00aa34c051111529290cf23c6ba940c2c0c142James Kung 3412e00aa34c051111529290cf23c6ba940c2c0c142James Kung /** 3422e00aa34c051111529290cf23c6ba940c2c0c142James Kung * Gets the position of the view that is most prominently displayed within the list view. 3432e00aa34c051111529290cf23c6ba940c2c0c142James Kung */ 3442e00aa34c051111529290cf23c6ba940c2c0c142James Kung public int getMostVisiblePosition() { 3452e00aa34c051111529290cf23c6ba940c2c0c142James Kung final int firstPosition = getFirstVisiblePosition(); 3462e00aa34c051111529290cf23c6ba940c2c0c142James Kung final int height = getHeight(); 3472e00aa34c051111529290cf23c6ba940c2c0c142James Kung 3482e00aa34c051111529290cf23c6ba940c2c0c142James Kung int maxDisplayedHeight = 0; 3492e00aa34c051111529290cf23c6ba940c2c0c142James Kung int mostVisibleIndex = 0; 3502e00aa34c051111529290cf23c6ba940c2c0c142James Kung int i=0; 3512e00aa34c051111529290cf23c6ba940c2c0c142James Kung int bottom = 0; 3522e00aa34c051111529290cf23c6ba940c2c0c142James Kung while (bottom < height) { 3532e00aa34c051111529290cf23c6ba940c2c0c142James Kung View child = getChildAt(i); 3542e00aa34c051111529290cf23c6ba940c2c0c142James Kung if (child == null) { 3552e00aa34c051111529290cf23c6ba940c2c0c142James Kung break; 3562e00aa34c051111529290cf23c6ba940c2c0c142James Kung } 3572e00aa34c051111529290cf23c6ba940c2c0c142James Kung bottom = child.getBottom(); 3582e00aa34c051111529290cf23c6ba940c2c0c142James Kung int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop()); 3592e00aa34c051111529290cf23c6ba940c2c0c142James Kung if (displayedHeight > maxDisplayedHeight) { 3602e00aa34c051111529290cf23c6ba940c2c0c142James Kung mostVisibleIndex = i; 3612e00aa34c051111529290cf23c6ba940c2c0c142James Kung maxDisplayedHeight = displayedHeight; 3622e00aa34c051111529290cf23c6ba940c2c0c142James Kung } 3632e00aa34c051111529290cf23c6ba940c2c0c142James Kung i++; 3642e00aa34c051111529290cf23c6ba940c2c0c142James Kung } 3652e00aa34c051111529290cf23c6ba940c2c0c142James Kung return firstPosition + mostVisibleIndex; 3662e00aa34c051111529290cf23c6ba940c2c0c142James Kung } 3672e00aa34c051111529290cf23c6ba940c2c0c142James Kung 3682e00aa34c051111529290cf23c6ba940c2c0c142James Kung @Override 3692e00aa34c051111529290cf23c6ba940c2c0c142James Kung public void onDateChanged() { 3702e00aa34c051111529290cf23c6ba940c2c0c142James Kung goTo(mController.getSelectedDay(), false, true, true); 3712e00aa34c051111529290cf23c6ba940c2c0c142James Kung } 372090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette 373090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette /** 374090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * Attempts to return the date that has accessibility focus. 375090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * 376090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * @return The date that has accessibility focus, or {@code null} if no date 377090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * has focus. 378090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette */ 379090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette private CalendarDay findAccessibilityFocus() { 380090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette final int childCount = getChildCount(); 381090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette for (int i = 0; i < childCount; i++) { 382090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette final View child = getChildAt(i); 383e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung if (child instanceof MonthView) { 384e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung final CalendarDay focus = ((MonthView) child).getAccessibilityFocus(); 385090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette if (focus != null) { 386a35f6b580aefd7bf1c7e92306e13dacd44316714Alan Viverette if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { 387a35f6b580aefd7bf1c7e92306e13dacd44316714Alan Viverette // Clear focus to avoid ListView bug in Jelly Bean MR1. 388e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung ((MonthView) child).clearAccessibilityFocus(); 389a35f6b580aefd7bf1c7e92306e13dacd44316714Alan Viverette } 390090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette return focus; 391090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 392090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 393090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 394090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette 395090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette return null; 396090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 397090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette 398090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette /** 399090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * Attempts to restore accessibility focus to a given date. No-op if 400090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * {@code day} is {@code null}. 401090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * 402090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * @param day The date that should receive accessibility focus 403090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette * @return {@code true} if focus was restored 404090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette */ 405090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette private boolean restoreAccessibilityFocus(CalendarDay day) { 406090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette if (day == null) { 407090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette return false; 408090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 409090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette 410090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette final int childCount = getChildCount(); 411090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette for (int i = 0; i < childCount; i++) { 412090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette final View child = getChildAt(i); 413e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung if (child instanceof MonthView) { 414e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung if (((MonthView) child).restoreAccessibilityFocus(day)) { 415090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette return true; 416090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 417090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 418090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 419090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette 420090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette return false; 421090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 422090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette 423090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette @Override 424090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette protected void layoutChildren() { 425090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette final CalendarDay focusedDay = findAccessibilityFocus(); 426090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette super.layoutChildren(); 427cb3f2522609186db6239ad154af275957118295cSam Blitzstein if (mPerformingScroll) { 428cb3f2522609186db6239ad154af275957118295cSam Blitzstein mPerformingScroll = false; 429cb3f2522609186db6239ad154af275957118295cSam Blitzstein } else { 430cb3f2522609186db6239ad154af275957118295cSam Blitzstein restoreAccessibilityFocus(focusedDay); 431cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 432cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 433cb3f2522609186db6239ad154af275957118295cSam Blitzstein 434cb3f2522609186db6239ad154af275957118295cSam Blitzstein @Override 435cb3f2522609186db6239ad154af275957118295cSam Blitzstein public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 436cb3f2522609186db6239ad154af275957118295cSam Blitzstein super.onInitializeAccessibilityEvent(event); 437cb3f2522609186db6239ad154af275957118295cSam Blitzstein event.setItemCount(-1); 438cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 439cb3f2522609186db6239ad154af275957118295cSam Blitzstein 440aa0dd58622335c458af9afc0e0656e4664293087Scott Kennedy private static String getMonthAndYearString(CalendarDay day) { 441cb3f2522609186db6239ad154af275957118295cSam Blitzstein Calendar cal = Calendar.getInstance(); 442cb3f2522609186db6239ad154af275957118295cSam Blitzstein cal.set(day.year, day.month, day.day); 443cb3f2522609186db6239ad154af275957118295cSam Blitzstein 444cb3f2522609186db6239ad154af275957118295cSam Blitzstein StringBuffer sbuf = new StringBuffer(); 445cb3f2522609186db6239ad154af275957118295cSam Blitzstein sbuf.append(cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())); 446cb3f2522609186db6239ad154af275957118295cSam Blitzstein sbuf.append(" "); 447cb3f2522609186db6239ad154af275957118295cSam Blitzstein sbuf.append(YEAR_FORMAT.format(cal.getTime())); 448cb3f2522609186db6239ad154af275957118295cSam Blitzstein return sbuf.toString(); 449cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 450cb3f2522609186db6239ad154af275957118295cSam Blitzstein 451cb3f2522609186db6239ad154af275957118295cSam Blitzstein /** 452cb3f2522609186db6239ad154af275957118295cSam Blitzstein * Necessary for accessibility, to ensure we support "scrolling" forward and backward 453cb3f2522609186db6239ad154af275957118295cSam Blitzstein * in the month list. 454cb3f2522609186db6239ad154af275957118295cSam Blitzstein */ 455cb3f2522609186db6239ad154af275957118295cSam Blitzstein @Override 456cb3f2522609186db6239ad154af275957118295cSam Blitzstein public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 457cb3f2522609186db6239ad154af275957118295cSam Blitzstein super.onInitializeAccessibilityNodeInfo(info); 458cb3f2522609186db6239ad154af275957118295cSam Blitzstein info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 459cb3f2522609186db6239ad154af275957118295cSam Blitzstein info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 460cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 461cb3f2522609186db6239ad154af275957118295cSam Blitzstein 462cb3f2522609186db6239ad154af275957118295cSam Blitzstein /** 463cb3f2522609186db6239ad154af275957118295cSam Blitzstein * When scroll forward/backward events are received, announce the newly scrolled-to month. 464cb3f2522609186db6239ad154af275957118295cSam Blitzstein */ 465cb3f2522609186db6239ad154af275957118295cSam Blitzstein @SuppressLint("NewApi") 466cb3f2522609186db6239ad154af275957118295cSam Blitzstein @Override 467cb3f2522609186db6239ad154af275957118295cSam Blitzstein public boolean performAccessibilityAction(int action, Bundle arguments) { 468cb3f2522609186db6239ad154af275957118295cSam Blitzstein if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && 469cb3f2522609186db6239ad154af275957118295cSam Blitzstein action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { 470cb3f2522609186db6239ad154af275957118295cSam Blitzstein return super.performAccessibilityAction(action, arguments); 471cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 472cb3f2522609186db6239ad154af275957118295cSam Blitzstein 473cb3f2522609186db6239ad154af275957118295cSam Blitzstein // Figure out what month is showing. 474cb3f2522609186db6239ad154af275957118295cSam Blitzstein int firstVisiblePosition = getFirstVisiblePosition(); 475cb3f2522609186db6239ad154af275957118295cSam Blitzstein int month = firstVisiblePosition % 12; 476cb3f2522609186db6239ad154af275957118295cSam Blitzstein int year = firstVisiblePosition / 12 + mController.getMinYear(); 477cb3f2522609186db6239ad154af275957118295cSam Blitzstein CalendarDay day = new CalendarDay(year, month, 1); 478cb3f2522609186db6239ad154af275957118295cSam Blitzstein 479cb3f2522609186db6239ad154af275957118295cSam Blitzstein // Scroll either forward or backward one month. 480cb3f2522609186db6239ad154af275957118295cSam Blitzstein if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) { 481cb3f2522609186db6239ad154af275957118295cSam Blitzstein day.month++; 482cb3f2522609186db6239ad154af275957118295cSam Blitzstein if (day.month == 12) { 483cb3f2522609186db6239ad154af275957118295cSam Blitzstein day.month = 0; 484cb3f2522609186db6239ad154af275957118295cSam Blitzstein day.year++; 485cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 486cb3f2522609186db6239ad154af275957118295cSam Blitzstein } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { 487cb3f2522609186db6239ad154af275957118295cSam Blitzstein View firstVisibleView = getChildAt(0); 488cb3f2522609186db6239ad154af275957118295cSam Blitzstein // If the view is fully visible, jump one month back. Otherwise, we'll just jump 489cb3f2522609186db6239ad154af275957118295cSam Blitzstein // to the first day of first visible month. 490cb3f2522609186db6239ad154af275957118295cSam Blitzstein if (firstVisibleView != null && firstVisibleView.getTop() >= -1) { 491cb3f2522609186db6239ad154af275957118295cSam Blitzstein // There's an off-by-one somewhere, so the top of the first visible item will 492cb3f2522609186db6239ad154af275957118295cSam Blitzstein // actually be -1 when it's at the exact top. 493cb3f2522609186db6239ad154af275957118295cSam Blitzstein day.month--; 494cb3f2522609186db6239ad154af275957118295cSam Blitzstein if (day.month == -1) { 495cb3f2522609186db6239ad154af275957118295cSam Blitzstein day.month = 11; 496cb3f2522609186db6239ad154af275957118295cSam Blitzstein day.year--; 497cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 498cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 499cb3f2522609186db6239ad154af275957118295cSam Blitzstein } 500cb3f2522609186db6239ad154af275957118295cSam Blitzstein 501cb3f2522609186db6239ad154af275957118295cSam Blitzstein // Go to that month. 502cb3f2522609186db6239ad154af275957118295cSam Blitzstein Utils.tryAccessibilityAnnounce(this, getMonthAndYearString(day)); 503cb3f2522609186db6239ad154af275957118295cSam Blitzstein goTo(day, true, false, true); 504cb3f2522609186db6239ad154af275957118295cSam Blitzstein mPerformingScroll = true; 505cb3f2522609186db6239ad154af275957118295cSam Blitzstein return true; 506090a46d6ee500d3674401fd3de48cd0f12ce7186Alan Viverette } 5073e9818e0267619fecebd55095ab26c53eff92e93James Kung} 508