summaryrefslogtreecommitdiff
path: root/src/com/android/datetimepicker/date/DayPickerView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/datetimepicker/date/DayPickerView.java')
-rw-r--r--src/com/android/datetimepicker/date/DayPickerView.java394
1 files changed, 394 insertions, 0 deletions
diff --git a/src/com/android/datetimepicker/date/DayPickerView.java b/src/com/android/datetimepicker/date/DayPickerView.java
new file mode 100644
index 0000000..76dcf4f
--- /dev/null
+++ b/src/com/android/datetimepicker/date/DayPickerView.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.ListView;
+
+import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * This displays a list of months in a calendar format with selectable days.
+ */
+public class DayPickerView extends ListView implements OnScrollListener {
+
+ private static final String TAG = "MonthFragment";
+
+ // Affects when the month selection will change while scrolling up
+ protected static final int SCROLL_HYST_WEEKS = 2;
+ // How long the GoTo fling animation should last
+ protected static final int GOTO_SCROLL_DURATION = 250;
+ // How long to wait after receiving an onScrollStateChanged notification
+ // before acting on it
+ protected static final int SCROLL_CHANGE_DELAY = 40;
+ // The number of days to display in each week
+ public static final int DAYS_PER_WEEK = 7;
+ public static int LIST_TOP_OFFSET = -1; // so that the top line will be
+ // under the separator
+ // You can override these numbers to get a different appearance
+ protected int mNumWeeks = 6;
+ protected boolean mShowWeekNumber = false;
+ protected int mDaysPerWeek = 7;
+
+ // These affect the scroll speed and feel
+ protected float mFriction = 1.0f;
+
+ protected Context mContext;
+ protected Handler mHandler;
+
+ protected float mMinimumFlingVelocity;
+
+ // highlighted time
+ protected CalendarDay mSelectedDay = new CalendarDay();
+ protected SimpleMonthAdapter mAdapter;
+
+ protected CalendarDay mTempDay = new CalendarDay();
+
+ private static float mScale = 0;
+ // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
+ protected int mFirstDayOfWeek;
+ // The first day that is visible in the view
+ protected Time mFirstVisibleDay = new Time();
+ // The last name announced by accessibility
+ protected CharSequence mPrevMonthName;
+ // which month should be displayed/highlighted [0-11]
+ protected int mCurrentMonthDisplayed;
+ // used for tracking during a scroll
+ protected long mPreviousScrollPosition;
+ // used for tracking what state listview is in
+ protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ // used for tracking what state listview is in
+ protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ private final DatePickerController mController;
+
+ // This causes an update of the view at midnight
+ protected Runnable mTodayUpdater = new Runnable() {
+ @Override
+ public void run() {
+ Time midnight = new Time(mFirstVisibleDay.timezone);
+ midnight.setToNow();
+ long currentMillis = midnight.toMillis(true);
+
+ midnight.hour = 0;
+ midnight.minute = 0;
+ midnight.second = 0;
+ midnight.monthDay++;
+ long millisToMidnight = midnight.normalize(true) - currentMillis;
+ mHandler.postDelayed(this, millisToMidnight);
+
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ // This allows us to update our position when a day is tapped
+ protected DataSetObserver mObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ CalendarDay day = mAdapter.getSelectedDay();
+ if (day.year != mSelectedDay.year || day.day != mSelectedDay.day) {
+ goTo(day, true, true, false);
+ }
+ }
+ };
+
+ public DayPickerView(Context context, DatePickerController controller) {
+ super(context);
+ mHandler = new Handler();
+ mController = controller;
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setDrawSelectorOnTop(false);
+ init(context);
+ setCalendarDate(controller.getSelectedDay());
+ }
+
+ public void setCalendarDate(CalendarDay day) {
+ goTo(day, false, true, true);
+ }
+
+ public void init(Context context) {
+ mContext = context;
+ String tz = Time.getCurrentTimezone();
+ ViewConfiguration viewConfig = ViewConfiguration.get(context);
+ mMinimumFlingVelocity = viewConfig.getScaledMinimumFlingVelocity();
+
+ mFirstVisibleDay.timezone = tz;
+ mFirstVisibleDay.normalize(true);
+
+ setUpListView();
+ setUpAdapter();
+ setAdapter(mAdapter);
+ }
+
+ public void onChange() {
+ setUpAdapter();
+ setAdapter(mAdapter);
+ }
+
+ /**
+ * Creates a new adapter if necessary and sets up its parameters. Override
+ * this method to provide a custom adapter.
+ */
+ protected void setUpAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new SimpleMonthAdapter(getContext(), mController);
+ mAdapter.registerDataSetObserver(mObserver);
+ } else {
+ mAdapter.setSelectedDay(mSelectedDay);
+ mAdapter.notifyDataSetChanged();
+ }
+ // refresh the view with the new parameters
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /*
+ * Sets all the required fields for the list view. Override this method to
+ * set a different list view behavior.
+ */
+ protected void setUpListView() {
+ // Transparent background on scroll
+ setCacheColorHint(0);
+ // No dividers
+ setDivider(null);
+ // Items are clickable
+ setItemsCanFocus(true);
+ // The thumb gets in the way, so disable it
+ setFastScrollEnabled(false);
+ setVerticalScrollBarEnabled(false);
+ setOnScrollListener(this);
+ setFadingEdgeLength(0);
+ // Make the scrolling behavior nicer
+ setFriction(ViewConfiguration.getScrollFriction() * mFriction);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ setUpAdapter();
+ doResumeUpdates();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mHandler.removeCallbacks(mTodayUpdater);
+ }
+
+ // @Override
+ // public void onSaveInstanceState(Bundle outState) {
+ // outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true));
+ // }
+
+ /**
+ * Updates the user preference fields. Override this to use a different
+ * preference space.
+ */
+ protected void doResumeUpdates() {
+ // Get default week start based on locale, subtracting one for use with
+ // android Time.
+ Calendar cal = Calendar.getInstance(Locale.getDefault());
+ mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
+
+ mShowWeekNumber = false;
+
+ goTo(mSelectedDay, false, false, false);
+ mAdapter.setSelectedDay(mSelectedDay);
+ mTodayUpdater.run();
+ }
+
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param time The time to move to
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location
+ * @param setSelected Whether to set the given time as selected
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible
+ * @return Whether or not the view animated to the new location
+ */
+ public boolean goTo(CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {
+
+ // Set the selected day
+ if (setSelected) {
+ mSelectedDay.set(day);
+ }
+
+ mTempDay.set(day);
+ // Get the week we're going to
+ // TODO push Util function into Calendar public api.
+ 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();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "child at " + (i - 1) + " has top " + top);
+ }
+ } 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);
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "GoTo position " + position);
+ }
+ // 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) {
+ smoothScrollToPositionFromTop(
+ position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
+ return true;
+ } else {
+ setSelectionFromTop(position, LIST_TOP_OFFSET);
+ onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
+ }
+ } else if (setSelected) {
+ setMonthDisplayed(mSelectedDay);
+ }
+ return false;
+ }
+
+ /**
+ * Updates the title and selected month if the view has moved to a new
+ * month.
+ */
+ @Override
+ 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;
+ }
+
+ /**
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
+ */
+ protected void setMonthDisplayed(CalendarDay date) {
+ mCurrentMonthDisplayed = date.month;
+ invalidateViews();
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // use a post to prevent re-entering onScrollStateChanged before it
+ // exits
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
+
+ protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
+
+ 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;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
+ }
+ // 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;
+ }
+ }
+ }
+}