package com.fourmob.datetimepicker.date; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.os.Handler; import android.view.View; import android.view.ViewConfiguration; import android.widget.AbsListView; import android.widget.ListView; public class DayPickerView extends ListView implements AbsListView.OnScrollListener, DatePickerDialog.OnDateChangedListener { protected static final int GOTO_SCROLL_DURATION = 250; protected static final int SCROLL_CHANGE_DELAY = 40; public static int LIST_TOP_OFFSET = -1; protected Context mContext; protected Handler mHandler = new Handler(); protected SimpleMonthAdapter mAdapter; private final DatePickerController mController; protected int mCurrentMonthDisplayed; protected int mCurrentScrollState = 0; private boolean mPerformingScroll; protected long mPreviousScrollPosition; protected int mPreviousScrollState = 0; protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); protected SimpleMonthAdapter.CalendarDay mSelectedDay = new SimpleMonthAdapter.CalendarDay(); protected SimpleMonthAdapter.CalendarDay mTempDay = new SimpleMonthAdapter.CalendarDay(); protected int mNumWeeks = 6; protected boolean mShowWeekNumber = false; protected int mDaysPerWeek = 7; protected float mFriction = 1.0F; public DayPickerView(Context context, DatePickerController datePickerController) { super(context); mController = datePickerController; mController.registerOnDateChangedListener(this); setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); setDrawSelectorOnTop(false); init(context); onDateChanged(); } public int getMostVisiblePosition() { final int firstPosition = getFirstVisiblePosition(); final int height = getHeight(); int maxDisplayedHeight = 0; int mostVisibleIndex = 0; int i=0; int bottom = 0; while (bottom < height) { View child = getChildAt(i); if (child == null) { break; } bottom = child.getBottom(); int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop()); if (displayedHeight > maxDisplayedHeight) { mostVisibleIndex = i; maxDisplayedHeight = displayedHeight; } i++; } return firstPosition + mostVisibleIndex; } public boolean goTo(SimpleMonthAdapter.CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) { // Set the selected day if (setSelected) { mSelectedDay.set(day); } mTempDay.set(day); final int position = (day.year - mController.getMinYear()) * SimpleMonthAdapter.MONTHS_IN_YEAR + day.month; View child; int i = 0; int top = 0; // Find a child that's completely in the view do { child = getChildAt(i++); if (child == null) { break; } top = child.getTop(); } while (top < 0); // Compute the first and last position visible int selectedPosition; if (child != null) { selectedPosition = getPositionForView(child); } else { selectedPosition = 0; } if (setSelected) { mAdapter.setSelectedDay(mSelectedDay); } // Check if the selected day is now outside of our visible range // and if so scroll to the month that contains it if (position != selectedPosition || forceScroll) { setMonthDisplayed(mTempDay); mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; if (animate && Build.VERSION.SDK_INT >= 11) { smoothScrollToPositionFromTop(position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION); return true; } else { postSetSelection(position); } } else if (setSelected) { setMonthDisplayed(mSelectedDay); } return false; } public void init(Context paramContext) { mContext = paramContext; setUpListView(); setUpAdapter(); setAdapter(mAdapter); } protected void layoutChildren() { super.layoutChildren(); if (mPerformingScroll) { mPerformingScroll = false; } } public void onChange() { setUpAdapter(); setAdapter(mAdapter); } public void onDateChanged() { goTo(mController.getSelectedDay(), false, true, true); } public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { SimpleMonthView child = (SimpleMonthView) view.getChildAt(0); if (child == null) { return; } // Figure out where we are long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); mPreviousScrollPosition = currScroll; mPreviousScrollState = mCurrentScrollState; } public void onScrollStateChanged(AbsListView absListView, int scroll) { mScrollStateChangedRunnable.doScrollStateChange(absListView, scroll); } public void postSetSelection(final int position) { clearFocus(); post(new Runnable() { public void run() { DayPickerView.this.setSelection(position); } }); onScrollStateChanged(this, 0); } protected void setMonthDisplayed(SimpleMonthAdapter.CalendarDay calendarDay) { this.mCurrentMonthDisplayed = calendarDay.month; invalidateViews(); } protected void setUpAdapter() { if (mAdapter == null) { mAdapter = new SimpleMonthAdapter(getContext(), mController); } mAdapter.setSelectedDay(this.mSelectedDay); mAdapter.notifyDataSetChanged(); } protected void setUpListView() { setCacheColorHint(0); setDivider(null); setItemsCanFocus(true); setFastScrollEnabled(false); setVerticalScrollBarEnabled(false); setOnScrollListener(this); setFadingEdgeLength(0); setFrictionIfSupported(ViewConfiguration.getScrollFriction() * mFriction); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) void setFrictionIfSupported(float friction) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { setFriction(friction); } } protected class ScrollStateRunnable implements Runnable { private int mNewState; /** * Sets up the runnable with a short delay in case the scroll state * immediately changes again. * * @param view The list view that changed state * @param scrollState The new state it changed to */ public void doScrollStateChange(AbsListView view, int scrollState) { mHandler.removeCallbacks(this); mNewState = scrollState; mHandler.postDelayed(this, SCROLL_CHANGE_DELAY); } @Override public void run() { mCurrentScrollState = mNewState; // Fix the position after a scroll or a fling ends if (mNewState == OnScrollListener.SCROLL_STATE_IDLE && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { mPreviousScrollState = mNewState; int i = 0; View child = getChildAt(i); while (child != null && child.getBottom() <= 0) { child = getChildAt(++i); } if (child == null) { // The view is no longer visible, just return return; } int firstPosition = getFirstVisiblePosition(); int lastPosition = getLastVisiblePosition(); boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1; final int top = child.getTop(); final int bottom = child.getBottom(); final int midpoint = getHeight() / 2; if (scroll && top < LIST_TOP_OFFSET) { if (bottom > midpoint) { smoothScrollBy(top, GOTO_SCROLL_DURATION); } else { smoothScrollBy(bottom, GOTO_SCROLL_DURATION); } } } else { mPreviousScrollState = mNewState; } } } }