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 | |
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')
-rw-r--r-- | core/java/android/content/res/ColorStateList.java | 27 | ||||
-rw-r--r-- | core/java/android/widget/CalendarView.java | 199 | ||||
-rw-r--r-- | core/java/android/widget/CalendarViewLegacyDelegate.java | 9 | ||||
-rw-r--r-- | core/java/android/widget/CalendarViewMaterialDelegate.java | 160 | ||||
-rw-r--r-- | core/java/android/widget/DatePicker.java | 13 | ||||
-rwxr-xr-x | core/java/android/widget/DatePickerCalendarDelegate.java | 463 | ||||
-rw-r--r-- | core/java/android/widget/DayPickerAdapter.java | 283 | ||||
-rw-r--r-- | core/java/android/widget/DayPickerView.java | 560 | ||||
-rw-r--r-- | core/java/android/widget/SimpleMonthAdapter.java | 220 | ||||
-rw-r--r-- | core/java/android/widget/SimpleMonthView.java | 695 | ||||
-rw-r--r-- | core/java/android/widget/TextViewWithCircularIndicator.java | 87 | ||||
-rw-r--r-- | core/java/android/widget/YearPickerView.java | 245 | ||||
-rw-r--r-- | core/java/com/android/internal/widget/AccessibleDateAnimator.java | 56 | ||||
-rw-r--r-- | core/java/com/android/internal/widget/ViewPager.java | 34 |
14 files changed, 1447 insertions, 1604 deletions
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 841b09d..7d8dff3 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -464,6 +464,33 @@ public class ColorStateList implements Parcelable { return mColors; } + /** + * Returns whether the specified state is referenced in any of the state + * specs contained within this ColorStateList. + * <p> + * Any reference, either positive or negative {ex. ~R.attr.state_enabled}, + * will cause this method to return {@code true}. Wildcards are not counted + * as references. + * + * @param state the state to search for + * @return {@code true} if the state if referenced, {@code false} otherwise + * @hide Use only as directed. For internal use only. + */ + public boolean hasState(int state) { + final int[][] stateSpecs = mStateSpecs; + final int specCount = stateSpecs.length; + for (int specIndex = 0; specIndex < specCount; specIndex++) { + final int[] states = stateSpecs[specIndex]; + final int stateCount = states.length; + for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) { + if (states[stateIndex] == state || states[stateIndex] == ~state) { + return true; + } + } + } + return false; + } + @Override public String toString() { return "ColorStateList{" + diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index 5bc16cb..2aaa356 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -18,6 +18,7 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.StyleRes; import android.annotation.Widget; import android.content.Context; import android.content.res.Configuration; @@ -31,6 +32,7 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -118,7 +120,9 @@ public class CalendarView extends FrameLayout { * @param count The shown week count. * * @attr ref android.R.styleable#CalendarView_shownWeekCount + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setShownWeekCount(int count) { mDelegate.setShownWeekCount(count); } @@ -129,7 +133,9 @@ public class CalendarView extends FrameLayout { * @return The shown week count. * * @attr ref android.R.styleable#CalendarView_shownWeekCount + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public int getShownWeekCount() { return mDelegate.getShownWeekCount(); } @@ -140,7 +146,9 @@ public class CalendarView extends FrameLayout { * @param color The week background color. * * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setSelectedWeekBackgroundColor(@ColorInt int color) { mDelegate.setSelectedWeekBackgroundColor(color); } @@ -151,8 +159,10 @@ public class CalendarView extends FrameLayout { * @return The week background color. * * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getSelectedWeekBackgroundColor() { return mDelegate.getSelectedWeekBackgroundColor(); } @@ -163,7 +173,9 @@ public class CalendarView extends FrameLayout { * @param color The focused month date color. * * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setFocusedMonthDateColor(@ColorInt int color) { mDelegate.setFocusedMonthDateColor(color); } @@ -174,8 +186,10 @@ public class CalendarView extends FrameLayout { * @return The focused month date color. * * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getFocusedMonthDateColor() { return mDelegate.getFocusedMonthDateColor(); } @@ -186,7 +200,9 @@ public class CalendarView extends FrameLayout { * @param color A not focused month date color. * * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setUnfocusedMonthDateColor(@ColorInt int color) { mDelegate.setUnfocusedMonthDateColor(color); } @@ -197,8 +213,10 @@ public class CalendarView extends FrameLayout { * @return A not focused month date color. * * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getUnfocusedMonthDateColor() { return mDelegate.getUnfocusedMonthDateColor(); } @@ -209,7 +227,9 @@ public class CalendarView extends FrameLayout { * @param color The week number color. * * @attr ref android.R.styleable#CalendarView_weekNumberColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setWeekNumberColor(@ColorInt int color) { mDelegate.setWeekNumberColor(color); } @@ -220,8 +240,10 @@ public class CalendarView extends FrameLayout { * @return The week number color. * * @attr ref android.R.styleable#CalendarView_weekNumberColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getWeekNumberColor() { return mDelegate.getWeekNumberColor(); } @@ -232,7 +254,9 @@ public class CalendarView extends FrameLayout { * @param color The week separator color. * * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setWeekSeparatorLineColor(@ColorInt int color) { mDelegate.setWeekSeparatorLineColor(color); } @@ -243,8 +267,10 @@ public class CalendarView extends FrameLayout { * @return The week separator color. * * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getWeekSeparatorLineColor() { return mDelegate.getWeekSeparatorLineColor(); } @@ -256,7 +282,9 @@ public class CalendarView extends FrameLayout { * @param resourceId The vertical bar drawable resource id. * * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setSelectedDateVerticalBar(@DrawableRes int resourceId) { mDelegate.setSelectedDateVerticalBar(resourceId); } @@ -268,7 +296,9 @@ public class CalendarView extends FrameLayout { * @param drawable The vertical bar drawable. * * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setSelectedDateVerticalBar(Drawable drawable) { mDelegate.setSelectedDateVerticalBar(drawable); } @@ -278,7 +308,9 @@ public class CalendarView extends FrameLayout { * the end of the selected date. * * @return The vertical bar drawable. + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public Drawable getSelectedDateVerticalBar() { return mDelegate.getSelectedDateVerticalBar(); } @@ -519,29 +551,36 @@ public class CalendarView extends FrameLayout { void setShownWeekCount(int count); int getShownWeekCount(); - void setSelectedWeekBackgroundColor(int color); + void setSelectedWeekBackgroundColor(@ColorInt int color); + @ColorInt int getSelectedWeekBackgroundColor(); - void setFocusedMonthDateColor(int color); + void setFocusedMonthDateColor(@ColorInt int color); + @ColorInt int getFocusedMonthDateColor(); - void setUnfocusedMonthDateColor(int color); + void setUnfocusedMonthDateColor(@ColorInt int color); + @ColorInt int getUnfocusedMonthDateColor(); - void setWeekNumberColor(int color); + void setWeekNumberColor(@ColorInt int color); + @ColorInt int getWeekNumberColor(); - void setWeekSeparatorLineColor(int color); + void setWeekSeparatorLineColor(@ColorInt int color); + @ColorInt int getWeekSeparatorLineColor(); - void setSelectedDateVerticalBar(int resourceId); + void setSelectedDateVerticalBar(@DrawableRes int resourceId); void setSelectedDateVerticalBar(Drawable drawable); Drawable getSelectedDateVerticalBar(); - void setWeekDayTextAppearance(int resourceId); + void setWeekDayTextAppearance(@StyleRes int resourceId); + @StyleRes int getWeekDayTextAppearance(); - void setDateTextAppearance(int resourceId); + void setDateTextAppearance(@StyleRes int resourceId); + @StyleRes int getDateTextAppearance(); void setMinDate(long minDate); @@ -569,18 +608,12 @@ public class CalendarView extends FrameLayout { * An abstract class which can be used as a start for CalendarView implementations */ abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate { - /** String for parsing dates. */ - private static final String DATE_FORMAT = "MM/dd/yyyy"; - /** The default minimal date. */ protected static final String DEFAULT_MIN_DATE = "01/01/1900"; /** The default maximal date. */ protected static final String DEFAULT_MAX_DATE = "01/01/2100"; - /** Date format for parsing dates. */ - protected static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); - protected CalendarView mDelegator; protected Context mContext; protected Locale mCurrentLocale; @@ -600,21 +633,131 @@ public class CalendarView extends FrameLayout { mCurrentLocale = locale; } - /** - * Parses the given <code>date</code> and in case of success sets - * the result to the <code>outDate</code>. - * - * @return True if the date was parsed. - */ - protected boolean parseDate(String date, Calendar outDate) { - try { - outDate.setTime(DATE_FORMATTER.parse(date)); - return true; - } catch (ParseException e) { - Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); - return false; - } + @Override + public void setShownWeekCount(int count) { + // Deprecated. + } + + @Override + public int getShownWeekCount() { + // Deprecated. + return 0; + } + + @Override + public void setSelectedWeekBackgroundColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getSelectedWeekBackgroundColor() { + return 0; + } + + @Override + public void setFocusedMonthDateColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getFocusedMonthDateColor() { + return 0; + } + + @Override + public void setUnfocusedMonthDateColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getUnfocusedMonthDateColor() { + return 0; + } + + @Override + public void setWeekNumberColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getWeekNumberColor() { + // Deprecated. + return 0; + } + + @Override + public void setWeekSeparatorLineColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getWeekSeparatorLineColor() { + // Deprecated. + return 0; + } + + @Override + public void setSelectedDateVerticalBar(@DrawableRes int resId) { + // Deprecated. + } + + @Override + public void setSelectedDateVerticalBar(Drawable drawable) { + // Deprecated. + } + + @Override + public Drawable getSelectedDateVerticalBar() { + // Deprecated. + return null; + } + + @Override + public void setShowWeekNumber(boolean showWeekNumber) { + // Deprecated. + } + + @Override + public boolean getShowWeekNumber() { + // Deprecated. + return false; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // Nothing to do here, configuration changes are already propagated + // by ViewGroup. } } + /** String for parsing dates. */ + private static final String DATE_FORMAT = "MM/dd/yyyy"; + + /** Date format for parsing dates. */ + private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + + /** + * Utility method for the date format used by CalendarView's min/max date. + * + * @hide Use only as directed. For internal use only. + */ + public static boolean parseDate(String date, Calendar outDate) { + if (date == null || date.isEmpty()) { + return false; + } + + try { + final Date parsedDate = DATE_FORMATTER.parse(date); + outDate.setTime(parsedDate); + return true; + } catch (ParseException e) { + Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); + return false; + } + } } diff --git a/core/java/android/widget/CalendarViewLegacyDelegate.java b/core/java/android/widget/CalendarViewLegacyDelegate.java index 2ab3548..6ab3828 100644 --- a/core/java/android/widget/CalendarViewLegacyDelegate.java +++ b/core/java/android/widget/CalendarViewLegacyDelegate.java @@ -27,7 +27,6 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.text.TextUtils; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -267,12 +266,12 @@ class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelega mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, LocaleData.get(Locale.getDefault()).firstDayOfWeek); final String minDate = a.getString(R.styleable.CalendarView_minDate); - if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { - parseDate(DEFAULT_MIN_DATE, mMinDate); + if (!CalendarView.parseDate(minDate, mMinDate)) { + CalendarView.parseDate(DEFAULT_MIN_DATE, mMinDate); } final String maxDate = a.getString(R.styleable.CalendarView_maxDate); - if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { - parseDate(DEFAULT_MAX_DATE, mMaxDate); + if (!CalendarView.parseDate(maxDate, mMaxDate)) { + CalendarView.parseDate(DEFAULT_MAX_DATE, mMaxDate); } if (mMaxDate.before(mMinDate)) { throw new IllegalArgumentException("Max date cannot be before min date."); diff --git a/core/java/android/widget/CalendarViewMaterialDelegate.java b/core/java/android/widget/CalendarViewMaterialDelegate.java index b0f3740..7bce756 100644 --- a/core/java/android/widget/CalendarViewMaterialDelegate.java +++ b/core/java/android/widget/CalendarViewMaterialDelegate.java @@ -16,20 +16,11 @@ package android.widget; -import com.android.internal.R; - +import android.annotation.StyleRes; import android.content.Context; -import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; import android.util.AttributeSet; -import android.util.MathUtils; import java.util.Calendar; -import java.util.Locale; - -import libcore.icu.LocaleData; class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDelegate { private final DayPickerView mDayPickerView; @@ -40,142 +31,32 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele int defStyleAttr, int defStyleRes) { super(delegator, context); - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.CalendarView, defStyleAttr, defStyleRes); - final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, - LocaleData.get(Locale.getDefault()).firstDayOfWeek); - - final long minDate = parseDateToMillis(a.getString( - R.styleable.CalendarView_minDate), DEFAULT_MIN_DATE); - final long maxDate = parseDateToMillis(a.getString( - R.styleable.CalendarView_maxDate), DEFAULT_MAX_DATE); - if (maxDate < minDate) { - throw new IllegalArgumentException("max date cannot be before min date"); - } - - final long setDate = MathUtils.constrain(System.currentTimeMillis(), minDate, maxDate); - final int dateTextAppearanceResId = a.getResourceId( - R.styleable.CalendarView_dateTextAppearance, - R.style.TextAppearance_DeviceDefault_Small); - - a.recycle(); - - mDayPickerView = new DayPickerView(context); - mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); - mDayPickerView.setCalendarTextAppearance(dateTextAppearanceResId); - mDayPickerView.setMinDate(minDate); - mDayPickerView.setMaxDate(maxDate); - mDayPickerView.setDate(setDate, false, true); + mDayPickerView = new DayPickerView(context, attrs, defStyleAttr, defStyleRes); mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener); delegator.addView(mDayPickerView); } - private long parseDateToMillis(String dateStr, String defaultDateStr) { - final Calendar tempCalendar = Calendar.getInstance(); - if (TextUtils.isEmpty(dateStr) || !parseDate(dateStr, tempCalendar)) { - parseDate(defaultDateStr, tempCalendar); - } - return tempCalendar.getTimeInMillis(); - } - @Override - public void setShownWeekCount(int count) { - // Deprecated. - } - - @Override - public int getShownWeekCount() { - // Deprecated. - return 0; - } - - @Override - public void setSelectedWeekBackgroundColor(int color) { - // TODO: Should use a ColorStateList. Deprecate? - } - - @Override - public int getSelectedWeekBackgroundColor() { - return 0; - } - - @Override - public void setFocusedMonthDateColor(int color) { - // TODO: Should use a ColorStateList. Deprecate? - } - - @Override - public int getFocusedMonthDateColor() { - return 0; - } - - @Override - public void setUnfocusedMonthDateColor(int color) { - // TODO: Should use a ColorStateList. Deprecate? - } - - @Override - public int getUnfocusedMonthDateColor() { - return 0; - } - - @Override - public void setWeekDayTextAppearance(int resourceId) { - + public void setWeekDayTextAppearance(@StyleRes int resId) { + mDayPickerView.setDayOfWeekTextAppearance(resId); } + @StyleRes @Override public int getWeekDayTextAppearance() { - return 0; + return mDayPickerView.getDayOfWeekTextAppearance(); } @Override - public void setDateTextAppearance(int resourceId) { - + public void setDateTextAppearance(@StyleRes int resId) { + mDayPickerView.setDayTextAppearance(resId); } + @StyleRes @Override public int getDateTextAppearance() { - return 0; - } - - @Override - public void setWeekNumberColor(int color) { - // Deprecated. - } - - @Override - public int getWeekNumberColor() { - // Deprecated. - return 0; - } - - @Override - public void setWeekSeparatorLineColor(int color) { - // Deprecated. - } - - @Override - public int getWeekSeparatorLineColor() { - // Deprecated. - return 0; - } - - @Override - public void setSelectedDateVerticalBar(int resourceId) { - // Deprecated. - } - - @Override - public void setSelectedDateVerticalBar(Drawable drawable) { - // Deprecated. - } - - @Override - public Drawable getSelectedDateVerticalBar() { - // Deprecated. - return null; + return mDayPickerView.getDayTextAppearance(); } @Override @@ -199,17 +80,6 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele } @Override - public void setShowWeekNumber(boolean showWeekNumber) { - // Deprecated. - } - - @Override - public boolean getShowWeekNumber() { - // Deprecated. - return false; - } - - @Override public void setFirstDayOfWeek(int firstDayOfWeek) { mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); } @@ -221,12 +91,12 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele @Override public void setDate(long date) { - mDayPickerView.setDate(date, true, false); + mDayPickerView.setDate(date, true); } @Override public void setDate(long date, boolean animate, boolean center) { - mDayPickerView.setDate(date, animate, center); + mDayPickerView.setDate(date, animate); } @Override @@ -239,12 +109,6 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele mOnDateChangeListener = listener; } - @Override - public void onConfigurationChanged(Configuration newConfig) { - // Nothing to do here, configuration changes are already propagated - // by ViewGroup. - } - private final DayPickerView.OnDaySelectedListener mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() { @Override diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 45998f7..19ae6e2 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -98,7 +98,7 @@ public class DatePicker extends FrameLayout { private final DatePickerDelegate mDelegate; /** - * The callback used to indicate the user changes\d the date. + * The callback used to indicate the user changed the date. */ public interface OnDateChangedListener { @@ -489,15 +489,14 @@ public class DatePicker extends FrameLayout { mDelegator = delegator; mContext = context; - // initialization based on locale setCurrentLocale(Locale.getDefault()); } protected void setCurrentLocale(Locale locale) { - if (locale.equals(mCurrentLocale)) { - return; + if (!locale.equals(mCurrentLocale)) { + mCurrentLocale = locale; + onLocaleChanged(locale); } - mCurrentLocale = locale; } @Override @@ -510,6 +509,10 @@ public class DatePicker extends FrameLayout { mValidationCallback.onValidationChanged(valid); } } + + protected void onLocaleChanged(Locale locale) { + // Stub. + } } /** diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index 0e3ec7f..de43b2f 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -26,31 +27,34 @@ import android.os.Parcelable; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; +import android.util.StateSet; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.widget.DayPickerView.OnDaySelectedListener; +import android.widget.YearPickerView.OnYearSelectedListener; import com.android.internal.R; -import com.android.internal.widget.AccessibleDateAnimator; import java.text.SimpleDateFormat; import java.util.Calendar; -import java.util.HashSet; import java.util.Locale; /** * A delegate for picking up a date (day / month / year). */ -class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements - View.OnClickListener, DatePickerController { +class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { + private static final int USE_LOCALE = 0; private static final int UNINITIALIZED = -1; - private static final int MONTH_AND_DAY_VIEW = 0; - private static final int YEAR_VIEW = 1; + private static final int VIEW_MONTH_DAY = 0; + private static final int VIEW_YEAR = 1; private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; @@ -61,33 +65,30 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i private static final int DAY_INDEX = 1; private static final int YEAR_INDEX = 2; - private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault()); - private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault()); + public static final int[] ATTRS_TEXT_COLOR = new int[]{com.android.internal.R.attr.textColor}; + + public static final int[] ATTRS_DISABLED_ALPHA = new int[]{ + com.android.internal.R.attr.disabledAlpha}; - private TextView mDayOfWeekView; + private SimpleDateFormat mYearFormat; + private SimpleDateFormat mMonthDayFormat; - /** Layout that contains the current month, day, and year. */ - private LinearLayout mMonthDayYearLayout; + // Top-level container. + private ViewGroup mContainer; - /** Clickable layout that contains the current day and year. */ - private LinearLayout mMonthAndDayLayout; + // Header views. + private TextView mHeaderYear; + private TextView mHeaderMonthDay; - private TextView mHeaderMonthTextView; - private TextView mHeaderDayOfMonthTextView; - private TextView mHeaderYearTextView; + // Picker views. + private ViewAnimator mAnimator; private DayPickerView mDayPickerView; private YearPickerView mYearPickerView; - private boolean mIsEnabled = true; - // Accessibility strings. - private String mDayPickerDescription; private String mSelectDay; - private String mYearPickerDescription; private String mSelectYear; - private AccessibleDateAnimator mAnimator; - private DatePicker.OnDateChangedListener mDateChangedListener; private int mCurrentView = UNINITIALIZED; @@ -99,13 +100,11 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i private int mFirstDayOfWeek = USE_LOCALE; - private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>(); - public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(delegator, context); - final Locale locale = Locale.getDefault(); + final Locale locale = mCurrentLocale; mMinDate = getCalendarForLocale(mMinDate, locale); mMaxDate = getCalendarForLocale(mMaxDate, locale); mTempDate = getCalendarForLocale(mMaxDate, locale); @@ -120,71 +119,64 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); final int layoutResourceId = a.getResourceId( - R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo); - final View mainView = inflater.inflate(layoutResourceId, null); - mDelegator.addView(mainView); + R.styleable.DatePicker_internalLayout, R.layout.date_picker_material); - mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header); + // Set up and attach container. + mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator); - // Layout that contains the current date and day name header. - final LinearLayout dateLayout = (LinearLayout) mainView.findViewById( - R.id.day_picker_selector_layout); - mMonthDayYearLayout = (LinearLayout) mainView.findViewById( - R.id.date_picker_month_day_year_layout); - mMonthAndDayLayout = (LinearLayout) mainView.findViewById( - R.id.date_picker_month_and_day_layout); - mMonthAndDayLayout.setOnClickListener(this); - mHeaderMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month); - mHeaderDayOfMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_day); - mHeaderYearTextView = (TextView) mainView.findViewById(R.id.date_picker_year); - mHeaderYearTextView.setOnClickListener(this); + // Set up header views. + final ViewGroup header = (ViewGroup) mContainer.findViewById(R.id.date_picker_header); + mHeaderYear = (TextView) header.findViewById(R.id.date_picker_header_year); + mHeaderYear.setOnClickListener(mOnHeaderClickListener); + mHeaderMonthDay = (TextView) header.findViewById(R.id.date_picker_header_date); + mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener); - // Obtain default highlight color from the theme. - final int defaultHighlightColor = mHeaderYearTextView.getHighlightColor(); + // For the sake of backwards compatibility, attempt to extract the text + // color from the header month text appearance. If it's set, we'll let + // that override the "real" header text color. + ColorStateList headerTextColor = null; - // Use Theme attributes if possible - final int dayOfWeekTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_dayOfWeekTextAppearance, 0); - if (dayOfWeekTextAppearanceResId != 0) { - mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId); + @SuppressWarnings("deprecation") + final int monthHeaderTextAppearance = a.getResourceId( + R.styleable.DatePicker_headerMonthTextAppearance, 0); + if (monthHeaderTextAppearance != 0) { + final TypedArray textAppearance = mContext.obtainStyledAttributes(null, + ATTRS_TEXT_COLOR, 0, monthHeaderTextAppearance); + final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0); + headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor); + textAppearance.recycle(); } - mDayOfWeekView.setBackground(a.getDrawable(R.styleable.DatePicker_dayOfWeekBackground)); - - dateLayout.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground)); - - final int monthTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerMonthTextAppearance, 0); - if (monthTextAppearanceResId != 0) { - mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId); + if (headerTextColor == null) { + headerTextColor = a.getColorStateList(R.styleable.DatePicker_headerTextColor); } - final int dayOfMonthTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerDayOfMonthTextAppearance, 0); - if (dayOfMonthTextAppearanceResId != 0) { - mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId); + if (headerTextColor != null) { + mHeaderYear.setTextColor(headerTextColor); + mHeaderMonthDay.setTextColor(headerTextColor); } - final int headerYearTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerYearTextAppearance, 0); - if (headerYearTextAppearanceResId != 0) { - mHeaderYearTextView.setTextAppearance(context, headerYearTextAppearanceResId); + // Set up header background, if available. + if (a.hasValueOrEmpty(R.styleable.DatePicker_headerBackground)) { + header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground)); } - mDayPickerView = new DayPickerView(mContext); + // Set up picker container. + mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator); + + // Set up day picker view. + mDayPickerView = (DayPickerView) mAnimator.findViewById(R.id.date_picker_day_picker); mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek); mDayPickerView.setMinDate(mMinDate.getTimeInMillis()); mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis()); mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener); - mYearPickerView = new YearPickerView(mContext); - mYearPickerView.init(this); + // Set up year picker view. + mYearPickerView = (YearPickerView) mAnimator.findViewById(R.id.date_picker_year_picker); mYearPickerView.setRange(mMinDate, mMaxDate); - - final ColorStateList yearBackgroundColor = a.getColorStateList( - R.styleable.DatePicker_yearListSelectorColor); - mYearPickerView.setYearBackgroundColor(yearBackgroundColor); + mYearPickerView.setDate(mCurrentDate.getTimeInMillis()); + mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener); final int yearTextAppearanceResId = a.getResourceId( R.styleable.DatePicker_yearListItemTextAppearance, 0); @@ -192,37 +184,138 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i mYearPickerView.setYearTextAppearance(yearTextAppearanceResId); } - final ColorStateList calendarTextColor = a.getColorStateList( - R.styleable.DatePicker_calendarTextColor); - mDayPickerView.setCalendarTextColor(calendarTextColor); + final int yearActivatedTextAppearanceResId = a.getResourceId( + R.styleable.DatePicker_yearListItemActivatedTextAppearance, 0); + if (yearActivatedTextAppearanceResId != 0) { + mYearPickerView.setYearActivatedTextAppearance(yearActivatedTextAppearanceResId); + } - final ColorStateList calendarDayBackgroundColor = a.getColorStateList( - R.styleable.DatePicker_calendarDayBackgroundColor); - mDayPickerView.setCalendarDayBackgroundColor(calendarDayBackgroundColor); + a.recycle(); - mDayPickerDescription = res.getString(R.string.day_picker_description); + // Set up content descriptions. mSelectDay = res.getString(R.string.select_day); - mYearPickerDescription = res.getString(R.string.year_picker_description); mSelectYear = res.getString(R.string.select_year); - mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator); - mAnimator.addView(mDayPickerView); - mAnimator.addView(mYearPickerView); - mAnimator.setDateMillis(mCurrentDate.getTimeInMillis()); + final Animation inAnim = new AlphaAnimation(0, 1); + inAnim.setDuration(ANIMATION_DURATION); + mAnimator.setInAnimation(inAnim); + + final Animation outAnim = new AlphaAnimation(1, 0); + outAnim.setDuration(ANIMATION_DURATION); + mAnimator.setOutAnimation(outAnim); - final Animation animation = new AlphaAnimation(0.0f, 1.0f); - animation.setDuration(ANIMATION_DURATION); - mAnimator.setInAnimation(animation); + // Initialize for current locale. This also initializes the date, so no + // need to call onDateChanged. + onLocaleChanged(mCurrentLocale); - final Animation animation2 = new AlphaAnimation(1.0f, 0.0f); - animation2.setDuration(ANIMATION_DURATION); - mAnimator.setOutAnimation(animation2); + setCurrentView(VIEW_MONTH_DAY); + } + + /** + * The legacy text color might have been poorly defined. Ensures that it + * has an appropriate activated state, using the selected state if one + * exists or modifying the default text color otherwise. + * + * @param color a legacy text color, or {@code null} + * @return a color state list with an appropriate activated state, or + * {@code null} if a valid activated state could not be generated + */ + @Nullable + private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) { + if (color == null || color.hasState(R.attr.state_activated)) { + return color; + } + + final int activatedColor; + final int defaultColor; + if (color.hasState(R.attr.state_selected)) { + activatedColor = color.getColorForState(StateSet.get( + StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0); + defaultColor = color.getColorForState(StateSet.get( + StateSet.VIEW_STATE_ENABLED), 0); + } else { + activatedColor = color.getDefaultColor(); + + // Generate a non-activated color using the disabled alpha. + final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA); + final float disabledAlpha = ta.getFloat(0, 0.30f); + defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha); + } + + if (activatedColor == 0 || defaultColor == 0) { + // We somehow failed to obtain the colors. + return null; + } + + final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}}; + final int[] colors = new int[] { activatedColor, defaultColor }; + return new ColorStateList(stateSet, colors); + } - updateDisplay(false); - setCurrentView(MONTH_AND_DAY_VIEW); + private int multiplyAlphaComponent(int color, float alphaMod) { + final int srcRgb = color & 0xFFFFFF; + final int srcAlpha = (color >> 24) & 0xFF; + final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f); + return srcRgb | (dstAlpha << 24); } /** + * Listener called when the user selects a day in the day picker view. + */ + private final OnDaySelectedListener mOnDaySelectedListener = new OnDaySelectedListener() { + @Override + public void onDaySelected(DayPickerView view, Calendar day) { + mCurrentDate.setTimeInMillis(day.getTimeInMillis()); + onDateChanged(true, true); + } + }; + + /** + * Listener called when the user selects a year in the year picker view. + */ + private final OnYearSelectedListener mOnYearSelectedListener = new OnYearSelectedListener() { + @Override + public void onYearChanged(YearPickerView view, int year) { + // If the newly selected month / year does not contain the + // currently selected day number, change the selected day number + // to the last day of the selected month or year. + // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 + // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 + final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); + final int month = mCurrentDate.get(Calendar.MONTH); + final int daysInMonth = getDaysInMonth(month, year); + if (day > daysInMonth) { + mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth); + } + + mCurrentDate.set(Calendar.YEAR, year); + onDateChanged(true, true); + + // Automatically switch to day picker. + setCurrentView(VIEW_MONTH_DAY); + } + }; + + /** + * Listener called when the user clicks on a header item. + */ + private final OnClickListener mOnHeaderClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + tryVibrate(); + + switch (v.getId()) { + case R.id.date_picker_header_year: + setCurrentView(VIEW_YEAR); + break; + case R.id.date_picker_header_date: + setCurrentView(VIEW_MONTH_DAY); + break; + } + } + }; + + /** * Gets a calendar for locale bootstrapped with the value of a given calendar. * * @param oldCalendar The old calendar. @@ -277,85 +370,70 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i return result; } - private void updateDisplay(boolean announce) { - if (mDayOfWeekView != null) { - mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, - Locale.getDefault())); + @Override + protected void onLocaleChanged(Locale locale) { + final TextView headerYear = mHeaderYear; + if (headerYear == null) { + // Abort, we haven't initialized yet. This method will get called + // again later after everything has been set up. + return; } - // Compute indices of Month, Day and Year views - final String bestDateTimePattern = - DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd"); - final int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern); + // Update the date formatter. + final String datePattern = DateFormat.getBestDateTimePattern(locale, "EMMMd"); + mMonthDayFormat = new SimpleDateFormat(datePattern, locale); + mYearFormat = new SimpleDateFormat("y", locale); - // Position the Year and MonthAndDay views within the header. - mMonthDayYearLayout.removeAllViews(); - if (viewIndices[YEAR_INDEX] == 0) { - mMonthDayYearLayout.addView(mHeaderYearTextView); - mMonthDayYearLayout.addView(mMonthAndDayLayout); - } else { - mMonthDayYearLayout.addView(mMonthAndDayLayout); - mMonthDayYearLayout.addView(mHeaderYearTextView); - } + // Update the header text. + onCurrentDateChanged(false); + } - // Position Day and Month views within the MonthAndDay view. - mMonthAndDayLayout.removeAllViews(); - if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) { - mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); - mMonthAndDayLayout.addView(mHeaderMonthTextView); - } else { - mMonthAndDayLayout.addView(mHeaderMonthTextView); - mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); + private void onCurrentDateChanged(boolean announce) { + if (mHeaderYear == null) { + // Abort, we haven't initialized yet. This method will get called + // again later after everything has been set up. + return; } - mHeaderMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT, - Locale.getDefault()).toUpperCase(Locale.getDefault())); - mHeaderDayOfMonthTextView.setText(mDayFormat.format(mCurrentDate.getTime())); - mHeaderYearTextView.setText(mYearFormat.format(mCurrentDate.getTime())); + final String year = mYearFormat.format(mCurrentDate.getTime()); + mHeaderYear.setText(year); - // Accessibility. - long millis = mCurrentDate.getTimeInMillis(); - mAnimator.setDateMillis(millis); - int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR; - String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags); - mMonthAndDayLayout.setContentDescription(monthAndDayText); + final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime()); + mHeaderMonthDay.setText(monthDay); + // TODO: This should use live regions. if (announce) { - flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; - String fullDateText = DateUtils.formatDateTime(mContext, millis, flags); + final long millis = mCurrentDate.getTimeInMillis(); + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; + final String fullDateText = DateUtils.formatDateTime(mContext, millis, flags); mAnimator.announceForAccessibility(fullDateText); } } private void setCurrentView(final int viewIndex) { - long millis = mCurrentDate.getTimeInMillis(); - switch (viewIndex) { - case MONTH_AND_DAY_VIEW: - mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); + case VIEW_MONTH_DAY: + mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); + if (mCurrentView != viewIndex) { - mMonthAndDayLayout.setSelected(true); - mHeaderYearTextView.setSelected(false); - mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); + mHeaderMonthDay.setActivated(true); + mHeaderYear.setActivated(false); + mAnimator.setDisplayedChild(VIEW_MONTH_DAY); mCurrentView = viewIndex; } - final int flags = DateUtils.FORMAT_SHOW_DATE; - final String dayString = DateUtils.formatDateTime(mContext, millis, flags); - mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString); mAnimator.announceForAccessibility(mSelectDay); break; - case YEAR_VIEW: - mYearPickerView.onDateChanged(); + case VIEW_YEAR: + mYearPickerView.setDate(mCurrentDate.getTimeInMillis()); + if (mCurrentView != viewIndex) { - mMonthAndDayLayout.setSelected(false); - mHeaderYearTextView.setSelected(true); - mAnimator.setDisplayedChild(YEAR_VIEW); + mHeaderMonthDay.setActivated(false); + mHeaderYear.setActivated(true); + mAnimator.setDisplayedChild(VIEW_YEAR); mCurrentView = viewIndex; } - final CharSequence yearString = mYearFormat.format(millis); - mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString); mAnimator.announceForAccessibility(mSelectYear); break; } @@ -383,20 +461,18 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i } private void onDateChanged(boolean fromUser, boolean callbackToClient) { + final int year = mCurrentDate.get(Calendar.YEAR); + if (callbackToClient && mDateChangedListener != null) { - final int year = mCurrentDate.get(Calendar.YEAR); final int monthOfYear = mCurrentDate.get(Calendar.MONTH); final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH); mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth); } - for (OnDateChangedListener listener : mListeners) { - listener.onDateChanged(); - } - - mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); + mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); + mYearPickerView.setYear(year); - updateDisplay(fromUser); + onCurrentDateChanged(fromUser); if (fromUser) { tryVibrate(); @@ -477,15 +553,12 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i @Override public void setEnabled(boolean enabled) { - mMonthAndDayLayout.setEnabled(enabled); - mHeaderYearTextView.setEnabled(enabled); - mAnimator.setEnabled(enabled); - mIsEnabled = enabled; + mContainer.setEnabled(false); } @Override public boolean isEnabled() { - return mIsEnabled; + return mContainer.isEnabled(); } @Override @@ -516,8 +589,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i @Override public void onConfigurationChanged(Configuration newConfig) { - mYearFormat = new SimpleDateFormat("y", newConfig.locale); - mDayFormat = new SimpleDateFormat("d", newConfig.locale); + setCurrentLocale(newConfig.locale); } @Override @@ -529,9 +601,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i int listPosition = -1; int listPositionOffset = -1; - if (mCurrentView == MONTH_AND_DAY_VIEW) { + if (mCurrentView == VIEW_MONTH_DAY) { listPosition = mDayPickerView.getMostVisiblePosition(); - } else if (mCurrentView == YEAR_VIEW) { + } else if (mCurrentView == VIEW_YEAR) { listPosition = mYearPickerView.getFirstVisiblePosition(); listPositionOffset = mYearPickerView.getFirstPositionOffset(); } @@ -544,20 +616,22 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; + // TODO: Move instance state into DayPickerView, YearPickerView. mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); mCurrentView = ss.getCurrentView(); mMinDate.setTimeInMillis(ss.getMinDate()); mMaxDate.setTimeInMillis(ss.getMaxDate()); - updateDisplay(false); + onCurrentDateChanged(false); setCurrentView(mCurrentView); final int listPosition = ss.getListPosition(); if (listPosition != -1) { - if (mCurrentView == MONTH_AND_DAY_VIEW) { - mDayPickerView.postSetSelection(listPosition); - } else if (mCurrentView == YEAR_VIEW) { - mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset()); + if (mCurrentView == VIEW_MONTH_DAY) { + mDayPickerView.setCurrentItem(listPosition); + } else if (mCurrentView == VIEW_YEAR) { + final int listPositionOffset = ss.getListPositionOffset(); + mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset); } } } @@ -577,28 +651,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i return DatePicker.class.getName(); } - @Override - public void onYearSelected(int year) { - adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year); - mCurrentDate.set(Calendar.YEAR, year); - onDateChanged(true, true); - - // Auto-advance to month and day view. - setCurrentView(MONTH_AND_DAY_VIEW); - } - - // If the newly selected month / year does not contain the currently selected day number, - // change the selected day number to the last day of the selected month or year. - // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 - // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 - private void adjustDayInMonthIfNeeded(int month, int year) { - int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); - int daysInMonth = getDaysInMonth(month, year); - if (day > daysInMonth) { - mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth); - } - } - public static int getDaysInMonth(int month, int year) { switch (month) { case Calendar.JANUARY: @@ -621,43 +673,10 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i } } - @Override - public void registerOnDateChangedListener(OnDateChangedListener listener) { - mListeners.add(listener); - } - - @Override - public Calendar getSelectedDay() { - return mCurrentDate; - } - - @Override - public void tryVibrate() { + private void tryVibrate() { mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE); } - @Override - public void onClick(View v) { - tryVibrate(); - if (v.getId() == R.id.date_picker_year) { - setCurrentView(YEAR_VIEW); - } else if (v.getId() == R.id.date_picker_month_and_day_layout) { - setCurrentView(MONTH_AND_DAY_VIEW); - } - } - - /** - * Listener called when the user selects a day in the day picker view. - */ - private final DayPickerView.OnDaySelectedListener - mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() { - @Override - public void onDaySelected(DayPickerView view, Calendar day) { - mCurrentDate.setTimeInMillis(day.getTimeInMillis()); - onDateChanged(true, true); - } - }; - /** * Class for managing state storing/restoring. */ diff --git a/core/java/android/widget/DayPickerAdapter.java b/core/java/android/widget/DayPickerAdapter.java new file mode 100644 index 0000000..4f9f09e --- /dev/null +++ b/core/java/android/widget/DayPickerAdapter.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2015 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 android.widget; + +import com.android.internal.widget.PagerAdapter; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SimpleMonthView.OnDayClickListener; + +import java.util.Calendar; + +/** + * An adapter for a list of {@link android.widget.SimpleMonthView} items. + */ +class DayPickerAdapter extends PagerAdapter { + private static final int MONTHS_IN_YEAR = 12; + + private final Calendar mMinDate = Calendar.getInstance(); + private final Calendar mMaxDate = Calendar.getInstance(); + + private final SparseArray<SimpleMonthView> mItems = new SparseArray<>(); + + private Calendar mSelectedDay = Calendar.getInstance(); + + private int mMonthTextAppearance; + private int mDayOfWeekTextAppearance; + private int mDayTextAppearance; + + private ColorStateList mCalendarTextColor; + private ColorStateList mDaySelectorColor; + private ColorStateList mDayHighlightColor; + + private OnDaySelectedListener mOnDaySelectedListener; + + private int mFirstDayOfWeek; + + public DayPickerAdapter(Context context) { + final TypedArray ta = context.obtainStyledAttributes(new int[] { + com.android.internal.R.attr.colorControlHighlight}); + mDayHighlightColor = ta.getColorStateList(0); + ta.recycle(); + } + + public void setRange(Calendar min, Calendar max) { + mMinDate.setTimeInMillis(min.getTimeInMillis()); + mMaxDate.setTimeInMillis(max.getTimeInMillis()); + + // Positions are now invalid, clear everything and start over. + notifyDataSetChanged(); + } + + /** + * Sets the first day of the week. + * + * @param weekStart which day the week should start on, valid values are + * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} + */ + public void setFirstDayOfWeek(int weekStart) { + mFirstDayOfWeek = weekStart; + + // Update displayed views. + final int count = mItems.size(); + for (int i = 0; i < count; i++) { + final SimpleMonthView monthView = mItems.valueAt(i); + monthView.setFirstDayOfWeek(weekStart); + } + } + + public int getFirstDayOfWeek() { + return mFirstDayOfWeek; + } + + /** + * Sets the selected day. + * + * @param day the selected day + */ + public void setSelectedDay(Calendar day) { + final int oldPosition = getPositionForDay(mSelectedDay); + final int newPosition = getPositionForDay(day); + + // Clear the old position if necessary. + if (oldPosition != newPosition) { + final SimpleMonthView oldMonthView = mItems.get(oldPosition, null); + if (oldMonthView != null) { + oldMonthView.setSelectedDay(-1); + } + } + + // Set the new position. + final SimpleMonthView newMonthView = mItems.get(newPosition, null); + if (newMonthView != null) { + final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH); + newMonthView.setSelectedDay(dayOfMonth); + } + + mSelectedDay = day; + } + + /** + * Sets the listener to call when the user selects a day. + * + * @param listener The listener to call. + */ + public void setOnDaySelectedListener(OnDaySelectedListener listener) { + mOnDaySelectedListener = listener; + } + + void setCalendarTextColor(ColorStateList calendarTextColor) { + mCalendarTextColor = calendarTextColor; + } + + void setDaySelectorColor(ColorStateList selectorColor) { + mDaySelectorColor = selectorColor; + } + + void setMonthTextAppearance(int resId) { + mMonthTextAppearance = resId; + } + + void setDayOfWeekTextAppearance(int resId) { + mDayOfWeekTextAppearance = resId; + } + + int getDayOfWeekTextAppearance() { + return mDayOfWeekTextAppearance; + } + + void setDayTextAppearance(int resId) { + mDayTextAppearance = resId; + } + + int getDayTextAppearance() { + return mDayTextAppearance; + } + + @Override + public int getCount() { + final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR); + final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH); + return diffMonth + MONTHS_IN_YEAR * diffYear + 1; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + private int getMonthForPosition(int position) { + return position % MONTHS_IN_YEAR + mMinDate.get(Calendar.MONTH); + } + + private int getYearForPosition(int position) { + return position / MONTHS_IN_YEAR + mMinDate.get(Calendar.YEAR); + } + + private int getPositionForDay(Calendar day) { + final int yearOffset = (day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR)); + final int monthOffset = (day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH)); + return yearOffset * MONTHS_IN_YEAR + monthOffset; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + final SimpleMonthView v = new SimpleMonthView(container.getContext()); + v.setOnDayClickListener(mOnDayClickListener); + v.setMonthTextAppearance(mMonthTextAppearance); + v.setDayOfWeekTextAppearance(mDayOfWeekTextAppearance); + v.setDayTextAppearance(mDayTextAppearance); + + if (mDaySelectorColor != null) { + v.setDaySelectorColor(mDaySelectorColor); + } + + if (mDayHighlightColor != null) { + v.setDayHighlightColor(mDayHighlightColor); + } + + if (mCalendarTextColor != null) { + v.setMonthTextColor(mCalendarTextColor); + v.setDayOfWeekTextColor(mCalendarTextColor); + v.setDayTextColor(mCalendarTextColor); + } + + final int month = getMonthForPosition(position); + final int year = getYearForPosition(position); + + final int selectedDay; + if (mSelectedDay.get(Calendar.MONTH) == month) { + selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH); + } else { + selectedDay = -1; + } + + final int enabledDayRangeStart; + if (mMinDate.get(Calendar.MONTH) == month && mMinDate.get(Calendar.YEAR) == year) { + enabledDayRangeStart = mMinDate.get(Calendar.DAY_OF_MONTH); + } else { + enabledDayRangeStart = 1; + } + + final int enabledDayRangeEnd; + if (mMaxDate.get(Calendar.MONTH) == month && mMaxDate.get(Calendar.YEAR) == year) { + enabledDayRangeEnd = mMaxDate.get(Calendar.DAY_OF_MONTH); + } else { + enabledDayRangeEnd = 31; + } + + v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek, + enabledDayRangeStart, enabledDayRangeEnd); + + mItems.put(position, v); + + container.addView(v); + + return v; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView(mItems.get(position)); + + mItems.remove(position); + } + + @Override + public int getItemPosition(Object object) { + final int index = mItems.indexOfValue((SimpleMonthView) object); + if (index < 0) { + return mItems.keyAt(index); + } + return -1; + } + + @Override + public CharSequence getPageTitle(int position) { + final SimpleMonthView v = mItems.get(position); + if (v != null) { + return v.getTitle(); + } + return null; + } + + private boolean isCalendarInRange(Calendar value) { + return value.compareTo(mMinDate) >= 0 && value.compareTo(mMaxDate) <= 0; + } + + private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() { + @Override + public void onDayClick(SimpleMonthView view, Calendar day) { + if (day != null && isCalendarInRange(day)) { + setSelectedDay(day); + + if (mOnDaySelectedListener != null) { + mOnDaySelectedListener.onDaySelected(DayPickerAdapter.this, day); + } + } + } + }; + + public interface OnDaySelectedListener { + public void onDaySelected(DayPickerAdapter view, Calendar day); + } +} 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); - } - } - }; } diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java deleted file mode 100644 index c807d56..0000000 --- a/core/java/android/widget/SimpleMonthAdapter.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2014 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 android.widget; - -import com.android.internal.R; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.view.View; -import android.view.ViewGroup; -import android.widget.SimpleMonthView.OnDayClickListener; - -import java.util.Calendar; - -/** - * An adapter for a list of {@link android.widget.SimpleMonthView} items. - */ -class SimpleMonthAdapter extends BaseAdapter { - private final Calendar mMinDate = Calendar.getInstance(); - private final Calendar mMaxDate = Calendar.getInstance(); - - private final Context mContext; - - private Calendar mSelectedDay = Calendar.getInstance(); - private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK); - private ColorStateList mCalendarDayBackgroundColor = ColorStateList.valueOf(Color.MAGENTA); - private OnDaySelectedListener mOnDaySelectedListener; - - private int mFirstDayOfWeek; - - public SimpleMonthAdapter(Context context) { - mContext = context; - } - - public void setRange(Calendar min, Calendar max) { - mMinDate.setTimeInMillis(min.getTimeInMillis()); - mMaxDate.setTimeInMillis(max.getTimeInMillis()); - - notifyDataSetInvalidated(); - } - - public void setFirstDayOfWeek(int firstDayOfWeek) { - mFirstDayOfWeek = firstDayOfWeek; - - notifyDataSetInvalidated(); - } - - public int getFirstDayOfWeek() { - return mFirstDayOfWeek; - } - - /** - * Updates the selected day and related parameters. - * - * @param day The day to highlight - */ - public void setSelectedDay(Calendar day) { - mSelectedDay = day; - - notifyDataSetChanged(); - } - - /** - * Sets the listener to call when the user selects a day. - * - * @param listener The listener to call. - */ - public void setOnDaySelectedListener(OnDaySelectedListener listener) { - mOnDaySelectedListener = listener; - } - - void setCalendarTextColor(ColorStateList colors) { - mCalendarTextColors = colors; - } - - void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) { - mCalendarDayBackgroundColor = dayBackgroundColor; - } - - /** - * Sets the text color, size, style, hint color, and highlight color from - * the specified TextAppearance resource. This is mostly copied from - * {@link TextView#setTextAppearance(Context, int)}. - */ - void setCalendarTextAppearance(int resId) { - final TypedArray a = mContext.obtainStyledAttributes(resId, R.styleable.TextAppearance); - - final ColorStateList textColor = a.getColorStateList(R.styleable.TextAppearance_textColor); - if (textColor != null) { - mCalendarTextColors = textColor; - } - - // TODO: Support font size, etc. - - a.recycle(); - } - - @Override - public int getCount() { - final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR); - final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH); - return diffMonth + 12 * diffYear + 1; - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @SuppressWarnings("unchecked") - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final SimpleMonthView v; - if (convertView != null) { - v = (SimpleMonthView) convertView; - } else { - v = new SimpleMonthView(mContext); - - // Set up the new view - final AbsListView.LayoutParams params = new AbsListView.LayoutParams( - AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT); - v.setLayoutParams(params); - v.setClickable(true); - v.setOnDayClickListener(mOnDayClickListener); - - v.setMonthTextColor(mCalendarTextColors); - v.setDayOfWeekTextColor(mCalendarTextColors); - v.setDayTextColor(mCalendarTextColors); - - v.setDayBackgroundColor(mCalendarDayBackgroundColor); - } - - final int minMonth = mMinDate.get(Calendar.MONTH); - final int minYear = mMinDate.get(Calendar.YEAR); - final int currentMonth = position + minMonth; - final int month = currentMonth % 12; - final int year = currentMonth / 12 + minYear; - final int selectedDay; - if (isSelectedDayInMonth(year, month)) { - selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH); - } else { - selectedDay = -1; - } - - // Invokes requestLayout() to ensure that the recycled view is set with the appropriate - // height/number of weeks before being displayed. - v.reuse(); - - final int enabledDayRangeStart; - if (minMonth == month && minYear == year) { - enabledDayRangeStart = mMinDate.get(Calendar.DAY_OF_MONTH); - } else { - enabledDayRangeStart = 1; - } - - final int enabledDayRangeEnd; - if (mMaxDate.get(Calendar.MONTH) == month && mMaxDate.get(Calendar.YEAR) == year) { - enabledDayRangeEnd = mMaxDate.get(Calendar.DAY_OF_MONTH); - } else { - enabledDayRangeEnd = 31; - } - - v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek, - enabledDayRangeStart, enabledDayRangeEnd); - v.invalidate(); - - return v; - } - - private boolean isSelectedDayInMonth(int year, int month) { - return mSelectedDay.get(Calendar.YEAR) == year && mSelectedDay.get(Calendar.MONTH) == month; - } - - private boolean isCalendarInRange(Calendar value) { - return value.compareTo(mMinDate) >= 0 && value.compareTo(mMaxDate) <= 0; - } - - private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() { - @Override - public void onDayClick(SimpleMonthView view, Calendar day) { - if (day != null && isCalendarInRange(day)) { - setSelectedDay(day); - - if (mOnDaySelectedListener != null) { - mOnDaySelectedListener.onDaySelected(SimpleMonthAdapter.this, day); - } - } - } - }; - - public interface OnDaySelectedListener { - public void onDaySelected(SimpleMonthAdapter view, Calendar day); - } -} diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index 58ad515..4e5a39a 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -18,8 +18,8 @@ package android.widget; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Align; @@ -29,8 +29,6 @@ import android.graphics.Typeface; import android.os.Bundle; import android.text.TextPaint; import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.format.Time; import android.util.AttributeSet; import android.util.IntArray; import android.util.StateSet; @@ -38,13 +36,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.internal.R; import com.android.internal.widget.ExploreByTouchHelper; import java.text.SimpleDateFormat; import java.util.Calendar; -import java.util.Formatter; import java.util.Locale; /** @@ -52,93 +50,80 @@ import java.util.Locale; * within the specified month. */ class SimpleMonthView extends View { - private static final int MIN_ROW_HEIGHT = 10; + private static final int DAYS_IN_WEEK = 7; + private static final int MAX_WEEKS_IN_MONTH = 6; private static final int DEFAULT_SELECTED_DAY = -1; private static final int DEFAULT_WEEK_START = Calendar.SUNDAY; - private static final int DEFAULT_NUM_DAYS = 7; - private static final int DEFAULT_NUM_ROWS = 6; - private static final int MAX_NUM_ROWS = 6; - private final Formatter mFormatter; - private final StringBuilder mStringBuilder; - - private final int mMonthTextSize; - private final int mDayOfWeekTextSize; - private final int mDayTextSize; - - /** Height of the header containing the month and day of week labels. */ - private final int mMonthHeaderHeight; + private static final String DEFAULT_TITLE_FORMAT = "MMMMy"; + private static final String DAY_OF_WEEK_FORMAT = "EEEEE"; private final TextPaint mMonthPaint = new TextPaint(); private final TextPaint mDayOfWeekPaint = new TextPaint(); private final TextPaint mDayPaint = new TextPaint(); + private final Paint mDaySelectorPaint = new Paint(); + private final Paint mDayHighlightPaint = new Paint(); - private final Paint mDayBackgroundPaint = new Paint(); + private final Calendar mCalendar = Calendar.getInstance(); + private final Calendar mDayLabelCalendar = Calendar.getInstance(); - /** Single-letter (when available) formatter for the day of week label. */ - private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault()); + private final MonthViewTouchHelper mTouchHelper; - // affects the padding on the sides of this view - private int mPadding = 0; + private final SimpleDateFormat mTitleFormatter; + private final SimpleDateFormat mDayOfWeekFormatter; - private String mDayOfWeekTypeface; - private String mMonthTypeface; + private CharSequence mTitle; private int mMonth; private int mYear; - // Quick reference to the width of this view, matches parent - private int mWidth; - - // The height this view should draw at in pixels, set by height param - private final int mRowHeight; + private int mPaddedWidth; + private int mPaddedHeight; - // If this view contains the today - private boolean mHasToday = false; + private final int mMonthHeight; + private final int mDayOfWeekHeight; + private final int mDayHeight; + private final int mCellWidth; + private final int mDaySelectorRadius; - // Which day is selected [0-6] or -1 if no day is selected + /** The day of month for the selected day, or -1 if no day is selected. */ private int mActivatedDay = -1; - // Which day is today [0-6] or -1 if no day is today + /** + * The day of month for today, or -1 if the today is not in the current + * month. + */ private int mToday = DEFAULT_SELECTED_DAY; - // Which day of the week to start on [0-6] + /** The first day of the week (ex. Calendar.SUNDAY). */ private int mWeekStart = DEFAULT_WEEK_START; - // How many days to display - private int mNumDays = DEFAULT_NUM_DAYS; - - // The number of days + a spot for week number if it is displayed - private int mNumCells = mNumDays; + /** The number of days (ex. 28) in the current month. */ + private int mDaysInMonth; - private int mDayOfWeekStart = 0; + /** + * The day of week (ex. Calendar.SUNDAY) for the first day of the current + * month. + */ + private int mDayOfWeekStart; - // First enabled day + /** The day of month for the first (inclusive) enabled day. */ private int mEnabledDayStart = 1; - // Last enabled day + /** The day of month for the last (inclusive) enabled day. */ private int mEnabledDayEnd = 31; - private final Calendar mCalendar = Calendar.getInstance(); - private final Calendar mDayLabelCalendar = Calendar.getInstance(); - - private final MonthViewTouchHelper mTouchHelper; - - private int mNumRows = DEFAULT_NUM_ROWS; + /** The number of week rows needed to display the current month. */ + private int mNumWeeks = MAX_WEEKS_IN_MONTH; - // Optional listener for handling day click actions + /** Optional listener for handling day click actions. */ private OnDayClickListener mOnDayClickListener; - // Whether to prevent setting the accessibility delegate - private boolean mLockAccessibilityDelegate; - - private int mNormalTextColor; - private int mDisabledTextColor; - private int mSelectedDayColor; - private ColorStateList mDayTextColor; + private int mTouchedDay = -1; + public SimpleMonthView(Context context) { this(context, null); } @@ -155,64 +140,123 @@ class SimpleMonthView extends View { super(context, attrs, defStyleAttr, defStyleRes); final Resources res = context.getResources(); - mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface); - mMonthTypeface = res.getString(R.string.sans_serif); - - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - - mDayTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size); - mMonthTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size); - mDayOfWeekTextSize = res.getDimensionPixelSize( - R.dimen.datepicker_month_day_label_text_size); - mMonthHeaderHeight = res.getDimensionPixelOffset( - R.dimen.datepicker_month_list_item_header_height); - - mRowHeight = Math.max(MIN_ROW_HEIGHT, - (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height) - - mMonthHeaderHeight) / MAX_NUM_ROWS); + mMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); + mDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); + mDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); + mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); + mDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius); // Set up accessibility components. mTouchHelper = new MonthViewTouchHelper(this); setAccessibilityDelegate(mTouchHelper); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - mLockAccessibilityDelegate = true; - initPaints(); + final Locale locale = res.getConfiguration().locale; + final String titleFormat = DateFormat.getBestDateTimePattern(locale, DEFAULT_TITLE_FORMAT); + mTitleFormatter = new SimpleDateFormat(titleFormat, locale); + mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale); + + setClickable(true); + initPaints(res); + } + + /** + * Applies the specified text appearance resource to a paint, returning the + * text color if one is set in the text appearance. + * + * @param p the paint to modify + * @param resId the resource ID of the text appearance + * @return the text color, if available + */ + private ColorStateList applyTextAppearance(Paint p, int resId) { + final TypedArray ta = mContext.obtainStyledAttributes(null, + R.styleable.TextAppearance, 0, resId); + + final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily); + if (fontFamily != null) { + p.setTypeface(Typeface.create(fontFamily, 0)); + } + + p.setTextSize(ta.getDimensionPixelSize( + R.styleable.TextAppearance_textSize, (int) p.getTextSize())); + + final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor); + if (textColor != null) { + final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0); + p.setColor(enabledColor); + } + + ta.recycle(); + + return textColor; + } + + public void setMonthTextAppearance(int resId) { + applyTextAppearance(mMonthPaint, resId); + invalidate(); + } + + public void setDayOfWeekTextAppearance(int resId) { + applyTextAppearance(mDayOfWeekPaint, resId); + invalidate(); + } + + public void setDayTextAppearance(int resId) { + final ColorStateList textColor = applyTextAppearance(mDayPaint, resId); + if (textColor != null) { + mDayTextColor = textColor; + } + + invalidate(); + } + + public CharSequence getTitle() { + if (mTitle == null) { + mTitle = mTitleFormatter.format(mCalendar.getTime()); + } + return mTitle; } /** * Sets up the text and style properties for painting. */ - private void initPaints() { + private void initPaints(Resources res) { + final String monthTypeface = res.getString(R.string.date_picker_month_typeface); + final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface); + final String dayTypeface = res.getString(R.string.date_picker_day_typeface); + + final int monthTextSize = res.getDimensionPixelSize( + R.dimen.date_picker_month_text_size); + final int dayOfWeekTextSize = res.getDimensionPixelSize( + R.dimen.date_picker_day_of_week_text_size); + final int dayTextSize = res.getDimensionPixelSize( + R.dimen.date_picker_day_text_size); + mMonthPaint.setAntiAlias(true); - mMonthPaint.setTextSize(mMonthTextSize); - mMonthPaint.setTypeface(Typeface.create(mMonthTypeface, Typeface.BOLD)); + mMonthPaint.setTextSize(monthTextSize); + mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0)); mMonthPaint.setTextAlign(Align.CENTER); mMonthPaint.setStyle(Style.FILL); mDayOfWeekPaint.setAntiAlias(true); - mDayOfWeekPaint.setTextSize(mDayOfWeekTextSize); - mDayOfWeekPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.BOLD)); + mDayOfWeekPaint.setTextSize(dayOfWeekTextSize); + mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0)); mDayOfWeekPaint.setTextAlign(Align.CENTER); mDayOfWeekPaint.setStyle(Style.FILL); - mDayBackgroundPaint.setAntiAlias(true); - mDayBackgroundPaint.setStyle(Style.FILL); + mDaySelectorPaint.setAntiAlias(true); + mDaySelectorPaint.setStyle(Style.FILL); + + mDayHighlightPaint.setAntiAlias(true); + mDayHighlightPaint.setStyle(Style.FILL); mDayPaint.setAntiAlias(true); - mDayPaint.setTextSize(mDayTextSize); + mDayPaint.setTextSize(dayTextSize); + mDayPaint.setTypeface(Typeface.create(dayTypeface, 0)); mDayPaint.setTextAlign(Align.CENTER); mDayPaint.setStyle(Style.FILL); } - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale); - } - void setMonthTextColor(ColorStateList monthTextColor) { final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0); mMonthPaint.setColor(enabledColor); @@ -230,20 +274,18 @@ class SimpleMonthView extends View { invalidate(); } - void setDayBackgroundColor(ColorStateList dayBackgroundColor) { + void setDaySelectorColor(ColorStateList dayBackgroundColor) { final int activatedColor = dayBackgroundColor.getColorForState( StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); - mDayBackgroundPaint.setColor(activatedColor); + mDaySelectorPaint.setColor(activatedColor); invalidate(); } - @Override - public void setAccessibilityDelegate(AccessibilityDelegate delegate) { - // Workaround for a JB MR1 issue where accessibility delegates on - // top-level ListView items are overwritten. - if (!mLockAccessibilityDelegate) { - super.setAccessibilityDelegate(delegate); - } + void setDayHighlightColor(ColorStateList dayHighlightColor) { + final int pressedColor = dayHighlightColor.getColorForState( + StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0); + mDayHighlightPaint.setColor(pressedColor); + invalidate(); } public void setOnDayClickListener(OnDayClickListener listener) { @@ -253,30 +295,124 @@ class SimpleMonthView extends View { @Override public boolean dispatchHoverEvent(MotionEvent event) { // First right-of-refusal goes the touch exploration helper. - if (mTouchHelper.dispatchHoverEvent(event)) { - return true; - } - return super.dispatchHoverEvent(event); + return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { - case MotionEvent.ACTION_UP: - final int day = getDayFromLocation(event.getX(), event.getY()); - if (day >= 0) { - onDayClick(day); + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + final int touchedDay = getDayAtLocation(event.getX(), event.getY()); + if (mTouchedDay != touchedDay) { + mTouchedDay = touchedDay; + invalidate(); } break; + + case MotionEvent.ACTION_UP: + final int clickedDay = getDayAtLocation(event.getX(), event.getY()); + onDayClicked(clickedDay); + // Fall through. + case MotionEvent.ACTION_CANCEL: + // Reset touched day on stream end. + mTouchedDay = -1; + invalidate(); + break; } return true; } @Override protected void onDraw(Canvas canvas) { - drawMonthTitle(canvas); - drawWeekDayLabels(canvas); + final int paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + canvas.translate(paddingLeft, paddingTop); + + drawMonth(canvas); + drawDaysOfWeek(canvas); drawDays(canvas); + + canvas.translate(-paddingLeft, -paddingTop); + } + + private void drawMonth(Canvas canvas) { + final float x = mPaddedWidth / 2f; + + // Vertically centered within the month header height. + final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent(); + final float y = (mMonthHeight - lineHeight) / 2f; + + canvas.drawText(getTitle().toString(), x, y, mMonthPaint); + } + + private void drawDaysOfWeek(Canvas canvas) { + final float cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2); + + // Vertically centered within the cell height. + final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); + final float y = mMonthHeight + (mDayOfWeekHeight - lineHeight) / 2f; + + for (int i = 0; i < DAYS_IN_WEEK; i++) { + final int calendarDay = (i + mWeekStart) % DAYS_IN_WEEK; + mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); + + final String dayLabel = mDayOfWeekFormatter.format(mDayLabelCalendar.getTime()); + final float x = (2 * i + 1) * cellWidthHalf; + canvas.drawText(dayLabel, x, y, mDayOfWeekPaint); + } + } + + /** + * Draws the month days. + */ + private void drawDays(Canvas canvas) { + final int cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2); + + // Vertically centered within the cell height. + final float halfLineHeight = (mDayPaint.ascent() + mDayPaint.descent()) / 2; + float centerY = mMonthHeight + mDayOfWeekHeight + mDayHeight / 2f; + + for (int day = 1, j = findDayOffset(); day <= mDaysInMonth; day++) { + final int x = (2 * j + 1) * cellWidthHalf; + int stateMask = 0; + + if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { + stateMask |= StateSet.VIEW_STATE_ENABLED; + } + + final boolean isDayActivated = mActivatedDay == day; + if (isDayActivated) { + stateMask |= StateSet.VIEW_STATE_ACTIVATED; + + // Adjust the circle to be centered on the row. + canvas.drawCircle(x, centerY, mDaySelectorRadius, mDaySelectorPaint); + } else if (mTouchedDay == day) { + stateMask |= StateSet.VIEW_STATE_PRESSED; + + // Adjust the circle to be centered on the row. + canvas.drawCircle(x, centerY, mDaySelectorRadius, mDayHighlightPaint); + } + + final boolean isDayToday = mToday == day; + final int dayTextColor; + if (isDayToday && !isDayActivated) { + dayTextColor = mDaySelectorPaint.getColor(); + } else { + final int[] stateSet = StateSet.get(stateMask); + dayTextColor = mDayTextColor.getColorForState(stateSet, 0); + } + mDayPaint.setColor(dayTextColor); + + canvas.drawText("" + day, x, centerY - halfLineHeight, mDayPaint); + + j++; + + if (j == DAYS_IN_WEEK) { + j = 0; + centerY += mDayHeight; + } + } } private static boolean isValidDayOfWeek(int day) { @@ -288,18 +424,52 @@ class SimpleMonthView extends View { } /** - * Sets all the parameters for displaying this week. Parameters have a default value and - * will only update if a new value is included, except for focus month, which will always - * default to no focus month if no value is passed in. The only required parameter is the - * week start. + * Sets the selected day. * - * @param selectedDay the selected day of the month, or -1 for no selection. - * @param month the month. - * @param year the year. - * @param weekStart which day the week should start on. {@link Calendar#SUNDAY} through - * {@link Calendar#SATURDAY}. - * @param enabledDayStart the first enabled day. - * @param enabledDayEnd the last enabled day. + * @param dayOfMonth the selected day of the month, or {@code -1} to clear + * the selection + */ + public void setSelectedDay(int dayOfMonth) { + mActivatedDay = dayOfMonth; + + // Invalidate cached accessibility information. + mTouchHelper.invalidateRoot(); + invalidate(); + } + + /** + * Sets the first day of the week. + * + * @param weekStart which day the week should start on, valid values are + * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} + */ + public void setFirstDayOfWeek(int weekStart) { + if (isValidDayOfWeek(weekStart)) { + mWeekStart = weekStart; + } else { + mWeekStart = mCalendar.getFirstDayOfWeek(); + } + + // Invalidate cached accessibility information. + mTouchHelper.invalidateRoot(); + invalidate(); + } + + /** + * Sets all the parameters for displaying this week. + * <p> + * Parameters have a default value and will only update if a new value is + * included, except for focus month, which will always default to no focus + * month if no value is passed in. The only required parameter is the week + * start. + * + * @param selectedDay the selected day of the month, or -1 for no selection + * @param month the month + * @param year the year + * @param weekStart which day the week should start on, valid values are + * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} + * @param enabledDayStart the first enabled day + * @param enabledDayEnd the last enabled day */ void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart, int enabledDayEnd) { @@ -310,12 +480,6 @@ class SimpleMonthView extends View { } mYear = year; - // Figure out what day today is - final Time today = new Time(Time.getCurrentTimezone()); - today.setToNow(); - mHasToday = false; - mToday = -1; - mCalendar.set(Calendar.MONTH, mMonth); mCalendar.set(Calendar.YEAR, mYear); mCalendar.set(Calendar.DAY_OF_MONTH, 1); @@ -334,15 +498,20 @@ class SimpleMonthView extends View { mEnabledDayEnd = enabledDayEnd; } - mNumCells = getDaysInMonth(mMonth, mYear); - for (int i = 0; i < mNumCells; i++) { + // Figure out what day today is. + final Calendar today = Calendar.getInstance(); + mToday = -1; + mDaysInMonth = getDaysInMonth(mMonth, mYear); + for (int i = 0; i < mDaysInMonth; i++) { final int day = i + 1; if (sameDay(day, today)) { - mHasToday = true; mToday = day; } } - mNumRows = calculateNumRows(); + mNumWeeks = calculateNumRows(); + + // Invalidate the old title. + mTitle = null; // Invalidate cached accessibility information. mTouchHelper.invalidateRoot(); @@ -371,154 +540,118 @@ class SimpleMonthView extends View { } public void reuse() { - mNumRows = DEFAULT_NUM_ROWS; + mNumWeeks = MAX_WEEKS_IN_MONTH; requestLayout(); } private int calculateNumRows() { - int offset = findDayOffset(); - int dividend = (offset + mNumCells) / mNumDays; - int remainder = (offset + mNumCells) % mNumDays; - return (dividend + (remainder > 0 ? 1 : 0)); + final int offset = findDayOffset(); + final int dividend = (offset + mDaysInMonth) / DAYS_IN_WEEK; + final int remainder = (offset + mDaysInMonth) % DAYS_IN_WEEK; + return dividend + (remainder > 0 ? 1 : 0); } - private boolean sameDay(int day, Time today) { - return mYear == today.year && - mMonth == today.month && - day == today.monthDay; + private boolean sameDay(int day, Calendar today) { + return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH) + && day == today.get(Calendar.DAY_OF_MONTH); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows - + mMonthHeaderHeight); + final int preferredHeight = mDayHeight * mNumWeeks + mDayOfWeekHeight + mMonthHeight + + getPaddingTop() + getPaddingBottom(); + final int preferredWidth = mCellWidth * DAYS_IN_WEEK + + getPaddingStart() + getPaddingEnd(); + final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); + final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec); + setMeasuredDimension(resolvedWidth, resolvedHeight); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mWidth = w; + mPaddedWidth = w - getPaddingLeft() - getPaddingRight(); + mPaddedHeight = w - getPaddingTop() - getPaddingBottom(); // Invalidate cached accessibility information. mTouchHelper.invalidateRoot(); } - private String getMonthAndYearString() { - int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_NO_MONTH_DAY; - mStringBuilder.setLength(0); - long millis = mCalendar.getTimeInMillis(); - return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags, - Time.getCurrentTimezone()).toString(); - } - - private void drawMonthTitle(Canvas canvas) { - final float x = (mWidth + 2 * mPadding) / 2f; - - // Centered on the upper half of the month header. - final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent(); - final float y = mMonthHeaderHeight * 0.25f - lineHeight / 2f; - - canvas.drawText(getMonthAndYearString(), x, y, mMonthPaint); - } - - private void drawWeekDayLabels(Canvas canvas) { - final float dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); - - // Centered on the lower half of the month header. - final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); - final float y = mMonthHeaderHeight * 0.75f - lineHeight / 2f; - - for (int i = 0; i < mNumDays; i++) { - final int calendarDay = (i + mWeekStart) % mNumDays; - mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); - - final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime()); - final float x = (2 * i + 1) * dayWidthHalf + mPadding; - canvas.drawText(dayLabel, x, y, mDayOfWeekPaint); + private int findDayOffset() { + final int offset = mDayOfWeekStart - mWeekStart; + if (mDayOfWeekStart < mWeekStart) { + return offset + DAYS_IN_WEEK; } + return offset; } /** - * Draws the month days. + * Calculates the day of the month at the specified touch position. Returns + * the day of the month or -1 if the position wasn't in a valid day. + * + * @param x the x position of the touch event + * @param y the y position of the touch event + * @return the day of the month at (x, y) or -1 if the position wasn't in a + * valid day */ - private void drawDays(Canvas canvas) { - final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); - - // Centered within the row. - final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); - float y = mMonthHeaderHeight + (mRowHeight - lineHeight) / 2f; - - for (int day = 1, j = findDayOffset(); day <= mNumCells; day++) { - final int x = (2 * j + 1) * dayWidthHalf + mPadding; - int stateMask = 0; - - if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { - stateMask |= StateSet.VIEW_STATE_ENABLED; - } - - if (mActivatedDay == day) { - stateMask |= StateSet.VIEW_STATE_ACTIVATED; - - // Adjust the circle to be centered the row. - final float rowCenterY = y + lineHeight / 2; - canvas.drawCircle(x, rowCenterY, mRowHeight / 2, - mDayBackgroundPaint); - } - - final int[] stateSet = StateSet.get(stateMask); - final int dayTextColor = mDayTextColor.getColorForState(stateSet, 0); - mDayPaint.setColor(dayTextColor); - - final boolean isDayToday = mHasToday && mToday == day; - mDayPaint.setFakeBoldText(isDayToday); - - canvas.drawText(String.format("%d", day), x, y, mDayPaint); + private int getDayAtLocation(float x, float y) { + final int paddedX = (int) (x - getPaddingLeft() + 0.5f); + if (paddedX < 0 || paddedX >= mPaddedWidth) { + return -1; + } - j++; + final int headerHeight = mMonthHeight + mDayOfWeekHeight; + final int paddedY = (int) (y - getPaddingTop() + 0.5f); + if (paddedY < headerHeight || paddedY >= mPaddedHeight) { + return -1; + } - if (j == mNumDays) { - j = 0; - y += mRowHeight; - } + final int row = (paddedY - headerHeight) / mDayHeight; + final int col = (paddedX * DAYS_IN_WEEK) / mPaddedWidth; + final int index = col + row * DAYS_IN_WEEK; + final int day = index + 1 - findDayOffset(); + if (day < 1 || day > mDaysInMonth) { + return -1; } - } - private int findDayOffset() { - return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart) - - mWeekStart; + return day; } /** - * Calculates the day that the given x position is in, accounting for week - * number. Returns the day or -1 if the position wasn't in a day. + * Calculates the bounds of the specified day. * - * @param x The x position of the touch event - * @return The day number, or -1 if the position wasn't in a day + * @param day the day of the month + * @param outBounds the rect to populate with bounds */ - private int getDayFromLocation(float x, float y) { - int dayStart = mPadding; - if (x < dayStart || x > mWidth - mPadding) { - return -1; + private boolean getBoundsForDay(int day, Rect outBounds) { + if (day < 1 || day > mDaysInMonth) { + return false; } - // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels - int row = (int) (y - mMonthHeaderHeight) / mRowHeight; - int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); - int day = column - findDayOffset() + 1; - day += row * mNumDays; - if (day < 1 || day > mNumCells) { - return -1; - } - return day; + final int index = day - 1 + findDayOffset(); + final int row = index / DAYS_IN_WEEK; + final int col = index % DAYS_IN_WEEK; + + final int headerHeight = mMonthHeight + mDayOfWeekHeight; + final int paddedY = row * mDayHeight + headerHeight; + final int paddedX = col * mPaddedWidth; + + final int y = paddedY + getPaddingTop(); + final int x = paddedX + getPaddingLeft(); + + final int cellHeight = mDayHeight; + final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; + outBounds.set(x, y, (x + cellWidth), (y + cellHeight)); + + return true; } /** * Called when the user clicks on a day. Handles callbacks to the * {@link OnDayClickListener} if one is set. * - * @param day The day that was clicked + * @param day the day that was clicked */ - private void onDayClick(int day) { + private void onDayClicked(int day) { if (mOnDayClickListener != null) { Calendar date = Calendar.getInstance(); date.set(mYear, mMonth, day); @@ -530,44 +663,6 @@ class SimpleMonthView extends View { } /** - * @return The date that has accessibility focus, or {@code null} if no date - * has focus - */ - Calendar getAccessibilityFocus() { - final int day = mTouchHelper.getFocusedVirtualView(); - Calendar date = null; - if (day >= 0) { - date = Calendar.getInstance(); - date.set(mYear, mMonth, day); - } - return date; - } - - /** - * Clears accessibility focus within the view. No-op if the view does not - * contain accessibility focus. - */ - public void clearAccessibilityFocus() { - mTouchHelper.clearFocusedVirtualView(); - } - - /** - * Attempts to restore accessibility focus to the specified date. - * - * @param day The date which should receive focus - * @return {@code false} if the date is not valid for this month view, or - * {@code true} if the date received focus - */ - boolean restoreAccessibilityFocus(Calendar day) { - if ((day.get(Calendar.YEAR) != mYear) || (day.get(Calendar.MONTH) != mMonth) || - (day.get(Calendar.DAY_OF_MONTH) > mNumCells)) { - return false; - } - mTouchHelper.setFocusedVirtualView(day.get(Calendar.DAY_OF_MONTH)); - return true; - } - - /** * Provides a virtual view hierarchy for interfacing with an accessibility * service. */ @@ -581,24 +676,9 @@ class SimpleMonthView extends View { super(host); } - public void setFocusedVirtualView(int virtualViewId) { - getAccessibilityNodeProvider(SimpleMonthView.this).performAction( - virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - } - - public void clearFocusedVirtualView() { - final int focusedVirtualView = getFocusedVirtualView(); - if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) { - getAccessibilityNodeProvider(SimpleMonthView.this).performAction( - focusedVirtualView, - AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, - null); - } - } - @Override protected int getVirtualViewAt(float x, float y) { - final int day = getDayFromLocation(x, y); + final int day = getDayAtLocation(x, y); if (day >= 0) { return day; } @@ -607,7 +687,7 @@ class SimpleMonthView extends View { @Override protected void getVisibleVirtualViews(IntArray virtualViewIds) { - for (int day = 1; day <= mNumCells; day++) { + for (int day = 1; day <= mDaysInMonth; day++) { virtualViewIds.add(day); } } @@ -619,11 +699,20 @@ class SimpleMonthView extends View { @Override protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { - getItemBounds(virtualViewId, mTempRect); + final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect); + + if (!hasBounds) { + // The day is invalid, kill the node. + mTempRect.setEmpty(); + node.setContentDescription(""); + node.setBoundsInParent(mTempRect); + node.setVisibleToUser(false); + return; + } node.setContentDescription(getItemDescription(virtualViewId)); node.setBoundsInParent(mTempRect); - node.addAction(AccessibilityNodeInfo.ACTION_CLICK); + node.addAction(AccessibilityAction.ACTION_CLICK); if (virtualViewId == mActivatedDay) { node.setSelected(true); @@ -636,7 +725,7 @@ class SimpleMonthView extends View { Bundle arguments) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: - onDayClick(virtualViewId); + onDayClicked(virtualViewId); return true; } @@ -644,26 +733,6 @@ class SimpleMonthView extends View { } /** - * Calculates the bounding rectangle of a given time object. - * - * @param day The day to calculate bounds for - * @param rect The rectangle in which to store the bounds - */ - private void getItemBounds(int day, Rect rect) { - final int offsetX = mPadding; - final int offsetY = mMonthHeaderHeight; - final int cellHeight = mRowHeight; - final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays); - final int index = ((day - 1) + findDayOffset()); - final int row = (index / mNumDays); - final int column = (index % mNumDays); - final int x = (offsetX + (column * cellWidth)); - final int y = (offsetY + (row * cellHeight)); - - rect.set(x, y, (x + cellWidth), (y + cellHeight)); - } - - /** * Generates a description for a given time object. Since this * description will be spoken, the components are ordered by descending * specificity as DAY MONTH YEAR. diff --git a/core/java/android/widget/TextViewWithCircularIndicator.java b/core/java/android/widget/TextViewWithCircularIndicator.java deleted file mode 100644 index d3c786c..0000000 --- a/core/java/android/widget/TextViewWithCircularIndicator.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2014 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 android.widget; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Typeface; -import android.util.AttributeSet; - -import com.android.internal.R; - -class TextViewWithCircularIndicator extends TextView { - private final Paint mCirclePaint = new Paint(); - private final String mItemIsSelectedText; - - public TextViewWithCircularIndicator(Context context) { - this(context, null); - } - - public TextViewWithCircularIndicator(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TextViewWithCircularIndicator(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public TextViewWithCircularIndicator(Context context, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - final Resources res = context.getResources(); - mItemIsSelectedText = res.getString(R.string.item_is_selected); - - init(); - } - - private void init() { - mCirclePaint.setTypeface(Typeface.create(mCirclePaint.getTypeface(), Typeface.BOLD)); - mCirclePaint.setAntiAlias(true); - mCirclePaint.setTextAlign(Paint.Align.CENTER); - mCirclePaint.setStyle(Paint.Style.FILL); - } - - public void setCircleColor(int color) { - mCirclePaint.setColor(color); - invalidate(); - } - - @Override - public void onDraw(Canvas canvas) { - if (isActivated()) { - final int width = getWidth(); - final int height = getHeight(); - final int radius = Math.min(width, height) / 2; - canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint); - } - - super.onDraw(canvas); - } - - @Override - public CharSequence getContentDescription() { - final CharSequence itemText = getText(); - if (isActivated()) { - return String.format(mItemIsSelectedText, itemText); - } else { - return itemText; - } - } -}
\ No newline at end of file diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java index 6f0465f..7bd502e 100644 --- a/core/java/android/widget/YearPickerView.java +++ b/core/java/android/widget/YearPickerView.java @@ -17,10 +17,9 @@ package android.widget; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.Resources; import android.util.AttributeSet; -import android.util.StateSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; @@ -32,23 +31,14 @@ import com.android.internal.R; /** * Displays a selectable list of years. */ -class YearPickerView extends ListView implements AdapterView.OnItemClickListener, - OnDateChangedListener { - private final Calendar mMinDate = Calendar.getInstance(); - private final Calendar mMaxDate = Calendar.getInstance(); - +class YearPickerView extends ListView { private final YearAdapter mAdapter; private final int mViewSize; private final int mChildSize; - private DatePickerController mController; - - private int mSelectedPosition = -1; - private int mYearActivatedColor; + private OnYearSelectedListener mOnYearSelectedListener; - public YearPickerView(Context context) { - this(context, null); - } + private long mCurrentTimeMillis; public YearPickerView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.listViewStyle); @@ -69,104 +59,187 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener mViewSize = res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height); mChildSize = res.getDimensionPixelOffset(R.dimen.datepicker_year_label_height); - setVerticalFadingEdgeEnabled(true); - setFadingEdgeLength(mChildSize / 3); - - final int paddingTop = res.getDimensionPixelSize( - R.dimen.datepicker_year_picker_padding_top); - setPadding(0, paddingTop, 0, 0); + setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + final int year = mAdapter.getYearForPosition(position); + mAdapter.setSelection(year); - setOnItemClickListener(this); - setDividerHeight(0); + if (mOnYearSelectedListener != null) { + mOnYearSelectedListener.onYearChanged(YearPickerView.this, year); + } + } + }); - mAdapter = new YearAdapter(getContext(), R.layout.year_label_text_view); + mAdapter = new YearAdapter(getContext()); setAdapter(mAdapter); } - public void setRange(Calendar min, Calendar max) { - mMinDate.setTimeInMillis(min.getTimeInMillis()); - mMaxDate.setTimeInMillis(max.getTimeInMillis()); + public void setOnYearSelectedListener(OnYearSelectedListener listener) { + mOnYearSelectedListener = listener; + } - updateAdapterData(); + public void setDate(long currentTimeMillis) { + mCurrentTimeMillis = currentTimeMillis; } - public void init(DatePickerController controller) { - mController = controller; - mController.registerOnDateChangedListener(this); + /** + * Sets the currently selected year. Jumps immediately to the new year. + * + * @param year the target year + */ + public void setYear(final int year) { + mAdapter.setSelection(year); - updateAdapterData(); + post(new Runnable() { + @Override + public void run() { + final int position = mAdapter.getPositionForYear(year); + if (position >= 0 && position < getCount()) { + setSelectionCentered(position); + } + } + }); + } - onDateChanged(); + public void setSelectionCentered(int position) { + final int offset = mViewSize / 2 - mChildSize / 2; + setSelectionFromTop(position, offset); } - public void setYearBackgroundColor(ColorStateList yearBackgroundColor) { - mYearActivatedColor = yearBackgroundColor.getColorForState( - StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); - invalidate(); + public void setRange(Calendar min, Calendar max) { + mAdapter.setRange(min, max); } public void setYearTextAppearance(int resId) { mAdapter.setItemTextAppearance(resId); } - private void updateAdapterData() { - mAdapter.clear(); + public void setYearActivatedTextAppearance(int resId) { + mAdapter.setItemActivatedTextAppearance(resId); + } + + private static class YearAdapter extends BaseAdapter { + private static final int ITEM_LAYOUT = R.layout.year_label_text_view; + + private final LayoutInflater mInflater; + + private int mActivatedYear; + private int mMinYear; + private int mCount; - final int maxYear = mMaxDate.get(Calendar.YEAR); - for (int year = mMinDate.get(Calendar.YEAR); year <= maxYear; year++) { - mAdapter.add(year); + private int mItemTextAppearanceResId; + private int mItemActivatedTextAppearanceResId; + + public YearAdapter(Context context) { + mInflater = LayoutInflater.from(context); } - } - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - mController.tryVibrate(); - if (position != mSelectedPosition) { - mSelectedPosition = position; - mAdapter.notifyDataSetChanged(); + public void setRange(Calendar minDate, Calendar maxDate) { + final int minYear = minDate.get(Calendar.YEAR); + final int count = maxDate.get(Calendar.YEAR) - minYear + 1; + + if (mMinYear != minYear || mCount != count) { + mMinYear = minYear; + mCount = count; + notifyDataSetInvalidated(); + } } - mController.onYearSelected(mAdapter.getItem(position)); - } - private class YearAdapter extends ArrayAdapter<Integer> { - private int mItemTextAppearanceResId; + public boolean setSelection(int year) { + if (mActivatedYear != year) { + mActivatedYear = year; + notifyDataSetChanged(); + return true; + } + return false; + } + + public void setItemTextAppearance(int resId) { + mItemTextAppearanceResId = resId; + notifyDataSetChanged(); + } + + public void setItemActivatedTextAppearance(int resId) { + mItemActivatedTextAppearanceResId = resId; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mCount; + } - public YearAdapter(Context context, int resource) { - super(context, resource); + @Override + public Integer getItem(int position) { + return getYearForPosition(position); + } + + @Override + public long getItemId(int position) { + return getYearForPosition(position); + } + + public int getPositionForYear(int year) { + return year - mMinYear; + } + + public int getYearForPosition(int position) { + return mMinYear + position; + } + + @Override + public boolean hasStableIds() { + return true; } @Override public View getView(int position, View convertView, ViewGroup parent) { - final TextViewWithCircularIndicator v = (TextViewWithCircularIndicator) - super.getView(position, convertView, parent); - v.setTextAppearance(v.getContext(), mItemTextAppearanceResId); - v.setCircleColor(mYearActivatedColor); + if (convertView == null) { + convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); + } - final int year = getItem(position); - final boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year; - v.setActivated(selected); + final int year = getYearForPosition(position); + final boolean activated = mActivatedYear == year; + final int textAppearanceResId; + if (activated && mItemActivatedTextAppearanceResId != 0) { + textAppearanceResId = mItemActivatedTextAppearanceResId; + } else { + textAppearanceResId = mItemTextAppearanceResId; + } + + final TextView v = (TextView) convertView; + v.setText("" + year); + v.setTextAppearance(v.getContext(), textAppearanceResId); + v.setActivated(activated); return v; } - public void setItemTextAppearance(int resId) { - mItemTextAppearanceResId = resId; + @Override + public int getItemViewType(int position) { + return 0; } - } - public void postSetSelectionCentered(final int position) { - postSetSelectionFromTop(position, mViewSize / 2 - mChildSize / 2); - } + @Override + public int getViewTypeCount() { + return 1; + } - public void postSetSelectionFromTop(final int position, final int offset) { - post(new Runnable() { + @Override + public boolean isEmpty() { + return false; + } - @Override - public void run() { - setSelectionFromTop(position, offset); - requestLayout(); - } - }); + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } } public int getFirstPositionOffset() { @@ -177,22 +250,28 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener return firstChild.getTop(); } - @Override - public void onDateChanged() { - updateAdapterData(); - mAdapter.notifyDataSetChanged(); - postSetSelectionCentered( - mController.getSelectedDay().get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR)); - } - /** @hide */ @Override public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { super.onInitializeAccessibilityEventInternal(event); + // There are a bunch of years, so don't bother. if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { event.setFromIndex(0); event.setToIndex(0); } } + + /** + * The callback used to indicate the user changed the year. + */ + public interface OnYearSelectedListener { + /** + * Called upon a year change. + * + * @param view The view associated with this listener. + * @param year The year that was set. + */ + void onYearChanged(YearPickerView view, int year); + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/AccessibleDateAnimator.java b/core/java/com/android/internal/widget/AccessibleDateAnimator.java deleted file mode 100644 index f97a5d1..0000000 --- a/core/java/com/android/internal/widget/AccessibleDateAnimator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 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.internal.widget; - -import android.content.Context; -import android.text.format.DateUtils; -import android.util.AttributeSet; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ViewAnimator; - -/** - * @hide - */ -public class AccessibleDateAnimator extends ViewAnimator { - private long mDateMillis; - - public AccessibleDateAnimator(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void setDateMillis(long dateMillis) { - mDateMillis = dateMillis; - } - - /** - * Announce the currently-selected date when launched. - */ - @Override - public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - // Clear the event's current text so that only the current date will be spoken. - event.getText().clear(); - int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_WEEKDAY; - - String dateString = DateUtils.formatDateTime(getContext(), mDateMillis, flags); - event.getText().add(dateString); - return true; - } - return super.dispatchPopulateAccessibilityEventInternal(event); - } -} diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java index f916e6f..8018942 100644 --- a/core/java/com/android/internal/widget/ViewPager.java +++ b/core/java/com/android/internal/widget/ViewPager.java @@ -137,7 +137,7 @@ public class ViewPager extends ViewGroup { private int mRestoredCurItem = -1; private Parcelable mRestoredAdapterState = null; private ClassLoader mRestoredClassLoader = null; - private Scroller mScroller; + private final Scroller mScroller; private PagerObserver mObserver; private int mPageMargin; @@ -162,9 +162,9 @@ public class ViewPager extends ViewGroup { private boolean mIsBeingDragged; private boolean mIsUnableToDrag; - private int mDefaultGutterSize; + private final int mDefaultGutterSize; private int mGutterSize; - private int mTouchSlop; + private final int mTouchSlop; /** * Position of the last motion event. */ @@ -187,10 +187,10 @@ public class ViewPager extends ViewGroup { * Determines speed during touch scrolling */ private VelocityTracker mVelocityTracker; - private int mMinimumVelocity; - private int mMaximumVelocity; - private int mFlingDistance; - private int mCloseEnough; + private final int mMinimumVelocity; + private final int mMaximumVelocity; + private final int mFlingDistance; + private final int mCloseEnough; // If the pager is at least this close to its final position, complete the scroll // on touch down and let the user interact with the content inside instead of @@ -200,8 +200,8 @@ public class ViewPager extends ViewGroup { private boolean mFakeDragging; private long mFakeDragBeginTime; - private EdgeEffect mLeftEdge; - private EdgeEffect mRightEdge; + private final EdgeEffect mLeftEdge; + private final EdgeEffect mRightEdge; private boolean mFirstLayout = true; private boolean mNeedCalculatePageOffsets = false; @@ -339,20 +339,24 @@ public class ViewPager extends ViewGroup { interface Decor {} public ViewPager(Context context) { - super(context); - initViewPager(); + this(context, null); } public ViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - initViewPager(); + this(context, attrs, 0); } - void initViewPager() { + public ViewPager(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ViewPager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setWillNotDraw(false); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setFocusable(true); - final Context context = getContext(); + mScroller = new Scroller(context, sInterpolator); final ViewConfiguration configuration = ViewConfiguration.get(context); final float density = context.getResources().getDisplayMetrics().density; |