From 2e00aa34c051111529290cf23c6ba940c2c0c142 Mon Sep 17 00:00:00 2001 From: James Kung Date: Wed, 10 Apr 2013 17:37:26 -0700 Subject: Date picker layout modifications Bug: 8581974 Change-Id: I3c547c2ebf1bb6488b823044bd7fd7bdffdab69e --- src/com/android/datetimepicker/Utils.java | 9 +- .../datetimepicker/date/DatePickerController.java | 21 +-- .../datetimepicker/date/DatePickerDialog.java | 156 +++++++++++++++------ .../android/datetimepicker/date/DayPickerView.java | 146 +++++++------------ .../datetimepicker/date/MonthPickerView.java | 94 ------------- .../datetimepicker/date/OnDateChangedListener.java | 0 .../datetimepicker/date/SelectableTextView.java | 67 --------- .../datetimepicker/date/SimpleMonthAdapter.java | 3 +- .../datetimepicker/date/SimpleMonthView.java | 50 ++++--- .../date/TextViewWithCircularIndicator.java | 74 ++++++++++ .../datetimepicker/date/YearPickerView.java | 133 ++++++++++++++---- .../datetimepicker/time/TimePickerDialog.java | 5 +- 12 files changed, 395 insertions(+), 363 deletions(-) delete mode 100644 src/com/android/datetimepicker/date/MonthPickerView.java create mode 100644 src/com/android/datetimepicker/date/OnDateChangedListener.java delete mode 100644 src/com/android/datetimepicker/date/SelectableTextView.java create mode 100644 src/com/android/datetimepicker/date/TextViewWithCircularIndicator.java (limited to 'src/com/android') diff --git a/src/com/android/datetimepicker/Utils.java b/src/com/android/datetimepicker/Utils.java index ae3087b..83590bf 100644 --- a/src/com/android/datetimepicker/Utils.java +++ b/src/com/android/datetimepicker/Utils.java @@ -31,7 +31,7 @@ import java.util.Calendar; public class Utils { public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3; - public static final int PULSE_ANIMATOR_DURATION = 600; + public static final int PULSE_ANIMATOR_DURATION = 544; static final String SHARED_PREFS_NAME = "com.android.calendar_preferences"; @@ -103,10 +103,11 @@ public class Utils { * @param labelToAnimate the view to pulsate. * @return The animator object. Use .start() to begin. */ - public static ObjectAnimator getPulseAnimator(View labelToAnimate) { + public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio, + float increaseRatio) { Keyframe k0 = Keyframe.ofFloat(0f, 1f); - Keyframe k1 = Keyframe.ofFloat(0.25f, 0.85f); - Keyframe k2 = Keyframe.ofFloat(0.625f, 1.1f); + Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio); + Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio); Keyframe k3 = Keyframe.ofFloat(1f, 1f); PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3); diff --git a/src/com/android/datetimepicker/date/DatePickerController.java b/src/com/android/datetimepicker/date/DatePickerController.java index f92d687..173e58a 100644 --- a/src/com/android/datetimepicker/date/DatePickerController.java +++ b/src/com/android/datetimepicker/date/DatePickerController.java @@ -16,24 +16,29 @@ package com.android.datetimepicker.date; +import com.android.datetimepicker.date.DatePickerDialog.OnDateChangedListener; import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay; /** * Controller class to communicate among the various components of the date picker dialog. */ -public interface DatePickerController { +interface DatePickerController { - public void onYearPickerSelectionChanged(int year); + void onYearSelected(int year); - public void onMonthPickerSelectionChanged(int month); + void onDayOfMonthSelected(int year, int month, int day); - public void onDayPickerSelectionChanged(int year, int month, int day); + void registerOnDateChangedListener(OnDateChangedListener listener); - public CalendarDay getSelectedDay(); + void unregisterOnDateChangedListener(OnDateChangedListener listener); - public int getFirstDayOfWeek(); + CalendarDay getSelectedDay(); - public int getMinYear(); + int getFirstDayOfWeek(); - public int getMaxYear(); + int getMinYear(); + + int getMaxYear(); + + void tryVibrate(); } diff --git a/src/com/android/datetimepicker/date/DatePickerDialog.java b/src/com/android/datetimepicker/date/DatePickerDialog.java index e2b91ad..77bedbf 100644 --- a/src/com/android/datetimepicker/date/DatePickerDialog.java +++ b/src/com/android/datetimepicker/date/DatePickerDialog.java @@ -16,9 +16,13 @@ package com.android.datetimepicker.date; +import android.animation.ObjectAnimator; import android.app.Activity; import android.app.DialogFragment; +import android.content.Context; import android.os.Bundle; +import android.os.SystemClock; +import android.os.Vibrator; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -39,6 +43,8 @@ import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.HashSet; +import java.util.Iterator; import java.util.Locale; /** @@ -49,15 +55,14 @@ public class DatePickerDialog extends DialogFragment implements private static final String TAG = "DatePickerDialog"; - private static final int TOTAL_VIEWS = 2; - + 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 String KEY_SELECTED_YEAR = "year"; private static final String KEY_SELECTED_MONTH = "month"; private static final String KEY_SELECTED_DAY = "day"; - private static final String KEY_LIST_POSITION = "position"; + private static final String KEY_LIST_POSITION = "list_position"; private static final String KEY_WEEK_START = "week_start"; private static final String KEY_YEAR_START = "year_start"; private static final String KEY_YEAR_END = "year_end"; @@ -66,13 +71,15 @@ public class DatePickerDialog extends DialogFragment implements private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; - private static final int ANIMATION_DURATION = 500; + private static final int ANIMATION_DURATION = 300; + private static final int ANIMATION_DELAY = 500; private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault()); private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault()); private final Calendar mCalendar = Calendar.getInstance(); private OnDateSetListener mCallBack; + private HashSet mListeners = new HashSet(); private ViewAnimator mAnimator; @@ -85,13 +92,16 @@ public class DatePickerDialog extends DialogFragment implements private YearPickerView mYearPickerView; private Button mDoneButton; - private int mCurrentView; + private int mCurrentView = UNINITIALIZED; private int mWeekStart = mCalendar.getFirstDayOfWeek(); private int mMinYear = DEFAULT_START_YEAR; private int mMaxYear = DEFAULT_END_YEAR; - private final View[] mViews = new View[TOTAL_VIEWS]; + private Vibrator mVibrator; + private long mLastVibrate; + + private boolean mDelayAnimation = true; /** * The callback used to indicate the user is done filling in the date. @@ -108,6 +118,15 @@ public class DatePickerDialog extends DialogFragment implements void onDateSet(DatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth); } + /** + * The callback used to notify other date picker components of a change in selected date. + */ + interface OnDateChangedListener { + + public void onDateChanged(); + } + + public DatePickerDialog() { // Empty constructor required for dialog fragment. } @@ -136,8 +155,10 @@ public class DatePickerDialog extends DialogFragment implements @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getActivity().getWindow().setSoftInputMode( + final Activity activity = getActivity(); + activity.getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + mVibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); if (savedInstanceState != null) { mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR)); mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH)); @@ -151,11 +172,17 @@ public class DatePickerDialog extends DialogFragment implements outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR)); outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH)); outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH)); - outState.putInt(KEY_LIST_POSITION, mDayPickerView.getFirstVisiblePosition()); outState.putInt(KEY_WEEK_START, mWeekStart); outState.putInt(KEY_YEAR_START, mMinYear); outState.putInt(KEY_YEAR_END, mMaxYear); outState.putInt(KEY_CURRENT_VIEW, mCurrentView); + int listPosition = -1; + if (mCurrentView == MONTH_AND_DAY_VIEW) { + listPosition = mDayPickerView.getMostVisiblePosition(); + } else if (mCurrentView == YEAR_VIEW) { + listPosition = mYearPickerView.getFirstVisiblePosition(); + } + outState.putInt(KEY_LIST_POSITION, listPosition); } @Override @@ -173,27 +200,24 @@ public class DatePickerDialog extends DialogFragment implements mSelectedDayTextView = (TextView) view.findViewById(R.id.date_picker_day); mYearView = (TextView) view.findViewById(R.id.date_picker_year); mYearView.setOnClickListener(this); - final Activity activity = getActivity(); + int listPosition = -1; int currentView = MONTH_AND_DAY_VIEW; - mDayPickerView = new DayPickerView(activity, this); if (savedInstanceState != null) { - Log.d(TAG, - "Setting first visible position: " - + savedInstanceState.getInt(KEY_LIST_POSITION)); - mDayPickerView.setSelectionFromTop(savedInstanceState.getInt(KEY_LIST_POSITION), - DayPickerView.LIST_TOP_OFFSET); mWeekStart = savedInstanceState.getInt(KEY_WEEK_START); mMinYear = savedInstanceState.getInt(KEY_YEAR_START); mMaxYear = savedInstanceState.getInt(KEY_YEAR_END); currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW); + listPosition = savedInstanceState.getInt(KEY_LIST_POSITION); } - mYearPickerView = new YearPickerView(activity, this); - mViews[MONTH_AND_DAY_VIEW] = mDayPickerView; - mViews[YEAR_VIEW] = mYearPickerView; + final Activity activity = getActivity(); + mDayPickerView = new DayPickerView(activity, this); + mYearPickerView = new YearPickerView(activity, this); mAnimator = (ViewAnimator) view.findViewById(R.id.animator); + mAnimator.addView(mDayPickerView); + mAnimator.addView(mYearPickerView); // TODO: Replace with animation decided upon by the design team. Animation animation = new AlphaAnimation(0.0f, 1.0f); animation.setDuration(ANIMATION_DURATION); @@ -202,14 +226,13 @@ public class DatePickerDialog extends DialogFragment implements Animation animation2 = new AlphaAnimation(1.0f, 0.0f); animation2.setDuration(ANIMATION_DURATION); mAnimator.setOutAnimation(animation2); - mAnimator.addView(mDayPickerView); - mAnimator.addView(mYearPickerView); mDoneButton = (Button) view.findViewById(R.id.done); mDoneButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { + tryVibrate(); if (mCallBack != null) { mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR), mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH)); @@ -221,22 +244,48 @@ public class DatePickerDialog extends DialogFragment implements updateDisplay(); setCurrentView(currentView); + if (listPosition != -1) { + if (currentView == MONTH_AND_DAY_VIEW) { + mDayPickerView.postSetSelection(listPosition); + } else if (currentView == YEAR_VIEW) { + mYearPickerView.postSetSelection(listPosition); + } + } return view; } private void setCurrentView(final int viewIndex) { switch (viewIndex) { case MONTH_AND_DAY_VIEW: - mCurrentView = viewIndex; - mMonthAndDayView.setSelected(true); - mYearView.setSelected(false); - mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); + ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mMonthAndDayView, 0.9f, + 1.05f); + if (mDelayAnimation) { + pulseAnimator.setStartDelay(ANIMATION_DELAY); + mDelayAnimation = false; + } + mDayPickerView.onDateChanged(); + if (mCurrentView != viewIndex) { + mMonthAndDayView.setSelected(true); + mYearView.setSelected(false); + mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); + mCurrentView = viewIndex; + } + pulseAnimator.start(); break; case YEAR_VIEW: - mCurrentView = viewIndex; - mMonthAndDayView.setSelected(false); - mYearView.setSelected(true); - mAnimator.setDisplayedChild(YEAR_VIEW); + pulseAnimator = Utils.getPulseAnimator(mYearView, 0.85f, 1.1f); + if (mDelayAnimation) { + pulseAnimator.setStartDelay(ANIMATION_DELAY); + mDelayAnimation = false; + } + mYearPickerView.onDateChanged(); + if (mCurrentView != viewIndex) { + mMonthAndDayView.setSelected(false); + mYearView.setSelected(true); + mAnimator.setDisplayedChild(YEAR_VIEW); + mCurrentView = viewIndex; + } + pulseAnimator.start(); break; } } @@ -271,7 +320,6 @@ public class DatePickerDialog extends DialogFragment implements mMaxYear = endYear; if (mDayPickerView != null) { mDayPickerView.onChange(); - mYearPickerView.onChange(); } } @@ -293,6 +341,7 @@ public class DatePickerDialog extends DialogFragment implements @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) { @@ -301,31 +350,30 @@ public class DatePickerDialog extends DialogFragment implements } @Override - public void onYearPickerSelectionChanged(int year) { + public void onYearSelected(int year) { adjustDayInMonthIfNeeded(mCalendar.get(Calendar.MONTH), year); mCalendar.set(Calendar.YEAR, year); - mDayPickerView.setCalendarDate(getSelectedDay()); - updateDisplay(); - } - - @Override - public void onMonthPickerSelectionChanged(int month) { - adjustDayInMonthIfNeeded(month, mCalendar.get(Calendar.YEAR)); - mCalendar.set(Calendar.MONTH, month); - mDayPickerView.setCalendarDate(getSelectedDay()); + updatePickers(); setCurrentView(MONTH_AND_DAY_VIEW); updateDisplay(); } @Override - public void onDayPickerSelectionChanged(int year, int month, int day) { + public void onDayOfMonthSelected(int year, int month, int day) { mCalendar.set(Calendar.YEAR, year); mCalendar.set(Calendar.MONTH, month); mCalendar.set(Calendar.DAY_OF_MONTH, day); - mYearPickerView.setValue(mCalendar.get(Calendar.YEAR)); + updatePickers(); updateDisplay(); } + private void updatePickers() { + Iterator iterator = mListeners.iterator(); + while (iterator.hasNext()) { + iterator.next().onDateChanged(); + } + } + @Override public CalendarDay getSelectedDay() { @@ -346,4 +394,30 @@ public class DatePickerDialog extends DialogFragment implements public int getFirstDayOfWeek() { return mWeekStart; } + + @Override + public void registerOnDateChangedListener(OnDateChangedListener listener) { + mListeners.add(listener); + } + + @Override + public void unregisterOnDateChangedListener(OnDateChangedListener listener) { + mListeners.remove(listener); + } + + /** + * Try to vibrate. To prevent this becoming a single continuous vibration, nothing will + * happen if we have vibrated very recently. + */ + @Override + public void tryVibrate() { + if (mVibrator != null) { + long now = SystemClock.uptimeMillis(); + // We want to try to vibrate each individual tick discretely. + if (now - mLastVibrate >= 125) { + mVibrator.vibrate(5); + mLastVibrate = now; + } + } + } } diff --git a/src/com/android/datetimepicker/date/DayPickerView.java b/src/com/android/datetimepicker/date/DayPickerView.java index 76dcf4f..1b0e871 100644 --- a/src/com/android/datetimepicker/date/DayPickerView.java +++ b/src/com/android/datetimepicker/date/DayPickerView.java @@ -17,9 +17,7 @@ package com.android.datetimepicker.date; import android.content.Context; -import android.database.DataSetObserver; import android.os.Handler; -import android.text.format.Time; import android.util.Log; import android.view.View; import android.view.ViewConfiguration; @@ -27,15 +25,13 @@ import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; +import com.android.datetimepicker.date.DatePickerDialog.OnDateChangedListener; import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay; -import java.util.Calendar; -import java.util.Locale; - /** * This displays a list of months in a calendar format with selectable days. */ -public class DayPickerView extends ListView implements OnScrollListener { +public class DayPickerView extends ListView implements OnScrollListener, OnDateChangedListener { private static final String TAG = "MonthFragment"; @@ -61,8 +57,6 @@ public class DayPickerView extends ListView implements OnScrollListener { protected Context mContext; protected Handler mHandler; - protected float mMinimumFlingVelocity; - // highlighted time protected CalendarDay mSelectedDay = new CalendarDay(); protected SimpleMonthAdapter mAdapter; @@ -72,8 +66,6 @@ public class DayPickerView extends ListView implements OnScrollListener { private static float mScale = 0; // When the week starts; numbered like Time. (e.g. SUNDAY=0). protected int mFirstDayOfWeek; - // The first day that is visible in the view - protected Time mFirstVisibleDay = new Time(); // The last name announced by accessibility protected CharSequence mPrevMonthName; // which month should be displayed/highlighted [0-11] @@ -87,61 +79,19 @@ public class DayPickerView extends ListView implements OnScrollListener { private final DatePickerController mController; - // This causes an update of the view at midnight - protected Runnable mTodayUpdater = new Runnable() { - @Override - public void run() { - Time midnight = new Time(mFirstVisibleDay.timezone); - midnight.setToNow(); - long currentMillis = midnight.toMillis(true); - - midnight.hour = 0; - midnight.minute = 0; - midnight.second = 0; - midnight.monthDay++; - long millisToMidnight = midnight.normalize(true) - currentMillis; - mHandler.postDelayed(this, millisToMidnight); - - if (mAdapter != null) { - mAdapter.notifyDataSetChanged(); - } - } - }; - - // This allows us to update our position when a day is tapped - protected DataSetObserver mObserver = new DataSetObserver() { - @Override - public void onChanged() { - CalendarDay day = mAdapter.getSelectedDay(); - if (day.year != mSelectedDay.year || day.day != mSelectedDay.day) { - goTo(day, true, true, false); - } - } - }; - public DayPickerView(Context context, DatePickerController controller) { super(context); mHandler = new Handler(); mController = controller; + mController.registerOnDateChangedListener(this); setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); setDrawSelectorOnTop(false); init(context); - setCalendarDate(controller.getSelectedDay()); - } - - public void setCalendarDate(CalendarDay day) { - goTo(day, false, true, true); + onDateChanged(); } public void init(Context context) { mContext = context; - String tz = Time.getCurrentTimezone(); - ViewConfiguration viewConfig = ViewConfiguration.get(context); - mMinimumFlingVelocity = viewConfig.getScaledMinimumFlingVelocity(); - - mFirstVisibleDay.timezone = tz; - mFirstVisibleDay.normalize(true); - setUpListView(); setUpAdapter(); setAdapter(mAdapter); @@ -159,7 +109,6 @@ public class DayPickerView extends ListView implements OnScrollListener { protected void setUpAdapter() { if (mAdapter == null) { mAdapter = new SimpleMonthAdapter(getContext(), mController); - mAdapter.registerDataSetObserver(mObserver); } else { mAdapter.setSelectedDay(mSelectedDay); mAdapter.notifyDataSetChanged(); @@ -188,41 +137,6 @@ public class DayPickerView extends ListView implements OnScrollListener { setFriction(ViewConfiguration.getScrollFriction() * mFriction); } - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - setUpAdapter(); - doResumeUpdates(); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mHandler.removeCallbacks(mTodayUpdater); - } - - // @Override - // public void onSaveInstanceState(Bundle outState) { - // outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true)); - // } - - /** - * Updates the user preference fields. Override this to use a different - * preference space. - */ - protected void doResumeUpdates() { - // Get default week start based on locale, subtracting one for use with - // android Time. - Calendar cal = Calendar.getInstance(Locale.getDefault()); - mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1; - - mShowWeekNumber = false; - - goTo(mSelectedDay, false, false, false); - mAdapter.setSelectedDay(mSelectedDay); - mTodayUpdater.run(); - } - /** * This moves to the specified time in the view. If the time is not already * in range it will move the list so that the first of the month containing @@ -246,10 +160,9 @@ public class DayPickerView extends ListView implements OnScrollListener { } mTempDay.set(day); - // Get the week we're going to - // TODO push Util function into Calendar public api. - int position = (day.year - mController.getMinYear()) + final int position = (day.year - mController.getMinYear()) * SimpleMonthAdapter.MONTHS_IN_YEAR + day.month; + Log.d(TAG, "Year: " + day.year); View child; int i = 0; @@ -291,8 +204,7 @@ public class DayPickerView extends ListView implements OnScrollListener { position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION); return true; } else { - setSelectionFromTop(position, LIST_TOP_OFFSET); - onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE); + postSetSelection(position); } } else if (setSelected) { setMonthDisplayed(mSelectedDay); @@ -300,6 +212,18 @@ public class DayPickerView extends ListView implements OnScrollListener { 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. @@ -391,4 +315,36 @@ public class DayPickerView extends ListView implements OnScrollListener { } } } + + /** + * 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; + } + + @Override + public void onDateChanged() { + goTo(mController.getSelectedDay(), false, true, true); + } } diff --git a/src/com/android/datetimepicker/date/MonthPickerView.java b/src/com/android/datetimepicker/date/MonthPickerView.java deleted file mode 100644 index 81f10ab..0000000 --- a/src/com/android/datetimepicker/date/MonthPickerView.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.datetimepicker.date; - -import android.content.Context; -import android.graphics.drawable.StateListDrawable; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.GridView; -import android.widget.SimpleAdapter; - -import com.android.datetimepicker.R; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** - * Displays a selectable list of months in a grid format. - */ -public class MonthPickerView extends GridView implements OnItemClickListener { - - private static final int NUM_COLUMNS = 3; - private static final int NUM_MONTHS = 12; - - private final Calendar mCalendar = Calendar.getInstance(); - private final DatePickerController mController; - - /** - * @param context - */ - public MonthPickerView(Context context, DatePickerController controller) { - super(context); - ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - setLayoutParams(frame); - setNumColumns(NUM_COLUMNS); - init(context); - mController = controller; - setOnItemClickListener(this); - setSelector(new StateListDrawable()); - } - - private void init(Context context) { - ArrayList months = new ArrayList(); - mCalendar.set(Calendar.DAY_OF_MONTH, 1); - for (int i = 0; i < NUM_MONTHS; i++) { - mCalendar.set(Calendar.MONTH, i); - months.add(mCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, - Locale.getDefault()).toUpperCase(Locale.getDefault())); - } - setAdapter(new ArrayAdapter(context, R.layout.month_text_view, months)); - } - - public class MonthPickerAdapter extends SimpleAdapter { - - /** - * @param context - * @param data - * @param resource - * @param from - * @param to - */ - public MonthPickerAdapter(Context context, - List> data, - int resource, String[] from, int[] to) { - super(context, data, resource, from, to); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - mController.onMonthPickerSelectionChanged(position); - } -} diff --git a/src/com/android/datetimepicker/date/OnDateChangedListener.java b/src/com/android/datetimepicker/date/OnDateChangedListener.java new file mode 100644 index 0000000..e69de29 diff --git a/src/com/android/datetimepicker/date/SelectableTextView.java b/src/com/android/datetimepicker/date/SelectableTextView.java deleted file mode 100644 index 0da34fd..0000000 --- a/src/com/android/datetimepicker/date/SelectableTextView.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.datetimepicker.date; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Paint.Style; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.android.datetimepicker.R; - -/** - * A text view which, when pressed or selected, displays a blue circle around the text. - */ -public class SelectableTextView extends TextView { - - private static final int SELECTED_CIRCLE_ALPHA = 60; - - Paint mCirclePaint = new Paint(); - - private final int mRadius; - private final int mCircleColor; - - public SelectableTextView(Context context, AttributeSet attrs) { - super(context, attrs); - Resources res = context.getResources(); - mCircleColor = res.getColor(R.color.selected_text); - mRadius = res.getDimensionPixelOffset(R.dimen.month_select_circle_radius); - init(); - } - - private void init() { - mCirclePaint.setFakeBoldText(true); - mCirclePaint.setAntiAlias(true); - mCirclePaint.setColor(mCircleColor); - mCirclePaint.setTextAlign(Align.CENTER); - mCirclePaint.setStyle(Style.FILL); - mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA); - } - - @Override - public void onDraw(Canvas canvas) { - if (isPressed() || isSelected()) { - canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, - mCirclePaint); - } - super.onDraw(canvas); - } -} diff --git a/src/com/android/datetimepicker/date/SimpleMonthAdapter.java b/src/com/android/datetimepicker/date/SimpleMonthAdapter.java index 533776e..6d3ef7a 100644 --- a/src/com/android/datetimepicker/date/SimpleMonthAdapter.java +++ b/src/com/android/datetimepicker/date/SimpleMonthAdapter.java @@ -207,7 +207,8 @@ public class SimpleMonthAdapter extends BaseAdapter implements OnTouchListener { * @param day The day that was tapped */ protected void onDayTapped(CalendarDay day) { - mController.onDayPickerSelectionChanged(day.year, day.month, day.day); + mController.tryVibrate(); + mController.onDayOfMonthSelected(day.year, day.month, day.day); setSelectedDay(day); } diff --git a/src/com/android/datetimepicker/date/SimpleMonthView.java b/src/com/android/datetimepicker/date/SimpleMonthView.java index 666c43e..1beeef6 100644 --- a/src/com/android/datetimepicker/date/SimpleMonthView.java +++ b/src/com/android/datetimepicker/date/SimpleMonthView.java @@ -22,7 +22,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; -import android.graphics.Rect; +import android.graphics.Typeface; import android.text.format.Time; import android.view.View; @@ -110,9 +110,12 @@ public class SimpleMonthView extends View { // affects the padding on the sides of this view protected int mPadding = 0; - protected Rect r = new Rect(); + private String mDayOfWeekTypeface; + private String mMonthTitleTypeface; + protected Paint mMonthNumPaint; protected Paint mMonthTitlePaint; + protected Paint mMonthTitleBGPaint; protected Paint mSelectedCirclePaint; protected Paint mMonthDayLabelPaint; @@ -152,15 +155,10 @@ public class SimpleMonthView extends View { private int mNumRows = DEFAULT_NUM_ROWS; - protected int mBGColor; - protected int mFocusMonthColor; - protected int mOtherMonthColor; - protected int mDaySeparatorColor; - protected int mTodayOutlineColor; - protected int mWeekNumColor; - protected int mDayTextColor; - protected int mMonthTitleTextColor; + protected int mTodayNumberColor; + protected int mMonthTitleColor; + protected int mMonthTitleBGColor; public SimpleMonthView(Context context) { super(context); @@ -169,14 +167,14 @@ public class SimpleMonthView extends View { mDayLabelCalendar = Calendar.getInstance(); mCalendar = Calendar.getInstance(); - mBGColor = res.getColor(R.color.month_bgcolor); - mFocusMonthColor = res.getColor(R.color.month_mini_day_number); - mOtherMonthColor = res.getColor(R.color.month_other_month_day_number); - mDaySeparatorColor = res.getColor(R.color.month_grid_lines); - mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color); - mDayTextColor = res.getColor(R.color.calendar_day_number); - mMonthTitleTextColor = res.getColor(R.color.selected_text); - mWeekNumColor = res.getColor(R.color.month_week_num_color); + + mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface); + mMonthTitleTypeface = res.getString(R.string.sans_serif); + + mDayTextColor = res.getColor(R.color.date_picker_text_normal); + mTodayNumberColor = res.getColor(R.color.blue); + mMonthTitleColor = res.getColor(R.color.white); + mMonthTitleBGColor = res.getColor(R.color.circle_background); MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.day_number_size); MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_label_size); @@ -200,15 +198,22 @@ public class SimpleMonthView extends View { mMonthTitlePaint.setFakeBoldText(true); mMonthTitlePaint.setAntiAlias(true); mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE); - mMonthTitlePaint.setColor(mMonthTitleTextColor); + mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD)); + mMonthTitlePaint.setColor(mDayTextColor); mMonthTitlePaint.setTextAlign(Align.CENTER); mMonthTitlePaint.setStyle(Style.FILL); - mMonthTitlePaint.setFakeBoldText(true); + + mMonthTitleBGPaint = new Paint(); + mMonthTitleBGPaint.setFakeBoldText(true); + mMonthTitleBGPaint.setAntiAlias(true); + mMonthTitleBGPaint.setColor(mMonthTitleBGColor); + mMonthTitleBGPaint.setTextAlign(Align.CENTER); + mMonthTitleBGPaint.setStyle(Style.FILL); mSelectedCirclePaint = new Paint(); mSelectedCirclePaint.setFakeBoldText(true); mSelectedCirclePaint.setAntiAlias(true); - mSelectedCirclePaint.setColor(mMonthTitleTextColor); + mSelectedCirclePaint.setColor(mTodayNumberColor); mSelectedCirclePaint.setTextAlign(Align.CENTER); mSelectedCirclePaint.setStyle(Style.FILL); mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA); @@ -217,6 +222,7 @@ public class SimpleMonthView extends View { mMonthDayLabelPaint.setAntiAlias(true); mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE); mMonthDayLabelPaint.setColor(mDayTextColor); + mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL)); mMonthDayLabelPaint.setStyle(Style.FILL); mMonthDayLabelPaint.setTextAlign(Align.CENTER); mMonthDayLabelPaint.setFakeBoldText(true); @@ -370,7 +376,7 @@ public class SimpleMonthView extends View { } if (mHasToday && mToday == dayNumber) { - mMonthNumPaint.setColor(mMonthTitleTextColor); + mMonthNumPaint.setColor(mTodayNumberColor); } else { mMonthNumPaint.setColor(mDayTextColor); } diff --git a/src/com/android/datetimepicker/date/TextViewWithCircularIndicator.java b/src/com/android/datetimepicker/date/TextViewWithCircularIndicator.java new file mode 100644 index 0000000..66140f5 --- /dev/null +++ b/src/com/android/datetimepicker/date/TextViewWithCircularIndicator.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.datetimepicker.date; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.datetimepicker.R; + +/** + * A text view which, when pressed or activated, displays a blue circle around the text. + */ +public class TextViewWithCircularIndicator extends TextView { + + private static final int SELECTED_CIRCLE_ALPHA = 60; + + Paint mCirclePaint = new Paint(); + + private final int mRadius; + private final int mCircleColor; + private boolean mDrawCircle; + + public TextViewWithCircularIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + Resources res = context.getResources(); + mCircleColor = res.getColor(R.color.blue); + mRadius = res.getDimensionPixelOffset(R.dimen.month_select_circle_radius); + init(); + } + + private void init() { + mCirclePaint.setFakeBoldText(true); + mCirclePaint.setAntiAlias(true); + mCirclePaint.setColor(mCircleColor); + mCirclePaint.setTextAlign(Align.CENTER); + mCirclePaint.setStyle(Style.FILL); + mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA); + } + + public void drawIndicator(boolean drawCircle) { + mDrawCircle = drawCircle; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mDrawCircle) { + final int width = getWidth(); + final int height = getHeight(); + int radius = Math.min(width, height) / 2; + canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint); + } + } +} diff --git a/src/com/android/datetimepicker/date/YearPickerView.java b/src/com/android/datetimepicker/date/YearPickerView.java index 5134879..10847f2 100644 --- a/src/com/android/datetimepicker/date/YearPickerView.java +++ b/src/com/android/datetimepicker/date/YearPickerView.java @@ -17,52 +17,129 @@ package com.android.datetimepicker.date; import android.content.Context; -import android.view.Gravity; +import android.content.res.Resources; +import android.graphics.drawable.StateListDrawable; +import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.NumberPicker; -import android.widget.NumberPicker.OnValueChangeListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.datetimepicker.R; +import com.android.datetimepicker.date.DatePickerDialog.OnDateChangedListener; + +import java.util.ArrayList; +import java.util.List; /** - * A number picker allowing a user to choose a specific year. + * Displays a selectable list of years. */ -public class YearPickerView extends FrameLayout implements OnValueChangeListener { +public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener { - private final NumberPicker mPicker; private final DatePickerController mController; + private YearAdapter mAdapter; + private int mViewSize; + private int mChildSize; + private TextViewWithCircularIndicator mSelectedView; + /** + * @param context + */ public YearPickerView(Context context, DatePickerController controller) { super(context); mController = controller; + mController.registerOnDateChangedListener(this); + setVerticalFadingEdgeEnabled(true); ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); - setLayoutParams(frame); - mPicker = new NumberPicker(context); - LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - params.gravity = Gravity.CENTER; - mPicker.setLayoutParams(params); - mPicker.setOnLongPressUpdateInterval(100); - mPicker.setMinValue(controller.getMinYear()); - mPicker.setMaxValue(controller.getMaxYear()); - mPicker.setWrapSelectorWheel(false); - mPicker.setValue(controller.getSelectedDay().year); - mPicker.setOnValueChangedListener(this); - addView(mPicker); + setLayoutParams(frame); + Resources res = context.getResources(); + mViewSize = res.getDimensionPixelOffset(R.dimen.pager_height); + mChildSize = res.getDimensionPixelOffset(R.dimen.year_label_height); + setFadingEdgeLength(mChildSize / 3); + init(context); + setOnItemClickListener(this); + setSelector(new StateListDrawable()); + setDividerHeight(0); + onDateChanged(); + } + + private void init(Context context) { + ArrayList years = new ArrayList(); + for (int year = mController.getMinYear(); year <= mController.getMaxYear(); year++) { + years.add(Integer.valueOf(year).toString()); + } + mAdapter = new YearAdapter(context, R.layout.year_label_text_view, years); + setAdapter(mAdapter); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + mController.tryVibrate(); + TextViewWithCircularIndicator clickedView = (TextViewWithCircularIndicator) view; + if (mSelectedView != clickedView) { + mSelectedView.drawIndicator(false); + mSelectedView.requestLayout(); + clickedView.drawIndicator(true); + clickedView.requestLayout(); + mSelectedView = clickedView; + } + mController.onYearSelected(getYearFromTextView(clickedView)); + mAdapter.notifyDataSetChanged(); } - public void setValue(int value) { - mPicker.setValue(value); + private int getYearFromTextView(TextView view) { + return Integer.valueOf(view.getText().toString()); } - public void onChange() { - mPicker.setMinValue(mController.getMinYear()); - mPicker.setMaxValue(mController.getMaxYear()); - requestLayout(); + private class YearAdapter extends ArrayAdapter { + + public YearAdapter(Context context, int resource, List objects) { + super(context, resource, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextViewWithCircularIndicator v = (TextViewWithCircularIndicator) + super.getView(position, convertView, parent); + v.requestLayout(); + int year = getYearFromTextView(v); + boolean selected = mController.getSelectedDay().year == year; + v.drawIndicator(selected); + if (selected) { + mSelectedView = v; + } + return v; + } + } + + public void postSetSelection(final int position) { + post(new Runnable() { + + @Override + public void run() { + setSelection(position); + requestLayout(); + } + }); + } + + public void postSetSelectionFromTop(final int position) { + post(new Runnable() { + + @Override + public void run() { + setSelectionFromTop(position, mViewSize / 2 - mChildSize / 2); + requestLayout(); + } + }); } @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - mController.onYearPickerSelectionChanged(newVal); + public void onDateChanged() { + mAdapter.notifyDataSetChanged(); + postSetSelectionFromTop(mController.getSelectedDay().year - mController.getMinYear()); } } diff --git a/src/com/android/datetimepicker/time/TimePickerDialog.java b/src/com/android/datetimepicker/time/TimePickerDialog.java index a5ef395..27742a2 100644 --- a/src/com/android/datetimepicker/time/TimePickerDialog.java +++ b/src/com/android/datetimepicker/time/TimePickerDialog.java @@ -36,9 +36,8 @@ import android.widget.RelativeLayout; import android.widget.TextView; import com.android.datetimepicker.R; - -import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListener; import com.android.datetimepicker.Utils; +import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListener; import java.text.DateFormatSymbols; import java.util.ArrayList; @@ -404,7 +403,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL mHourView.setTextColor(hourColor); mMinuteView.setTextColor(minuteColor); - ObjectAnimator pulseAnimator = Utils.getPulseAnimator(labelToAnimate); + ObjectAnimator pulseAnimator = Utils.getPulseAnimator(labelToAnimate, 0.85f, 1.1f); if (delayLabelAnimate) { pulseAnimator.setStartDelay(PULSE_ANIMATOR_DELAY); } -- cgit v1.2.3