diff options
author | Alan Viverette <alanv@google.com> | 2015-03-23 13:13:25 -0700 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2015-03-23 13:13:25 -0700 |
commit | 0ef59ac0e57e9b99d174d4a53f7d9639357743ac (patch) | |
tree | ab71b8c3506571d5ce39a27bd7bb7a3ed1e65a21 /core/java/android/widget/DayPickerView.java | |
parent | 2a16460c7c914729e9c256ce39d681524d53b7dc (diff) | |
download | frameworks_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.java | 560 |
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); - } - } - }; } |