Home | History | Annotate | Download | only in month
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.calendar.month;
     18 
     19 import com.android.calendar.R;
     20 import com.android.calendar.Utils;
     21 
     22 import android.app.Activity;
     23 import android.app.ListFragment;
     24 import android.content.Context;
     25 import android.content.res.Resources;
     26 import android.database.DataSetObserver;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.text.TextUtils;
     30 import android.text.format.DateUtils;
     31 import android.text.format.Time;
     32 import android.util.Log;
     33 import android.view.LayoutInflater;
     34 import android.view.View;
     35 import android.view.ViewConfiguration;
     36 import android.view.ViewGroup;
     37 import android.view.accessibility.AccessibilityEvent;
     38 import android.widget.AbsListView;
     39 import android.widget.AbsListView.OnScrollListener;
     40 import android.widget.ListView;
     41 import android.widget.TextView;
     42 
     43 import java.util.Calendar;
     44 import java.util.HashMap;
     45 import java.util.Locale;
     46 
     47 /**
     48  * <p>
     49  * This displays a titled list of weeks with selectable days. It can be
     50  * configured to display the week number, start the week on a given day, show a
     51  * reduced number of days, or display an arbitrary number of weeks at a time. By
     52  * overriding methods and changing variables this fragment can be customized to
     53  * easily display a month selection component in a given style.
     54  * </p>
     55  */
     56 public class SimpleDayPickerFragment extends ListFragment implements OnScrollListener {
     57 
     58     private static final String TAG = "MonthFragment";
     59     private static final String KEY_CURRENT_TIME = "current_time";
     60 
     61     // Affects when the month selection will change while scrolling up
     62     protected static final int SCROLL_HYST_WEEKS = 2;
     63     // How long the GoTo fling animation should last
     64     protected static final int GOTO_SCROLL_DURATION = 500;
     65     // How long to wait after receiving an onScrollStateChanged notification
     66     // before acting on it
     67     protected static final int SCROLL_CHANGE_DELAY = 40;
     68     // The number of days to display in each week
     69     public static final int DAYS_PER_WEEK = 7;
     70     // The size of the month name displayed above the week list
     71     protected static final int MINI_MONTH_NAME_TEXT_SIZE = 18;
     72     public static int LIST_TOP_OFFSET = -1;  // so that the top line will be under the separator
     73     protected int WEEK_MIN_VISIBLE_HEIGHT = 12;
     74     protected int BOTTOM_BUFFER = 20;
     75     protected int mSaturdayColor = 0;
     76     protected int mSundayColor = 0;
     77     protected int mDayNameColor = 0;
     78 
     79     // You can override these numbers to get a different appearance
     80     protected int mNumWeeks = 6;
     81     protected boolean mShowWeekNumber = false;
     82     protected int mDaysPerWeek = 7;
     83 
     84     // These affect the scroll speed and feel
     85     protected float mFriction = 1.0f;
     86 
     87     protected Context mContext;
     88     protected Handler mHandler;
     89 
     90     protected float mMinimumFlingVelocity;
     91 
     92     // highlighted time
     93     protected Time mSelectedDay = new Time();
     94     protected SimpleWeeksAdapter mAdapter;
     95     protected ListView mListView;
     96     protected ViewGroup mDayNamesHeader;
     97     protected String[] mDayLabels;
     98 
     99     // disposable variable used for time calculations
    100     protected Time mTempTime = new Time();
    101 
    102     private static float mScale = 0;
    103     // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
    104     protected int mFirstDayOfWeek;
    105     // The first day of the focus month
    106     protected Time mFirstDayOfMonth = new Time();
    107     // The first day that is visible in the view
    108     protected Time mFirstVisibleDay = new Time();
    109     // The name of the month to display
    110     protected TextView mMonthName;
    111     // The last name announced by accessibility
    112     protected CharSequence mPrevMonthName;
    113     // which month should be displayed/highlighted [0-11]
    114     protected int mCurrentMonthDisplayed;
    115     // used for tracking during a scroll
    116     protected long mPreviousScrollPosition;
    117     // used for tracking which direction the view is scrolling
    118     protected boolean mIsScrollingUp = false;
    119     // used for tracking what state listview is in
    120     protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
    121     // used for tracking what state listview is in
    122     protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
    123 
    124     // This causes an update of the view at midnight
    125     protected Runnable mTodayUpdater = new Runnable() {
    126         @Override
    127         public void run() {
    128             Time midnight = new Time(mFirstVisibleDay.timezone);
    129             midnight.setToNow();
    130             long currentMillis = midnight.toMillis(true);
    131 
    132             midnight.hour = 0;
    133             midnight.minute = 0;
    134             midnight.second = 0;
    135             midnight.monthDay++;
    136             long millisToMidnight = midnight.normalize(true) - currentMillis;
    137             mHandler.postDelayed(this, millisToMidnight);
    138 
    139             if (mAdapter != null) {
    140                 mAdapter.notifyDataSetChanged();
    141             }
    142         }
    143     };
    144 
    145     // This allows us to update our position when a day is tapped
    146     protected DataSetObserver mObserver = new DataSetObserver() {
    147         @Override
    148         public void onChanged() {
    149             Time day = mAdapter.getSelectedDay();
    150             if (day.year != mSelectedDay.year || day.yearDay != mSelectedDay.yearDay) {
    151                 goTo(day.toMillis(true), true, true, false);
    152             }
    153         }
    154     };
    155 
    156     public SimpleDayPickerFragment(long initialTime) {
    157         goTo(initialTime, false, true, true);
    158         mHandler = new Handler();
    159     }
    160 
    161     @Override
    162     public void onAttach(Activity activity) {
    163         super.onAttach(activity);
    164         mContext = activity;
    165         String tz = Time.getCurrentTimezone();
    166         ViewConfiguration viewConfig = ViewConfiguration.get(activity);
    167         mMinimumFlingVelocity = viewConfig.getScaledMinimumFlingVelocity();
    168 
    169         // Ensure we're in the correct time zone
    170         mSelectedDay.switchTimezone(tz);
    171         mSelectedDay.normalize(true);
    172         mFirstDayOfMonth.timezone = tz;
    173         mFirstDayOfMonth.normalize(true);
    174         mFirstVisibleDay.timezone = tz;
    175         mFirstVisibleDay.normalize(true);
    176         mTempTime.timezone = tz;
    177 
    178         Resources res = activity.getResources();
    179         mSaturdayColor = res.getColor(R.color.month_saturday);
    180         mSundayColor = res.getColor(R.color.month_sunday);
    181         mDayNameColor = res.getColor(R.color.month_day_names_color);
    182 
    183         // Adjust sizes for screen density
    184         if (mScale == 0) {
    185             mScale = activity.getResources().getDisplayMetrics().density;
    186             if (mScale != 1) {
    187                 WEEK_MIN_VISIBLE_HEIGHT *= mScale;
    188                 BOTTOM_BUFFER *= mScale;
    189                 LIST_TOP_OFFSET *= mScale;
    190             }
    191         }
    192         setUpAdapter();
    193         setListAdapter(mAdapter);
    194     }
    195 
    196     /**
    197      * Creates a new adapter if necessary and sets up its parameters. Override
    198      * this method to provide a custom adapter.
    199      */
    200     protected void setUpAdapter() {
    201         HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
    202         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
    203         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
    204         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
    205         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
    206                 Time.getJulianDay(mSelectedDay.toMillis(false), mSelectedDay.gmtoff));
    207         if (mAdapter == null) {
    208             mAdapter = new SimpleWeeksAdapter(getActivity(), weekParams);
    209             mAdapter.registerDataSetObserver(mObserver);
    210         } else {
    211             mAdapter.updateParams(weekParams);
    212         }
    213         // refresh the view with the new parameters
    214         mAdapter.notifyDataSetChanged();
    215     }
    216 
    217     @Override
    218     public void onCreate(Bundle savedInstanceState) {
    219         super.onCreate(savedInstanceState);
    220         if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TIME)) {
    221             goTo(savedInstanceState.getLong(KEY_CURRENT_TIME), false, true, true);
    222         }
    223     }
    224 
    225     @Override
    226     public void onActivityCreated(Bundle savedInstanceState) {
    227         super.onActivityCreated(savedInstanceState);
    228 
    229         setUpListView();
    230         setUpHeader();
    231 
    232         mMonthName = (TextView) getView().findViewById(R.id.month_name);
    233         SimpleWeekView child = (SimpleWeekView) mListView.getChildAt(0);
    234         if (child == null) {
    235             return;
    236         }
    237         int julianDay = child.getFirstJulianDay();
    238         mFirstVisibleDay.setJulianDay(julianDay);
    239         // set the title to the month of the second week
    240         mTempTime.setJulianDay(julianDay + DAYS_PER_WEEK);
    241         setMonthDisplayed(mTempTime, true);
    242     }
    243 
    244     /**
    245      * Sets up the strings to be used by the header. Override this method to use
    246      * different strings or modify the view params.
    247      */
    248     protected void setUpHeader() {
    249         mDayLabels = new String[7];
    250         for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
    251             mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
    252                     DateUtils.LENGTH_SHORTEST).toUpperCase();
    253         }
    254     }
    255 
    256     /**
    257      * Sets all the required fields for the list view. Override this method to
    258      * set a different list view behavior.
    259      */
    260     protected void setUpListView() {
    261         // Configure the listview
    262         mListView = getListView();
    263         // Transparent background on scroll
    264         mListView.setCacheColorHint(0);
    265         // No dividers
    266         mListView.setDivider(null);
    267         // Items are clickable
    268         mListView.setItemsCanFocus(true);
    269         // The thumb gets in the way, so disable it
    270         mListView.setFastScrollEnabled(false);
    271         mListView.setVerticalScrollBarEnabled(false);
    272         mListView.setOnScrollListener(this);
    273         mListView.setFadingEdgeLength(0);
    274         // Make the scrolling behavior nicer
    275         mListView.setFriction(ViewConfiguration.getScrollFriction() * mFriction);
    276     }
    277 
    278     @Override
    279     public void onResume() {
    280         super.onResume();
    281         setUpAdapter();
    282         doResumeUpdates();
    283     }
    284 
    285     @Override
    286     public void onPause() {
    287         super.onPause();
    288         mHandler.removeCallbacks(mTodayUpdater);
    289     }
    290 
    291     @Override
    292     public void onSaveInstanceState(Bundle outState) {
    293         outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true));
    294     }
    295 
    296     /**
    297      * Updates the user preference fields. Override this to use a different
    298      * preference space.
    299      */
    300     protected void doResumeUpdates() {
    301         // Get default week start based on locale, subtracting one for use with android Time.
    302         Calendar cal = Calendar.getInstance(Locale.getDefault());
    303         mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
    304 
    305         mShowWeekNumber = false;
    306 
    307         updateHeader();
    308         goTo(mSelectedDay.toMillis(true), false, false, false);
    309         mAdapter.setSelectedDay(mSelectedDay);
    310         mTodayUpdater.run();
    311     }
    312 
    313     /**
    314      * Fixes the day names header to provide correct spacing and updates the
    315      * label text. Override this to set up a custom header.
    316      */
    317     protected void updateHeader() {
    318         TextView label = (TextView) mDayNamesHeader.findViewById(R.id.wk_label);
    319         if (mShowWeekNumber) {
    320             label.setVisibility(View.VISIBLE);
    321         } else {
    322             label.setVisibility(View.GONE);
    323         }
    324         int offset = mFirstDayOfWeek - 1;
    325         for (int i = 1; i < 8; i++) {
    326             label = (TextView) mDayNamesHeader.getChildAt(i);
    327             if (i < mDaysPerWeek + 1) {
    328                 int position = (offset + i) % 7;
    329                 label.setText(mDayLabels[position]);
    330                 label.setVisibility(View.VISIBLE);
    331                 if (position == Time.SATURDAY) {
    332                     label.setTextColor(mSaturdayColor);
    333                 } else if (position == Time.SUNDAY) {
    334                     label.setTextColor(mSundayColor);
    335                 } else {
    336                     label.setTextColor(mDayNameColor);
    337                 }
    338             } else {
    339                 label.setVisibility(View.GONE);
    340             }
    341         }
    342         mDayNamesHeader.invalidate();
    343     }
    344 
    345     @Override
    346     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    347         View v = inflater.inflate(R.layout.month_by_week,
    348                 container, false);
    349         mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
    350         return v;
    351     }
    352 
    353     /**
    354      * Returns the UTC millis since epoch representation of the currently
    355      * selected time.
    356      *
    357      * @return
    358      */
    359     public long getSelectedTime() {
    360         return mSelectedDay.toMillis(true);
    361     }
    362 
    363     /**
    364      * This moves to the specified time in the view. If the time is not already
    365      * in range it will move the list so that the first of the month containing
    366      * the time is at the top of the view. If the new time is already in view
    367      * the list will not be scrolled unless forceScroll is true. This time may
    368      * optionally be highlighted as selected as well.
    369      *
    370      * @param time The time to move to
    371      * @param animate Whether to scroll to the given time or just redraw at the
    372      *            new location
    373      * @param setSelected Whether to set the given time as selected
    374      * @param forceScroll Whether to recenter even if the time is already
    375      *            visible
    376      * @return Whether or not the view animated to the new location
    377      */
    378     public boolean goTo(long time, boolean animate, boolean setSelected, boolean forceScroll) {
    379         if (time == -1) {
    380             Log.e(TAG, "time is invalid");
    381             return false;
    382         }
    383 
    384         // Set the selected day
    385         if (setSelected) {
    386             mSelectedDay.set(time);
    387             mSelectedDay.normalize(true);
    388         }
    389 
    390         // If this view isn't returned yet we won't be able to load the lists
    391         // current position, so return after setting the selected day.
    392         if (!isResumed()) {
    393             if (Log.isLoggable(TAG, Log.DEBUG)) {
    394                 Log.d(TAG, "We're not visible yet");
    395             }
    396             return false;
    397         }
    398 
    399         mTempTime.set(time);
    400         long millis = mTempTime.normalize(true);
    401         // Get the week we're going to
    402         // TODO push Util function into Calendar public api.
    403         int position = Utils.getWeeksSinceEpochFromJulianDay(
    404                 Time.getJulianDay(millis, mTempTime.gmtoff), mFirstDayOfWeek);
    405 
    406         View child;
    407         int i = 0;
    408         int top = 0;
    409         // Find a child that's completely in the view
    410         do {
    411             child = mListView.getChildAt(i++);
    412             if (child == null) {
    413                 break;
    414             }
    415             top = child.getTop();
    416             if (Log.isLoggable(TAG, Log.DEBUG)) {
    417                 Log.d(TAG, "child at " + (i-1) + " has top " + top);
    418             }
    419         } while (top < 0);
    420 
    421         // Compute the first and last position visible
    422         int firstPosition;
    423         if (child != null) {
    424             firstPosition = mListView.getPositionForView(child);
    425         } else {
    426             firstPosition = 0;
    427         }
    428         int lastPosition = firstPosition + mNumWeeks - 1;
    429         if (top > BOTTOM_BUFFER) {
    430             lastPosition--;
    431         }
    432 
    433         if (setSelected) {
    434             mAdapter.setSelectedDay(mSelectedDay);
    435         }
    436 
    437         if (Log.isLoggable(TAG, Log.DEBUG)) {
    438             Log.d(TAG, "GoTo position " + position);
    439         }
    440         // Check if the selected day is now outside of our visible range
    441         // and if so scroll to the month that contains it
    442         if (position < firstPosition || position > lastPosition || forceScroll) {
    443             mFirstDayOfMonth.set(mTempTime);
    444             mFirstDayOfMonth.monthDay = 1;
    445             millis = mFirstDayOfMonth.normalize(true);
    446             setMonthDisplayed(mFirstDayOfMonth, true);
    447             position = Utils.getWeeksSinceEpochFromJulianDay(
    448                     Time.getJulianDay(millis, mFirstDayOfMonth.gmtoff), mFirstDayOfWeek);
    449 
    450             mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
    451             if (animate) {
    452                 mListView.smoothScrollToPositionFromTop(
    453                         position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
    454                 return true;
    455             } else {
    456                 mListView.setSelectionFromTop(position, LIST_TOP_OFFSET);
    457                 // Perform any after scroll operations that are needed
    458                 onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
    459             }
    460         } else if (setSelected) {
    461             // Otherwise just set the selection
    462             setMonthDisplayed(mSelectedDay, true);
    463         }
    464         return false;
    465     }
    466 
    467      /**
    468      * Updates the title and selected month if the view has moved to a new
    469      * month.
    470      */
    471     @Override
    472     public void onScroll(
    473             AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    474         SimpleWeekView child = (SimpleWeekView)view.getChildAt(0);
    475         if (child == null) {
    476             return;
    477         }
    478 
    479         // Figure out where we are
    480         long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
    481         mFirstVisibleDay.setJulianDay(child.getFirstJulianDay());
    482 
    483         // If we have moved since our last call update the direction
    484         if (currScroll < mPreviousScrollPosition) {
    485             mIsScrollingUp = true;
    486         } else if (currScroll > mPreviousScrollPosition) {
    487             mIsScrollingUp = false;
    488         } else {
    489             return;
    490         }
    491 
    492         mPreviousScrollPosition = currScroll;
    493         mPreviousScrollState = mCurrentScrollState;
    494 
    495         updateMonthHighlight(mListView);
    496     }
    497 
    498     /**
    499      * Figures out if the month being shown has changed and updates the
    500      * highlight if needed
    501      *
    502      * @param view The ListView containing the weeks
    503      */
    504     private void updateMonthHighlight(AbsListView view) {
    505         SimpleWeekView child = (SimpleWeekView) view.getChildAt(0);
    506         if (child == null) {
    507             return;
    508         }
    509 
    510         // Figure out where we are
    511         int offset = child.getBottom() < WEEK_MIN_VISIBLE_HEIGHT ? 1 : 0;
    512         // Use some hysteresis for checking which month to highlight. This
    513         // causes the month to transition when two full weeks of a month are
    514         // visible.
    515         child = (SimpleWeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
    516 
    517         if (child == null) {
    518             return;
    519         }
    520 
    521         // Find out which month we're moving into
    522         int month;
    523         if (mIsScrollingUp) {
    524             month = child.getFirstMonth();
    525         } else {
    526             month = child.getLastMonth();
    527         }
    528 
    529         // And how it relates to our current highlighted month
    530         int monthDiff;
    531         if (mCurrentMonthDisplayed == 11 && month == 0) {
    532             monthDiff = 1;
    533         } else if (mCurrentMonthDisplayed == 0 && month == 11) {
    534             monthDiff = -1;
    535         } else {
    536             monthDiff = month - mCurrentMonthDisplayed;
    537         }
    538 
    539         // Only switch months if we're scrolling away from the currently
    540         // selected month
    541         if (monthDiff != 0) {
    542             int julianDay = child.getFirstJulianDay();
    543             if (mIsScrollingUp) {
    544                 // Takes the start of the week
    545             } else {
    546                 // Takes the start of the following week
    547                 julianDay += DAYS_PER_WEEK;
    548             }
    549             mTempTime.setJulianDay(julianDay);
    550             setMonthDisplayed(mTempTime, false);
    551         }
    552     }
    553 
    554     /**
    555      * Sets the month displayed at the top of this view based on time. Override
    556      * to add custom events when the title is changed.
    557      *
    558      * @param time A day in the new focus month.
    559      * @param updateHighlight TODO(epastern):
    560      */
    561     protected void setMonthDisplayed(Time time, boolean updateHighlight) {
    562         CharSequence oldMonth = mMonthName.getText();
    563         mMonthName.setText(Utils.formatMonthYear(mContext, time));
    564         mMonthName.invalidate();
    565         if (!TextUtils.equals(oldMonth, mMonthName.getText())) {
    566             mMonthName.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
    567         }
    568         mCurrentMonthDisplayed = time.month;
    569         if (updateHighlight) {
    570             mAdapter.updateFocusMonth(mCurrentMonthDisplayed);
    571         }
    572     }
    573 
    574     @Override
    575     public void onScrollStateChanged(AbsListView view, int scrollState) {
    576         // use a post to prevent re-entering onScrollStateChanged before it
    577         // exits
    578         mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
    579     }
    580 
    581     protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
    582 
    583     protected class ScrollStateRunnable implements Runnable {
    584         private int mNewState;
    585 
    586         /**
    587          * Sets up the runnable with a short delay in case the scroll state
    588          * immediately changes again.
    589          *
    590          * @param view The list view that changed state
    591          * @param scrollState The new state it changed to
    592          */
    593         public void doScrollStateChange(AbsListView view, int scrollState) {
    594             mHandler.removeCallbacks(this);
    595             mNewState = scrollState;
    596             mHandler.postDelayed(this, SCROLL_CHANGE_DELAY);
    597         }
    598 
    599         public void run() {
    600             mCurrentScrollState = mNewState;
    601             if (Log.isLoggable(TAG, Log.DEBUG)) {
    602                 Log.d(TAG,
    603                         "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
    604             }
    605             // Fix the position after a scroll or a fling ends
    606             if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
    607                     && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
    608                 mPreviousScrollState = mNewState;
    609                 // Uncomment the below to add snap to week back
    610 //                int i = 0;
    611 //                View child = mView.getChildAt(i);
    612 //                while (child != null && child.getBottom() <= 0) {
    613 //                    child = mView.getChildAt(++i);
    614 //                }
    615 //                if (child == null) {
    616 //                    // The view is no longer visible, just return
    617 //                    return;
    618 //                }
    619 //                int dist = child.getTop();
    620 //                if (dist < LIST_TOP_OFFSET) {
    621 //                    if (Log.isLoggable(TAG, Log.DEBUG)) {
    622 //                        Log.d(TAG, "scrolling by " + dist + " up? " + mIsScrollingUp);
    623 //                    }
    624 //                    int firstPosition = mView.getFirstVisiblePosition();
    625 //                    int lastPosition = mView.getLastVisiblePosition();
    626 //                    boolean scroll = firstPosition != 0 && lastPosition != mView.getCount() - 1;
    627 //                    if (mIsScrollingUp && scroll) {
    628 //                        mView.smoothScrollBy(dist, 500);
    629 //                    } else if (!mIsScrollingUp && scroll) {
    630 //                        mView.smoothScrollBy(child.getHeight() + dist, 500);
    631 //                    }
    632 //                }
    633                 mAdapter.updateFocusMonth(mCurrentMonthDisplayed);
    634             } else {
    635                 mPreviousScrollState = mNewState;
    636             }
    637         }
    638     }
    639 }
    640