summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget/DayPickerView.java
diff options
context:
space:
mode:
authorAlan Viverette <alanv@google.com>2015-03-23 13:13:25 -0700
committerAlan Viverette <alanv@google.com>2015-03-23 13:13:25 -0700
commit0ef59ac0e57e9b99d174d4a53f7d9639357743ac (patch)
treeab71b8c3506571d5ce39a27bd7bb7a3ed1e65a21 /core/java/android/widget/DayPickerView.java
parent2a16460c7c914729e9c256ce39d681524d53b7dc (diff)
downloadframeworks_base-0ef59ac0e57e9b99d174d4a53f7d9639357743ac.zip
frameworks_base-0ef59ac0e57e9b99d174d4a53f7d9639357743ac.tar.gz
frameworks_base-0ef59ac0e57e9b99d174d4a53f7d9639357743ac.tar.bz2
Update DatePicker and CalendarView to latest Material spec
Bug: 19431364 Change-Id: If364a051a5208d170495de4182e46b32c7560e08
Diffstat (limited to 'core/java/android/widget/DayPickerView.java')
-rw-r--r--core/java/android/widget/DayPickerView.java560
1 files changed, 138 insertions, 422 deletions
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index 65af45d..a7ae926 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -16,87 +16,177 @@
package android.widget;
+import com.android.internal.widget.ViewPager;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.util.Log;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
import android.util.MathUtils;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
+import libcore.icu.LocaleData;
+
/**
* This displays a list of months in a calendar format with selectable days.
*/
-class DayPickerView extends ListView implements AbsListView.OnScrollListener {
- private static final String TAG = "DayPickerView";
+class DayPickerView extends ViewPager {
+ private static final int DEFAULT_START_YEAR = 1900;
+ private static final int DEFAULT_END_YEAR = 2100;
- // How long the GoTo fling animation should last
- private static final int GOTO_SCROLL_DURATION = 250;
+ private final Calendar mSelectedDay = Calendar.getInstance();
+ private final Calendar mMinDate = Calendar.getInstance();
+ private final Calendar mMaxDate = Calendar.getInstance();
- // How long to wait after receiving an onScrollStateChanged notification before acting on it
- private static final int SCROLL_CHANGE_DELAY = 40;
+ private final DayPickerAdapter mAdapter;
- // so that the top line will be under the separator
- private static final int LIST_TOP_OFFSET = -1;
+ /** Temporary calendar used for date calculations. */
+ private Calendar mTempCalendar;
- private final SimpleMonthAdapter mAdapter = new SimpleMonthAdapter(getContext());
+ private OnDaySelectedListener mOnDaySelectedListener;
- private final ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(this);
+ public DayPickerView(Context context) {
+ this(context, null);
+ }
- private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
+ public DayPickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.calendarViewStyle);
+ }
- // highlighted time
- private Calendar mSelectedDay = Calendar.getInstance();
- private Calendar mTempDay = Calendar.getInstance();
- private Calendar mMinDate = Calendar.getInstance();
- private Calendar mMaxDate = Calendar.getInstance();
+ public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
- private Calendar mTempCalendar;
+ public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- private OnDaySelectedListener mOnDaySelectedListener;
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.CalendarView, defStyleAttr, defStyleRes);
- // which month should be displayed/highlighted [0-11]
- private int mCurrentMonthDisplayed;
- // used for tracking what state listview is in
- private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
- // used for tracking what state listview is in
- private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
+ LocaleData.get(Locale.getDefault()).firstDayOfWeek);
- private boolean mPerformingScroll;
+ final String minDate = a.getString(R.styleable.CalendarView_minDate);
+ final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
- public DayPickerView(Context context) {
- super(context);
+ final int monthTextAppearanceResId = a.getResourceId(
+ R.styleable.CalendarView_monthTextAppearance,
+ R.style.TextAppearance_Material_Widget_Calendar_Month);
+ final int dayOfWeekTextAppearanceResId = a.getResourceId(
+ R.styleable.CalendarView_weekDayTextAppearance,
+ R.style.TextAppearance_Material_Widget_Calendar_DayOfWeek);
+ final int dayTextAppearanceResId = a.getResourceId(
+ R.styleable.CalendarView_dateTextAppearance,
+ R.style.TextAppearance_Material_Widget_Calendar_Day);
+
+ final ColorStateList daySelectorColor = a.getColorStateList(
+ R.styleable.CalendarView_daySelectorColor);
+
+ a.recycle();
+
+ // Set up adapter.
+ mAdapter = new DayPickerAdapter(context);
+ mAdapter.setMonthTextAppearance(monthTextAppearanceResId);
+ mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId);
+ mAdapter.setDayTextAppearance(dayTextAppearanceResId);
+ mAdapter.setDaySelectorColor(daySelectorColor);
setAdapter(mAdapter);
- setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- setDrawSelectorOnTop(false);
- setUpListView();
- goTo(mSelectedDay.getTimeInMillis(), false, false, true);
+ // Set up min and max dates.
+ final Calendar tempDate = Calendar.getInstance();
+ if (!CalendarView.parseDate(minDate, tempDate)) {
+ tempDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
+ }
+ final long minDateMillis = tempDate.getTimeInMillis();
+
+ if (!CalendarView.parseDate(maxDate, tempDate)) {
+ tempDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
+ }
+ final long maxDateMillis = tempDate.getTimeInMillis();
+
+ if (maxDateMillis < minDateMillis) {
+ throw new IllegalArgumentException("maxDate must be >= minDate");
+ }
+
+ final long setDateMillis = MathUtils.constrain(
+ System.currentTimeMillis(), minDateMillis, maxDateMillis);
- mAdapter.setOnDaySelectedListener(mProxyOnDaySelectedListener);
+ setFirstDayOfWeek(firstDayOfWeek);
+ setMinDate(minDateMillis);
+ setMaxDate(maxDateMillis);
+ setDate(setDateMillis, false);
+
+ // Proxy selection callbacks to our own listener.
+ mAdapter.setOnDaySelectedListener(new DayPickerAdapter.OnDaySelectedListener() {
+ @Override
+ public void onDaySelected(DayPickerAdapter adapter, Calendar day) {
+ if (mOnDaySelectedListener != null) {
+ mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
+ }
+ }
+ });
+ }
+
+ public void setDayOfWeekTextAppearance(int resId) {
+ mAdapter.setDayOfWeekTextAppearance(resId);
+ }
+
+ public int getDayOfWeekTextAppearance() {
+ return mAdapter.getDayOfWeekTextAppearance();
+ }
+
+ public void setDayTextAppearance(int resId) {
+ mAdapter.setDayTextAppearance(resId);
+ }
+
+ public int getDayTextAppearance() {
+ return mAdapter.getDayTextAppearance();
}
/**
* Sets the currently selected date to the specified timestamp. Jumps
* immediately to the new date. To animate to the new date, use
- * {@link #setDate(long, boolean, boolean)}.
+ * {@link #setDate(long, boolean)}.
*
- * @param timeInMillis
+ * @param timeInMillis the target day in milliseconds
*/
public void setDate(long timeInMillis) {
- setDate(timeInMillis, false, true);
+ setDate(timeInMillis, false);
}
- public void setDate(long timeInMillis, boolean animate, boolean forceScroll) {
- goTo(timeInMillis, animate, true, forceScroll);
+ /**
+ * Sets the currently selected date to the specified timestamp. Jumps
+ * immediately to the new date, optionally animating the transition.
+ *
+ * @param timeInMillis the target day in milliseconds
+ * @param animate whether to smooth scroll to the new position
+ */
+ public void setDate(long timeInMillis, boolean animate) {
+ setDate(timeInMillis, animate, true);
+ }
+
+ /**
+ * Moves to the month containing the specified day, optionally setting the
+ * day as selected.
+ *
+ * @param timeInMillis the target day in milliseconds
+ * @param animate whether to smooth scroll to the new position
+ * @param setSelected whether to set the specified day as selected
+ */
+ private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
+ // Set the selected day
+ if (setSelected) {
+ mSelectedDay.setTimeInMillis(timeInMillis);
+ }
+
+ final int position = getPositionFromDay(timeInMillis);
+ if (position != getCurrentItem()) {
+ setCurrentItem(position, animate);
+ }
}
public long getDate() {
@@ -137,7 +227,7 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
// Changing the min/max date changes the selection position since we
// don't really have stable IDs. Jumps immediately to the new position.
- goTo(mSelectedDay.getTimeInMillis(), false, false, true);
+ setDate(mSelectedDay.getTimeInMillis(), false, false);
}
/**
@@ -149,30 +239,9 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
mOnDaySelectedListener = listener;
}
- /*
- * Sets all the required fields for the list view. Override this method to
- * set a different list view behavior.
- */
- private 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());
- }
-
private int getDiffMonths(Calendar start, Calendar end) {
final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
- final int diffMonths = end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
- return diffMonths;
+ return end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
}
private int getPositionFromDay(long timeInMillis) {
@@ -190,366 +259,13 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
}
/**
- * 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 day The day 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
- */
- private boolean goTo(long day, boolean animate, boolean setSelected, boolean forceScroll) {
-
- // Set the selected day
- if (setSelected) {
- mSelectedDay.setTimeInMillis(day);
- }
-
- mTempDay.setTimeInMillis(day);
- final int position = getPositionFromDay(day);
-
- 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) {
- smoothScrollToPositionFromTop(
- position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
- return true;
- } else {
- postSetSelection(position);
- }
- } else if (setSelected) {
- setMonthDisplayed(mSelectedDay);
- }
- return false;
- }
-
- public void postSetSelection(final int position) {
- clearFocus();
- post(new Runnable() {
-
- @Override
- public void run() {
- setSelection(position);
- }
- });
- onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
- }
-
- /**
- * 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;
- }
-
- 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(Calendar date) {
- if (mCurrentMonthDisplayed != date.get(Calendar.MONTH)) {
- mCurrentMonthDisplayed = date.get(Calendar.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);
- }
-
- void setCalendarTextColor(ColorStateList colors) {
- mAdapter.setCalendarTextColor(colors);
- }
-
- void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) {
- mAdapter.setCalendarDayBackgroundColor(dayBackgroundColor);
- }
-
- void setCalendarTextAppearance(int resId) {
- mAdapter.setCalendarTextAppearance(resId);
- }
-
- protected class ScrollStateRunnable implements Runnable {
- private int mNewState;
- private View mParent;
-
- ScrollStateRunnable(View view) {
- mParent = view;
- }
-
- /**
- * 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) {
- mParent.removeCallbacks(this);
- mNewState = scrollState;
- mParent.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;
- }
- }
- }
-
- /**
* Gets the position of the view that is most prominently displayed within the list view.
*/
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;
- }
-
- /**
- * Attempts to return the date that has accessibility focus.
- *
- * @return The date that has accessibility focus, or {@code null} if no date
- * has focus.
- */
- private Calendar findAccessibilityFocus() {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child instanceof SimpleMonthView) {
- final Calendar focus = ((SimpleMonthView) child).getAccessibilityFocus();
- if (focus != null) {
- return focus;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Attempts to restore accessibility focus to a given date. No-op if
- * {@code day} is {@code null}.
- *
- * @param day The date that should receive accessibility focus
- * @return {@code true} if focus was restored
- */
- private boolean restoreAccessibilityFocus(Calendar day) {
- if (day == null) {
- return false;
- }
-
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child instanceof SimpleMonthView) {
- if (((SimpleMonthView) child).restoreAccessibilityFocus(day)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Override
- protected void layoutChildren() {
- final Calendar focusedDay = findAccessibilityFocus();
- super.layoutChildren();
- if (mPerformingScroll) {
- mPerformingScroll = false;
- } else {
- restoreAccessibilityFocus(focusedDay);
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
- }
-
- /** @hide */
- @Override
- public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
- super.onInitializeAccessibilityEventInternal(event);
- event.setItemCount(-1);
- }
-
- private String getMonthAndYearString(Calendar day) {
- final StringBuilder sbuf = new StringBuilder();
- sbuf.append(day.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()));
- sbuf.append(" ");
- sbuf.append(mYearFormat.format(day.getTime()));
- return sbuf.toString();
- }
-
- /**
- * Necessary for accessibility, to ensure we support "scrolling" forward and backward
- * in the month list.
- *
- * @hide
- */
- @Override
- public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfoInternal(info);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
- }
-
- /**
- * When scroll forward/backward events are received, announce the newly scrolled-to month.
- *
- * @hide
- */
- @Override
- public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
- if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
- action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
- return super.performAccessibilityActionInternal(action, arguments);
- }
-
- // Figure out what month is showing.
- final int firstVisiblePosition = getFirstVisiblePosition();
- final int month = firstVisiblePosition % 12;
- final int year = firstVisiblePosition / 12 + mMinDate.get(Calendar.YEAR);
- final Calendar day = Calendar.getInstance();
- day.set(year, month, 1);
-
- // Scroll either forward or backward one month.
- if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
- day.add(Calendar.MONTH, 1);
- if (day.get(Calendar.MONTH) == 12) {
- day.set(Calendar.MONTH, 0);
- day.add(Calendar.YEAR, 1);
- }
- } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
- View firstVisibleView = getChildAt(0);
- // If the view is fully visible, jump one month back. Otherwise, we'll just jump
- // to the first day of first visible month.
- if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
- // There's an off-by-one somewhere, so the top of the first visible item will
- // actually be -1 when it's at the exact top.
- day.add(Calendar.MONTH, -1);
- if (day.get(Calendar.MONTH) == -1) {
- day.set(Calendar.MONTH, 11);
- day.add(Calendar.YEAR, -1);
- }
- }
- }
-
- // Go to that month.
- announceForAccessibility(getMonthAndYearString(day));
- goTo(day.getTimeInMillis(), true, false, true);
- mPerformingScroll = true;
- return true;
+ return getCurrentItem();
}
public interface OnDaySelectedListener {
public void onDaySelected(DayPickerView view, Calendar day);
}
-
- private final SimpleMonthAdapter.OnDaySelectedListener
- mProxyOnDaySelectedListener = new SimpleMonthAdapter.OnDaySelectedListener() {
- @Override
- public void onDaySelected(SimpleMonthAdapter adapter, Calendar day) {
- if (mOnDaySelectedListener != null) {
- mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
- }
- }
- };
}