diff --git a/library/res/color/done_text_color.xml b/library/res/color/done_text_color.xml
index e6e55ed4..85d0e9e9 100644
--- a/library/res/color/done_text_color.xml
+++ b/library/res/color/done_text_color.xml
@@ -16,6 +16,6 @@
-
+
\ No newline at end of file
diff --git a/library/res/color/done_text_color_dark.xml b/library/res/color/done_text_color_dark.xml
new file mode 100644
index 00000000..070baf2f
--- /dev/null
+++ b/library/res/color/done_text_color_dark.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/res/drawable/done_background_color.xml b/library/res/drawable/done_background_color.xml
new file mode 100644
index 00000000..19a38613
--- /dev/null
+++ b/library/res/drawable/done_background_color.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/res/drawable/done_background_color_dark.xml b/library/res/drawable/done_background_color_dark.xml
new file mode 100644
index 00000000..1665eee3
--- /dev/null
+++ b/library/res/drawable/done_background_color_dark.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/res/layout-land/radial_time_picker_dialog.xml b/library/res/layout-land/radial_time_picker_dialog.xml
index 02ec7d15..868764ba 100644
--- a/library/res/layout-land/radial_time_picker_dialog.xml
+++ b/library/res/layout-land/radial_time_picker_dialog.xml
@@ -15,57 +15,52 @@
~ limitations under the License
-->
+ android:id="@+id/time_picker_dialog"
+ android:layout_height="@dimen/dialog_height"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal"
+ android:focusable="true"
+ android:layout_marginLeft="@dimen/minimum_margin_sides"
+ android:layout_marginRight="@dimen/minimum_margin_sides"
+ android:layout_marginTop="@dimen/minimum_margin_top_bottom"
+ android:layout_marginBottom="@dimen/minimum_margin_top_bottom" >
+ android:layout_width="@dimen/left_side_width"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ android:id="@+id/time_display_background"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:background="@color/white" >
+ layout="@layout/radial_time_header_label"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/header_height"
+ android:layout_gravity="center" />
-
-
-
+ android:id="@+id/line"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="@color/line_background" />
+
+ android:id="@+id/time_picker"
+ android:layout_width="@dimen/picker_dimen"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:background="@color/circle_background" />
diff --git a/library/res/layout/radial_time_header_label.xml b/library/res/layout/radial_time_header_label.xml
index 211313c1..4e81dff9 100644
--- a/library/res/layout/radial_time_header_label.xml
+++ b/library/res/layout/radial_time_header_label.xml
@@ -15,6 +15,7 @@
~ limitations under the License
-->
@@ -36,25 +36,21 @@
android:layout_height="@dimen/picker_dimen"
android:layout_width="wrap_content"
android:layout_gravity="center"
+ android:background="@color/circle_background"
android:focusable="true"
android:focusableInTouchMode="true" />
-
-
-
+ android:minHeight="48dp"
+ android:text="@string/done_label"
+ android:textSize="@dimen/done_label_size"
+ android:textColor="@color/done_text_color" />
diff --git a/library/res/values-mdpi/dimens.xml b/library/res/values-mdpi/dimens.xml
new file mode 100644
index 00000000..3e9f210e
--- /dev/null
+++ b/library/res/values-mdpi/dimens.xml
@@ -0,0 +1,24 @@
+
+
+ 270dp
+ 30dp
+ 100dp
+ 270dp
+ 42dp
+
+ 50dp
+ 10sp
+ 16dp
+ 45dp
+ 24dp
+ 50dp
+ 24dp
+ 14dp
+ 16sp
+ 16sp
+ 64dp
+ 22dp
+
+ 340dp
+ 300dp
+
diff --git a/library/res/values-ru/strings.xml b/library/res/values-ru/strings.xml
new file mode 100644
index 00000000..a2c37230
--- /dev/null
+++ b/library/res/values-ru/strings.xml
@@ -0,0 +1,191 @@
+
+
+ Отмена
+ ОК
+ ч
+ м
+ с
+ часы
+ минуты
+ секунды
+ Удалить
+ --
+ :
+ :00
+ :30
+ —
+ .
+ Удалить
+ +/-
+ Ч
+ М
+ С
+ /
+ МЕСЯЦ
+ ДЕНЬ
+ ГОД
+
+
+ Пожалуйста, введите число больше или равно чем %1$d
+ Пожалуйста, введите число меньше или равно чем %1$d
+ Пожалуйста, введите число между %1$d и %2$d
+
+
+
+ Готово
+
+ Круговой ползунок часов
+
+ Круговой ползунок минут
+
+ Выберите час
+
+ Выберите минуты
+
+
+ Сетка дней месяца
+
+ Список годов
+
+ Выберите месяц и день
+
+ Выберите год
+
+ %1$s выбран
+
+ %1$s удален
+
+
+ --
+
+ :
+
+
+ sans-serif
+
+ sans-serif
+
+
+ sans-serif
+
+
+ тот же день каждый месяц
+
+
+ изменить конечную дату
+
+
+ Готово
+
+
+ Навсегда
+
+ До даты
+
+ До %s
+
+ Для ряда событий
+
+
+
+
+ Каждый %d день
+
+ Каждые %d дн.
+
+
+
+
+ Каждую %d неделю
+
+ Каждые %d нед.
+
+
+
+
+ Каждый %d месяц
+
+ Каждые %d мес.
+
+
+
+
+ Каждый %d год
+
+ Каждые %d лет
+
+
+
+
+ Для %d события
+ Для %d событий
+
+
+
+ ; до %s
+
+
+ ; один раз
+ ; для %d раз
+
+
+
+ Ежедневно
+ Каждые %d дн.
+
+
+ "Каждый будний день (Пон\u2013Пят)"
+
+
+ Еженедельно в %2$s
+ Каждые %1$d недель в %2$s
+
+
+ Ежемесячно
+
+ Ежегодно
+
+ Вкл.
+ Выкл.
+
+
+ Введите название страны
+
+
+ Очистить запрос
+
+
+ Результатов не найдено
+
+
+ Палестина
+
diff --git a/library/res/values/colors.xml b/library/res/values/colors.xml
index b6319817..5e25ef17 100644
--- a/library/res/values/colors.xml
+++ b/library/res/values/colors.xml
@@ -19,18 +19,30 @@
#f2f2f2#cccccc#8c8c8c
- #333333
- #CCCCCC
+ #000000
+ #cccccc#8c8c8c
- #7F000000
+ #7f000000#33b5e5
- #0099CC
- #FF999999
+ #c1e8f7
+ #33999999
+ #0099cc
+ #ff999999#999999#f2f2f2
- #FFD1D2D4
+ #ffd1d2d4
+
+
+ #ff3333
+ #853333
+ #404040
+ #363636
+ #808080
+ #ffffff
+ #888888
+ #bfbfbf#fff2f2f2#ff737373
diff --git a/library/src/com/doomonafireball/betterpickers/HapticFeedbackController.java b/library/src/com/doomonafireball/betterpickers/HapticFeedbackController.java
new file mode 100644
index 00000000..17cb1eee
--- /dev/null
+++ b/library/src/com/doomonafireball/betterpickers/HapticFeedbackController.java
@@ -0,0 +1,74 @@
+package com.doomonafireball.betterpickers;
+
+import android.app.Service;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/**
+ * A simple utility class to handle haptic feedback.
+ */
+public class HapticFeedbackController {
+ private static final int VIBRATE_DELAY_MS = 125;
+ private static final int VIBRATE_LENGTH_MS = 5;
+
+ private static boolean checkGlobalSetting(Context context) {
+ return Settings.System.getInt(context.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 1;
+ }
+
+ private final Context mContext;
+ private final ContentObserver mContentObserver;
+
+ private Vibrator mVibrator;
+ private boolean mIsGloballyEnabled;
+ private long mLastVibrate;
+
+ public HapticFeedbackController(Context context) {
+ mContext = context;
+ mContentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mIsGloballyEnabled = checkGlobalSetting(mContext);
+ }
+ };
+ }
+
+ /**
+ * Call to setup the controller.
+ */
+ public void start() {
+ mVibrator = (Vibrator) mContext.getSystemService(Service.VIBRATOR_SERVICE);
+
+ // Setup a listener for changes in haptic feedback settings
+ mIsGloballyEnabled = checkGlobalSetting(mContext);
+ Uri uri = Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED);
+ mContext.getContentResolver().registerContentObserver(uri, false, mContentObserver);
+ }
+
+ /**
+ * Call this when you don't need the controller anymore.
+ */
+ public void stop() {
+ mVibrator = null;
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ /**
+ * Try to vibrate. To prevent this becoming a single continuous vibration, nothing will
+ * happen if we have vibrated very recently.
+ */
+ public void tryVibrate() {
+ if (mVibrator != null && mIsGloballyEnabled) {
+ long now = SystemClock.uptimeMillis();
+ // We want to try to vibrate each individual tick discretely.
+ if (now - mLastVibrate >= VIBRATE_DELAY_MS) {
+ mVibrator.vibrate(VIBRATE_LENGTH_MS);
+ mLastVibrate = now;
+ }
+ }
+ }
+}
diff --git a/library/src/com/doomonafireball/betterpickers/Utils.java b/library/src/com/doomonafireball/betterpickers/Utils.java
index 54337f47..bbf2228e 100644
--- a/library/src/com/doomonafireball/betterpickers/Utils.java
+++ b/library/src/com/doomonafireball/betterpickers/Utils.java
@@ -36,6 +36,13 @@ public class Utils {
public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
public static final int PULSE_ANIMATOR_DURATION = 544;
+ // Alpha level for time picker selection.
+ public static final int SELECTED_ALPHA = 51;
+ public static final int SELECTED_ALPHA_THEME_DARK = 102;
+ // Alpha level for fully opaque.
+ public static final int FULL_ALPHA = 255;
+
+
static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
public static boolean isJellybeanOrLater() {
@@ -44,7 +51,6 @@ public static boolean isJellybeanOrLater() {
/**
* Try to speak the specified text, for accessibility. Only available on JB or later.
- *
* @param text Text to announce.
*/
@SuppressLint("NewApi")
@@ -77,10 +83,12 @@ public static int getDaysInMonth(int month, int year) {
}
/**
- * Takes a number of weeks since the epoch and calculates the Julian day of the Monday for that week.
+ * Takes a number of weeks since the epoch and calculates the Julian day of
+ * the Monday for that week.
*
- * This assumes that the week containing the {@link android.text.format.Time#EPOCH_JULIAN_DAY} is considered week 0.
- * It returns the Julian day for the Monday {@code week} weeks after the Monday of the week containing the epoch.
+ * This assumes that the week containing the {@link android.text.format.Time#EPOCH_JULIAN_DAY}
+ * is considered week 0. It returns the Julian day for the Monday
+ * {@code week} weeks after the Monday of the week containing the epoch.
*
* @param week Number of weeks since the epoch
* @return The julian day for the Monday of the given week since the epoch
@@ -90,15 +98,16 @@ public static int getJulianMondayFromWeeksSinceEpoch(int week) {
}
/**
- * Returns the week since {@link android.text.format.Time#EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted for first day of
- * week.
+ * Returns the week since {@link android.text.format.Time#EPOCH_JULIAN_DAY} (Jan 1, 1970)
+ * adjusted for first day of week.
*
- * This takes a julian day and the week start day and calculates which week since {@link
- * android.text.format.Time#EPOCH_JULIAN_DAY} that day occurs in, starting at 0. *Do not* use this to compute the
- * ISO week number for the year.
+ * This takes a julian day and the week start day and calculates which
+ * week since {@link android.text.format.Time#EPOCH_JULIAN_DAY} that day occurs in, starting
+ * at 0. *Do not* use this to compute the ISO week number for the year.
*
* @param julianDay The julian day to calculate the week number for
- * @param firstDayOfWeek Which week day is the first day of the week, see {@link android.text.format.Time#SUNDAY}
+ * @param firstDayOfWeek Which week day is the first day of the week,
+ * see {@link android.text.format.Time#SUNDAY}
* @return Weeks since the epoch
*/
public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
@@ -112,11 +121,11 @@ public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfW
/**
* Render an animator to pulsate a view in place.
- *
* @param labelToAnimate the view to pulsate.
* @return The animator object. Use .start() to begin.
*/
- public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio, float increaseRatio) {
+ public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio,
+ float increaseRatio) {
Keyframe k0 = Keyframe.ofFloat(0f, 1f);
Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio);
Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerController.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerController.java
index 190a16f3..bc0fd98e 100644
--- a/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerController.java
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerController.java
@@ -16,6 +16,9 @@
package com.doomonafireball.betterpickers.calendardatepicker;
+import com.doomonafireball.betterpickers.calendardatepicker.CalendarDatePickerDialog.OnDateChangedListener;
+import com.doomonafireball.betterpickers.calendardatepicker.MonthAdapter.CalendarDay;
+
/**
* Controller class to communicate among the various components of the date picker dialog.
*/
@@ -25,11 +28,11 @@ interface CalendarDatePickerController {
void onDayOfMonthSelected(int year, int month, int day);
- void registerOnDateChangedListener(CalendarDatePickerDialog.OnDateChangedListener listener);
+ void registerOnDateChangedListener(OnDateChangedListener listener);
- void unregisterOnDateChangedListener(CalendarDatePickerDialog.OnDateChangedListener listener);
+ void unregisterOnDateChangedListener(OnDateChangedListener listener);
- SimpleMonthAdapter.CalendarDay getSelectedDay();
+ CalendarDay getSelectedDay();
int getFirstDayOfWeek();
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerDialog.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerDialog.java
index 591e54c5..b42b17f5 100644
--- a/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerDialog.java
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/CalendarDatePickerDialog.java
@@ -16,16 +16,15 @@
package com.doomonafireball.betterpickers.calendardatepicker;
+import com.doomonafireball.betterpickers.HapticFeedbackController;
import com.doomonafireball.betterpickers.R;
import com.doomonafireball.betterpickers.Utils;
+import com.doomonafireball.betterpickers.calendardatepicker.MonthAdapter.CalendarDay;
import com.nineoldandroids.animation.ObjectAnimator;
import android.app.Activity;
-import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
-import android.os.SystemClock;
-import android.os.Vibrator;
import android.support.v4.app.DialogFragment;
import android.text.format.DateUtils;
import android.util.Log;
@@ -99,8 +98,7 @@ public class CalendarDatePickerDialog extends DialogFragment implements
private int mMinYear = DEFAULT_START_YEAR;
private int mMaxYear = DEFAULT_END_YEAR;
- private Vibrator mVibrator;
- private long mLastVibrate;
+ private HapticFeedbackController mHapticFeedbackController;
private boolean mDelayAnimation = true;
@@ -116,7 +114,7 @@ public class CalendarDatePickerDialog extends DialogFragment implements
public interface OnDateSetListener {
/**
- * @param DatePickerDialog The view associated with this listener.
+ * @param CalendarDatePickerDialog The view associated with this listener.
* @param year The year that was set.
* @param monthOfYear The month that was set (0-11) for compatibility with {@link java.util.Calendar}.
* @param dayOfMonth The day of the month that was set.
@@ -127,7 +125,7 @@ public interface OnDateSetListener {
/**
* The callback used to notify other date picker components of a change in selected date.
*/
- interface OnDateChangedListener {
+ public interface OnDateChangedListener {
public void onDateChanged();
}
@@ -164,7 +162,6 @@ public void onCreate(Bundle savedInstanceState) {
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));
@@ -221,7 +218,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
}
final Activity activity = getActivity();
- mDayPickerView = new DayPickerView(activity, this);
+ mDayPickerView = new SimpleDayPickerView(activity, this);
mYearPickerView = new YearPickerView(activity, this);
Resources res = getResources();
@@ -267,9 +264,23 @@ public void onClick(View v) {
mYearPickerView.postSetSelectionFromTop(listPosition, listPositionOffset);
}
}
+
+ mHapticFeedbackController = new HapticFeedbackController(activity);
return view;
}
+ @Override
+ public void onResume() {
+ super.onResume();
+ mHapticFeedbackController.start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHapticFeedbackController.stop();
+ }
+
private void setCurrentView(final int viewIndex) {
long millis = mCalendar.getTimeInMillis();
@@ -417,8 +428,8 @@ private void updatePickers() {
@Override
- public SimpleMonthAdapter.CalendarDay getSelectedDay() {
- return new SimpleMonthAdapter.CalendarDay(mCalendar);
+ public CalendarDay getSelectedDay() {
+ return new CalendarDay(mCalendar);
}
@Override
@@ -446,19 +457,8 @@ 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;
- }
- }
+ mHapticFeedbackController.tryVibrate();
}
}
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/DayPickerView.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/DayPickerView.java
index 2edd7056..0f340087 100644
--- a/library/src/com/doomonafireball/betterpickers/calendardatepicker/DayPickerView.java
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/DayPickerView.java
@@ -18,12 +18,14 @@
import com.doomonafireball.betterpickers.Utils;
import com.doomonafireball.betterpickers.calendardatepicker.CalendarDatePickerDialog.OnDateChangedListener;
+import com.doomonafireball.betterpickers.calendardatepicker.MonthAdapter.CalendarDay;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewConfiguration;
@@ -40,7 +42,7 @@
/**
* This displays a list of months in a calendar format with selectable days.
*/
-public class DayPickerView extends ListView implements OnScrollListener, OnDateChangedListener {
+public abstract class DayPickerView extends ListView implements OnScrollListener, OnDateChangedListener {
private static final String TAG = "MonthFragment";
@@ -68,12 +70,11 @@ public class DayPickerView extends ListView implements OnScrollListener, OnDateC
protected Handler mHandler;
// highlighted time
- protected SimpleMonthAdapter.CalendarDay mSelectedDay = new SimpleMonthAdapter.CalendarDay();
- protected SimpleMonthAdapter mAdapter;
+ protected CalendarDay mSelectedDay = new CalendarDay();
+ protected MonthAdapter mAdapter;
- protected SimpleMonthAdapter.CalendarDay mTempDay = new SimpleMonthAdapter.CalendarDay();
+ protected CalendarDay mTempDay = new CalendarDay();
- private static float mScale = 0;
// When the week starts; numbered like Time. (e.g. SUNDAY=0).
protected int mFirstDayOfWeek;
// The last name announced by accessibility
@@ -87,46 +88,57 @@ public class DayPickerView extends ListView implements OnScrollListener, OnDateC
// used for tracking what state listview is in
protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
- private final CalendarDatePickerController mController;
+ private CalendarDatePickerController mController;
private boolean mPerformingScroll;
+ public DayPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
public DayPickerView(Context context, CalendarDatePickerController controller) {
super(context);
- mHandler = new Handler();
+ init(context);
+ setController(controller);
+ }
+
+ public void setController(CalendarDatePickerController controller) {
mController = controller;
mController.registerOnDateChangedListener(this);
- setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- setDrawSelectorOnTop(false);
- init(context);
+ refreshAdapter();
onDateChanged();
}
public void init(Context context) {
+ mHandler = new Handler();
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setDrawSelectorOnTop(false);
+
mContext = context;
setUpListView();
- setUpAdapter();
- setAdapter(mAdapter);
}
public void onChange() {
- setUpAdapter();
- setAdapter(mAdapter);
+ refreshAdapter();
}
/**
- * Creates a new adapter if necessary and sets up its parameters. Override this method to provide a custom adapter.
+ * Creates a new adapter if necessary and sets up its parameters. Override
+ * this method to provide a custom adapter.
*/
- protected void setUpAdapter() {
+ protected void refreshAdapter() {
if (mAdapter == null) {
- mAdapter = new SimpleMonthAdapter(getContext(), mController);
+ mAdapter = createMonthAdapter(getContext(), mController);
} else {
mAdapter.setSelectedDay(mSelectedDay);
- mAdapter.notifyDataSetChanged();
}
// refresh the view with the new parameters
- mAdapter.notifyDataSetChanged();
+ setAdapter(mAdapter);
}
+ public abstract MonthAdapter createMonthAdapter(Context context,
+ CalendarDatePickerController controller);
+
/*
* Sets all the required fields for the list view. Override this method to
* set a different list view behavior.
@@ -161,7 +173,7 @@ protected void setUpListView() {
* @param forceScroll Whether to recenter even if the time is already visible
* @return Whether or not the view animated to the new location
*/
- public boolean goTo(SimpleMonthAdapter.CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {
+ public boolean goTo(CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {
// Set the selected day
if (setSelected) {
@@ -207,7 +219,7 @@ public boolean goTo(SimpleMonthAdapter.CalendarDay day, boolean animate, boolean
if (position != selectedPosition || forceScroll) {
setMonthDisplayed(mTempDay);
mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
- if (animate) {
+ if (animate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
smoothScrollToPositionFromTop(
position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
return true;
@@ -238,7 +250,7 @@ public void run() {
@Override
public void onScroll(
AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- SimpleMonthView child = (SimpleMonthView) view.getChildAt(0);
+ MonthView child = (MonthView) view.getChildAt(0);
if (child == null) {
return;
}
@@ -253,7 +265,7 @@ public void onScroll(
* 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(SimpleMonthAdapter.CalendarDay date) {
+ protected void setMonthDisplayed(CalendarDay date) {
mCurrentMonthDisplayed = date.month;
invalidateViews();
}
@@ -360,15 +372,17 @@ public void onDateChanged() {
*
* @return The date that has accessibility focus, or {@code null} if no date has focus.
*/
- private SimpleMonthAdapter.CalendarDay findAccessibilityFocus() {
+ private CalendarDay findAccessibilityFocus() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
- if (child instanceof SimpleMonthView) {
- final SimpleMonthAdapter.CalendarDay focus = ((SimpleMonthView) child).getAccessibilityFocus();
+ if (child instanceof MonthView) {
+ final CalendarDay focus = ((MonthView) child).getAccessibilityFocus();
if (focus != null) {
- // Clear focus to avoid ListView bug in Jelly Bean MR1.
- ((SimpleMonthView) child).clearAccessibilityFocus();
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Clear focus to avoid ListView bug in Jelly Bean MR1.
+ ((MonthView) child).clearAccessibilityFocus();
+ }
return focus;
}
}
@@ -383,7 +397,7 @@ private SimpleMonthAdapter.CalendarDay findAccessibilityFocus() {
* @param day The date that should receive accessibility focus
* @return {@code true} if focus was restored
*/
- private boolean restoreAccessibilityFocus(SimpleMonthAdapter.CalendarDay day) {
+ private boolean restoreAccessibilityFocus(CalendarDay day) {
if (day == null) {
return false;
}
@@ -391,8 +405,8 @@ private boolean restoreAccessibilityFocus(SimpleMonthAdapter.CalendarDay day) {
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)) {
+ if (child instanceof MonthView) {
+ if (((MonthView) child).restoreAccessibilityFocus(day)) {
return true;
}
}
@@ -403,7 +417,7 @@ private boolean restoreAccessibilityFocus(SimpleMonthAdapter.CalendarDay day) {
@Override
protected void layoutChildren() {
- final SimpleMonthAdapter.CalendarDay focusedDay = findAccessibilityFocus();
+ final CalendarDay focusedDay = findAccessibilityFocus();
super.layoutChildren();
if (mPerformingScroll) {
mPerformingScroll = false;
@@ -418,7 +432,7 @@ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
event.setItemCount(-1);
}
- private String getMonthAndYearString(SimpleMonthAdapter.CalendarDay day) {
+ private String getMonthAndYearString(CalendarDay day) {
Calendar cal = Calendar.getInstance();
cal.set(day.year, day.month, day.day);
@@ -454,7 +468,7 @@ public boolean performAccessibilityAction(int action, Bundle arguments) {
int firstVisiblePosition = getFirstVisiblePosition();
int month = firstVisiblePosition % 12;
int year = firstVisiblePosition / 12 + mController.getMinYear();
- SimpleMonthAdapter.CalendarDay day = new SimpleMonthAdapter.CalendarDay(year, month, 1);
+ CalendarDay day = new CalendarDay(year, month, 1);
// Scroll either forward or backward one month.
if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/MonthAdapter.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/MonthAdapter.java
new file mode 100644
index 00000000..f472d984
--- /dev/null
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/MonthAdapter.java
@@ -0,0 +1,224 @@
+/*
+ * 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.doomonafireball.betterpickers.calendardatepicker;
+
+import com.doomonafireball.betterpickers.calendardatepicker.MonthView.OnDayClickListener;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.text.format.Time;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView.LayoutParams;
+import android.widget.BaseAdapter;
+
+import java.util.Calendar;
+import java.util.HashMap;
+
+/**
+ * An adapter for a list of {@link MonthView} items.
+ */
+public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
+
+ private static final String TAG = "SimpleMonthAdapter";
+
+ private final Context mContext;
+ private final CalendarDatePickerController mController;
+
+ private CalendarDay mSelectedDay;
+
+ protected static int WEEK_7_OVERHANG_HEIGHT = 7;
+ protected static final int MONTHS_IN_YEAR = 12;
+
+ /**
+ * A convenience class to represent a specific date.
+ */
+ public static class CalendarDay {
+
+ private Calendar calendar;
+ private Time time;
+ int year;
+ int month;
+ int day;
+
+ public CalendarDay() {
+ setTime(System.currentTimeMillis());
+ }
+
+ public CalendarDay(long timeInMillis) {
+ setTime(timeInMillis);
+ }
+
+ public CalendarDay(Calendar calendar) {
+ year = calendar.get(Calendar.YEAR);
+ month = calendar.get(Calendar.MONTH);
+ day = calendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ public CalendarDay(int year, int month, int day) {
+ setDay(year, month, day);
+ }
+
+ public void set(CalendarDay date) {
+ year = date.year;
+ month = date.month;
+ day = date.day;
+ }
+
+ public void setDay(int year, int month, int day) {
+ this.year = year;
+ this.month = month;
+ this.day = day;
+ }
+
+ public synchronized void setJulianDay(int julianDay) {
+ if (time == null) {
+ time = new Time();
+ }
+ time.setJulianDay(julianDay);
+ setTime(time.toMillis(false));
+ }
+
+ private void setTime(long timeInMillis) {
+ if (calendar == null) {
+ calendar = Calendar.getInstance();
+ }
+ calendar.setTimeInMillis(timeInMillis);
+ month = calendar.get(Calendar.MONTH);
+ year = calendar.get(Calendar.YEAR);
+ day = calendar.get(Calendar.DAY_OF_MONTH);
+ }
+ }
+
+ public MonthAdapter(Context context, CalendarDatePickerController controller) {
+ mContext = context;
+ mController = controller;
+ init();
+ setSelectedDay(mController.getSelectedDay());
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param day The day to highlight
+ */
+ public void setSelectedDay(CalendarDay day) {
+ mSelectedDay = day;
+ notifyDataSetChanged();
+ }
+
+ public CalendarDay getSelectedDay() {
+ return mSelectedDay;
+ }
+
+ /**
+ * Set up the gesture detector and selected time
+ */
+ protected void init() {
+ mSelectedDay = new CalendarDay(System.currentTimeMillis());
+ }
+
+ @Override
+ public int getCount() {
+ return ((mController.getMaxYear() - mController.getMinYear()) + 1) * MONTHS_IN_YEAR;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ @SuppressWarnings("unchecked")
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ MonthView v;
+ HashMap drawingParams = null;
+ if (convertView != null) {
+ v = (MonthView) convertView;
+ // We store the drawing parameters in the view so it can be recycled
+ drawingParams = (HashMap) v.getTag();
+ } else {
+ v = createMonthView(mContext);
+ // Set up the new view
+ LayoutParams params = new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ v.setLayoutParams(params);
+ v.setClickable(true);
+ v.setOnDayClickListener(this);
+ }
+ if (drawingParams == null) {
+ drawingParams = new HashMap();
+ }
+ drawingParams.clear();
+
+ final int month = position % MONTHS_IN_YEAR;
+ final int year = position / MONTHS_IN_YEAR + mController.getMinYear();
+
+ int selectedDay = -1;
+ if (isSelectedDayInMonth(year, month)) {
+ selectedDay = mSelectedDay.day;
+ }
+
+ // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
+ // height/number of weeks before being displayed.
+ v.reuse();
+
+ drawingParams.put(MonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
+ drawingParams.put(MonthView.VIEW_PARAMS_YEAR, year);
+ drawingParams.put(MonthView.VIEW_PARAMS_MONTH, month);
+ drawingParams.put(MonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
+ v.setMonthParams(drawingParams);
+ v.invalidate();
+ return v;
+ }
+
+ public abstract MonthView createMonthView(Context context);
+
+ private boolean isSelectedDayInMonth(int year, int month) {
+ return mSelectedDay.year == year && mSelectedDay.month == month;
+ }
+
+
+ @Override
+ public void onDayClick(MonthView view, CalendarDay day) {
+ if (day != null) {
+ onDayTapped(day);
+ }
+ }
+
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ protected void onDayTapped(CalendarDay day) {
+ mController.tryVibrate();
+ mController.onDayOfMonthSelected(day.year, day.month, day.day);
+ setSelectedDay(day);
+ }
+}
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/MonthView.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/MonthView.java
new file mode 100644
index 00000000..ec2889df
--- /dev/null
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/MonthView.java
@@ -0,0 +1,681 @@
+/*
+ * 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.doomonafireball.betterpickers.calendardatepicker;
+
+import com.doomonafireball.betterpickers.R;
+import com.doomonafireball.betterpickers.Utils;
+import com.doomonafireball.betterpickers.calendardatepicker.MonthAdapter.CalendarDay;
+
+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.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.security.InvalidParameterException;
+import java.util.Calendar;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A calendar-like view displaying a specified month and the appropriate selectable day numbers within the specified
+ * month.
+ */
+public abstract class MonthView extends View {
+
+ private static final String TAG = "MonthView";
+
+ /**
+ * These params can be passed into the view to control how it appears.
+ * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
+ * values are unlikely to fit most layouts correctly.
+ */
+ /**
+ * This sets the height of this week in pixels
+ */
+ public static final String VIEW_PARAMS_HEIGHT = "height";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week, calculated using {@link
+ * Utils#getWeeksSinceEpochFromJulianDay}
+ */
+ public static final String VIEW_PARAMS_MONTH = "month";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week, calculated using {@link
+ * Utils#getWeeksSinceEpochFromJulianDay}
+ */
+ public static final String VIEW_PARAMS_YEAR = "year";
+ /**
+ * This sets one of the days in this view as selected {@link android.text.format.Time#SUNDAY} through {@link
+ * android.text.format.Time#SATURDAY}.
+ */
+ public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
+ /**
+ * Which day the week should start on. {@link android.text.format.Time#SUNDAY} through {@link
+ * android.text.format.Time#SATURDAY}.
+ */
+ public static final String VIEW_PARAMS_WEEK_START = "week_start";
+ /**
+ * How many days to display at a time. Days will be displayed starting with {@link #mWeekStart}.
+ */
+ public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
+ /**
+ * Which month is currently in focus, as defined by {@link android.text.format.Time#month} [0-11].
+ */
+ public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
+ /**
+ * If this month should display week numbers. false if 0, true otherwise.
+ */
+ public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
+
+ protected static int DEFAULT_HEIGHT = 32;
+ protected static int MIN_HEIGHT = 10;
+ protected static final int DEFAULT_SELECTED_DAY = -1;
+ protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
+ protected static final int DEFAULT_NUM_DAYS = 7;
+ protected static final int DEFAULT_SHOW_WK_NUM = 0;
+ protected static final int DEFAULT_FOCUS_MONTH = -1;
+ protected static final int DEFAULT_NUM_ROWS = 6;
+ protected static final int MAX_NUM_ROWS = 6;
+
+ private static final int SELECTED_CIRCLE_ALPHA = 60;
+
+ protected static int DAY_SEPARATOR_WIDTH = 1;
+ protected static int MINI_DAY_NUMBER_TEXT_SIZE;
+ protected static int MONTH_LABEL_TEXT_SIZE;
+ protected static int MONTH_DAY_LABEL_TEXT_SIZE;
+ protected static int MONTH_HEADER_SIZE;
+ protected static int DAY_SELECTED_CIRCLE_SIZE;
+
+ // used for scaling to the device density
+ protected static float mScale = 0;
+
+ // affects the padding on the sides of this view
+ protected int mPadding = 0;
+
+ private String mDayOfWeekTypeface;
+ private String mMonthTitleTypeface;
+
+ protected Paint mMonthNumPaint;
+ protected Paint mMonthTitlePaint;
+ protected Paint mMonthTitleBGPaint;
+ protected Paint mSelectedCirclePaint;
+ protected Paint mMonthDayLabelPaint;
+
+ private final Formatter mFormatter;
+ private final StringBuilder mStringBuilder;
+
+ // The Julian day of the first day displayed by this item
+ protected int mFirstJulianDay = -1;
+ // The month of the first day in this week
+ protected int mFirstMonth = -1;
+ // The month of the last day in this week
+ protected int mLastMonth = -1;
+
+ protected int mMonth;
+
+ protected int mYear;
+ // Quick reference to the width of this view, matches parent
+ protected int mWidth;
+ // The height this view should draw at in pixels, set by height param
+ protected int mRowHeight = DEFAULT_HEIGHT;
+ // If this view contains the today
+ protected boolean mHasToday = false;
+ // Which day is selected [0-6] or -1 if no day is selected
+ protected int mSelectedDay = -1;
+ // Which day is today [0-6] or -1 if no day is today
+ protected int mToday = DEFAULT_SELECTED_DAY;
+ // Which day of the week to start on [0-6]
+ protected int mWeekStart = DEFAULT_WEEK_START;
+ // How many days to display
+ protected int mNumDays = DEFAULT_NUM_DAYS;
+ // The number of days + a spot for week number if it is displayed
+ protected int mNumCells = mNumDays;
+ // The left edge of the selected day
+ protected int mSelectedLeft = -1;
+ // The right edge of the selected day
+ protected int mSelectedRight = -1;
+
+ private final Calendar mCalendar;
+ private final Calendar mDayLabelCalendar;
+ private final MonthViewTouchHelper mTouchHelper;
+
+ private int mNumRows = DEFAULT_NUM_ROWS;
+
+ // Optional listener for handling day click actions
+ private OnDayClickListener mOnDayClickListener;
+ // Whether to prevent setting the accessibility delegate
+ private boolean mLockAccessibilityDelegate;
+
+ protected int mDayTextColor;
+ protected int mTodayNumberColor;
+ protected int mMonthTitleColor;
+ protected int mMonthTitleBGColor;
+
+ public MonthView(Context context) {
+ super(context);
+
+ Resources res = context.getResources();
+
+ mDayLabelCalendar = Calendar.getInstance();
+ mCalendar = Calendar.getInstance();
+
+ 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);
+
+ mStringBuilder = new StringBuilder(50);
+ mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
+
+ MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.day_number_size);
+ MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_label_size);
+ MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_day_label_text_size);
+ MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.month_list_item_header_height);
+ DAY_SELECTED_CIRCLE_SIZE = res
+ .getDimensionPixelSize(R.dimen.day_number_select_circle_radius);
+
+ mRowHeight = (res.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height)
+ - MONTH_HEADER_SIZE) / MAX_NUM_ROWS;
+
+ // Set up accessibility components.
+ mTouchHelper = new MonthViewTouchHelper(this);
+ ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
+ ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mLockAccessibilityDelegate = true;
+
+ // Sets up any standard paints that will be used
+ initView();
+ }
+
+ @Override
+ public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+ // Workaround for a JB MR1 issue where accessibility delegates on
+ // top-level ListView items are overwritten.
+ if (!mLockAccessibilityDelegate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ super.setAccessibilityDelegate(delegate);
+ }
+ }
+
+ public void setOnDayClickListener(OnDayClickListener listener) {
+ mOnDayClickListener = listener;
+ }
+
+ /* Removed for backwards compatibility with Gingerbread
+ @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);
+ }*/
+
+ @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);
+ }
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Sets up the text and style properties for painting. Override this if you want to use a different paint.
+ */
+ protected void initView() {
+ mMonthTitlePaint = new Paint();
+ mMonthTitlePaint.setFakeBoldText(true);
+ mMonthTitlePaint.setAntiAlias(true);
+ mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
+ mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
+ mMonthTitlePaint.setColor(mDayTextColor);
+ mMonthTitlePaint.setTextAlign(Align.CENTER);
+ mMonthTitlePaint.setStyle(Style.FILL);
+
+ 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(mTodayNumberColor);
+ mSelectedCirclePaint.setTextAlign(Align.CENTER);
+ mSelectedCirclePaint.setStyle(Style.FILL);
+ mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+
+ mMonthDayLabelPaint = new Paint();
+ 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);
+
+ mMonthNumPaint = new Paint();
+ mMonthNumPaint.setAntiAlias(true);
+ mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
+ mMonthNumPaint.setStyle(Style.FILL);
+ mMonthNumPaint.setTextAlign(Align.CENTER);
+ mMonthNumPaint.setFakeBoldText(false);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawMonthTitle(canvas);
+ drawMonthDayLabels(canvas);
+ drawMonthNums(canvas);
+ }
+
+ private int mDayOfWeekStart = 0;
+
+ /**
+ * Sets all the parameters for displaying this week. The only required parameter is the week number. Other
+ * 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. See {@link #VIEW_PARAMS_HEIGHT} for more info on
+ * parameters.
+ *
+ * @param params A map of the new parameters, see {@link #VIEW_PARAMS_HEIGHT}
+ */
+ public void setMonthParams(HashMap params) {
+ if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
+ throw new InvalidParameterException("You must specify month and year for this view");
+ }
+ setTag(params);
+ // We keep the current value for any params not present
+ if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+ mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
+ if (mRowHeight < MIN_HEIGHT) {
+ mRowHeight = MIN_HEIGHT;
+ }
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
+ mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
+ }
+
+ // Allocate space for caching the day numbers and focus values
+ mMonth = params.get(VIEW_PARAMS_MONTH);
+ mYear = params.get(VIEW_PARAMS_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);
+ mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
+
+ if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+ mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+ } else {
+ mWeekStart = mCalendar.getFirstDayOfWeek();
+ }
+
+ mNumCells = Utils.getDaysInMonth(mMonth, mYear);
+ for (int i = 0; i < mNumCells; i++) {
+ final int day = i + 1;
+ if (sameDay(day, today)) {
+ mHasToday = true;
+ mToday = day;
+ }
+ }
+ mNumRows = calculateNumRows();
+
+ // Invalidate cached accessibility information.
+ mTouchHelper.invalidateRoot();
+ }
+
+ public void reuse() {
+ mNumRows = DEFAULT_NUM_ROWS;
+ requestLayout();
+ }
+
+ private int calculateNumRows() {
+ int offset = findDayOffset();
+ int dividend = (offset + mNumCells) / mNumDays;
+ int remainder = (offset + mNumCells) % mNumDays;
+ return (dividend + (remainder > 0 ? 1 : 0));
+ }
+
+ private boolean sameDay(int day, Time today) {
+ return mYear == today.year &&
+ mMonth == today.month &&
+ day == today.monthDay;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
+ + MONTH_HEADER_SIZE);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+
+ // 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) {
+ int x = (mWidth + 2 * mPadding) / 2;
+ int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3);
+ canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
+ }
+
+ private void drawMonthDayLabels(Canvas canvas) {
+ int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
+ int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+ for (int i = 0; i < mNumDays; i++) {
+ int calendarDay = (i + mWeekStart) % mNumDays;
+ int x = (2 * i + 1) * dayWidthHalf + mPadding;
+ mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
+ canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
+ mMonthDayLabelPaint);
+ }
+ }
+
+ /**
+ * Draws the week and month day numbers for this week. Override this method if you need different placement.
+ *
+ * @param canvas The canvas to draw on
+ */
+ protected void drawMonthNums(Canvas canvas) {
+ int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
+ + MONTH_HEADER_SIZE;
+ int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+ int j = findDayOffset();
+ for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
+ int x = (2 * j + 1) * dayWidthHalf + mPadding;
+
+ int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
+
+ int startX = x - dayWidthHalf;
+ int stopX = x + dayWidthHalf;
+ int startY = y - yRelativeToDay;
+ int stopY = startY + mRowHeight;
+
+ drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
+
+ j++;
+ if (j == mNumDays) {
+ j = 0;
+ y += mRowHeight;
+ }
+ }
+ }
+
+ /**
+ * This method should draw the month day. Implemented by sub-classes to allow customization.
+ *
+ * @param canvas The canvas to draw on
+ * @param year The year of this month day
+ * @param month The month of this month day
+ * @param day The day number of this month day
+ * @param x The default x position to draw the day number
+ * @param y The default y position to draw the day number
+ * @param startX The left boundary of the day number rect
+ * @param stopX The right boundary of the day number rect
+ * @param startY The top boundary of the day number rect
+ * @param stopY The bottom boundary of the day number rect
+ */
+ public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
+ int x, int y, int startX, int stopX, int startY, int stopY);
+
+ private int findDayOffset() {
+ return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
+ - mWeekStart;
+ }
+
+
+ /**
+ * 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.
+ *
+ * @param x The x position of the touch event
+ * @return The day number, or -1 if the position wasn't in a day
+ */
+ public int getDayFromLocation(float x, float y) {
+ int dayStart = mPadding;
+ if (x < dayStart || x > mWidth - mPadding) {
+ return -1;
+ }
+ // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+ int row = (int) (y - MONTH_HEADER_SIZE) / 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;
+ }
+
+ /**
+ * 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
+ */
+ private void onDayClick(int day) {
+ if (mOnDayClickListener != null) {
+ mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
+ }
+
+ // This is a no-op if accessibility is turned off.
+ mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
+
+ /**
+ * @return The date that has accessibility focus, or {@code null} if no date has focus
+ */
+ public CalendarDay getAccessibilityFocus() {
+ final int day = mTouchHelper.getFocusedVirtualView();
+ if (day >= 0) {
+ return new CalendarDay(mYear, mMonth, day);
+ }
+ return null;
+ }
+
+ /**
+ * 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
+ */
+ public boolean restoreAccessibilityFocus(CalendarDay day) {
+ if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
+ return false;
+ }
+ mTouchHelper.setFocusedVirtualView(day.day);
+ return true;
+ }
+
+ /**
+ * Provides a virtual view hierarchy for interfacing with an accessibility service.
+ */
+ private class MonthViewTouchHelper extends ExploreByTouchHelper {
+
+ private static final String DATE_FORMAT = "dd MMMM yyyy";
+
+ private final Rect mTempRect = new Rect();
+ private final Calendar mTempCalendar = Calendar.getInstance();
+
+ public MonthViewTouchHelper(View host) {
+ super(host);
+ }
+
+ public void setFocusedVirtualView(int virtualViewId) {
+ getAccessibilityNodeProvider(MonthView.this).performAction(
+ virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
+ public void clearFocusedVirtualView() {
+ final int focusedVirtualView = getFocusedVirtualView();
+ if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
+ getAccessibilityNodeProvider(MonthView.this).performAction(
+ focusedVirtualView,
+ AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+ null);
+ }
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ final int day = getDayFromLocation(x, y);
+ if (day >= 0) {
+ return day;
+ }
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List virtualViewIds) {
+ for (int day = 1; day <= mNumCells; day++) {
+ virtualViewIds.add(day);
+ }
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ event.setContentDescription(getItemDescription(virtualViewId));
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId,
+ AccessibilityNodeInfoCompat node) {
+ getItemBounds(virtualViewId, mTempRect);
+
+ node.setContentDescription(getItemDescription(virtualViewId));
+ node.setBoundsInParent(mTempRect);
+ node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+ if (virtualViewId == mSelectedDay) {
+ node.setSelected(true);
+ }
+
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+ Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK:
+ onDayClick(virtualViewId);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 = MONTH_HEADER_SIZE;
+ 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.
+ *
+ * @param day The day to generate a description for
+ * @return A description of the time object
+ */
+ private CharSequence getItemDescription(int day) {
+ mTempCalendar.set(mYear, mMonth, day);
+ final CharSequence date = DateFormat.format(DATE_FORMAT,
+ mTempCalendar.getTimeInMillis());
+
+ if (day == mSelectedDay) {
+ return getContext().getString(R.string.item_is_selected, date);
+ }
+
+ return date;
+ }
+ }
+
+ /**
+ * Handles callbacks when the user clicks on a time object.
+ */
+ public interface OnDayClickListener {
+
+ public void onDayClick(MonthView view, CalendarDay day);
+ }
+}
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleDayPickerView.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleDayPickerView.java
new file mode 100644
index 00000000..26a849c4
--- /dev/null
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleDayPickerView.java
@@ -0,0 +1,40 @@
+/*
+ * 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.doomonafireball.betterpickers.calendardatepicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * A DayPickerView customized for {@link SimpleMonthAdapter}
+ */
+public class SimpleDayPickerView extends DayPickerView {
+
+ public SimpleDayPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SimpleDayPickerView(Context context, CalendarDatePickerController controller) {
+ super(context, controller);
+ }
+
+ @Override
+ public MonthAdapter createMonthAdapter(Context context, CalendarDatePickerController controller) {
+ return new SimpleMonthAdapter(context, controller);
+ }
+
+}
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthAdapter.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthAdapter.java
index 8f42868f..642ad906 100644
--- a/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthAdapter.java
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthAdapter.java
@@ -16,193 +16,19 @@
package com.doomonafireball.betterpickers.calendardatepicker;
-import android.annotation.SuppressLint;
import android.content.Context;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView.LayoutParams;
-import android.widget.BaseAdapter;
-
-import java.util.Calendar;
-import java.util.HashMap;
/**
* An adapter for a list of {@link SimpleMonthView} items.
*/
-public class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayClickListener {
-
- private static final String TAG = "SimpleMonthAdapter";
-
- private final Context mContext;
- private final CalendarDatePickerController mController;
-
- private CalendarDay mSelectedDay;
-
- protected static int sWeek7OverhangHeight = 7;
- protected static final int MONTHS_IN_YEAR = 12;
-
- /**
- * A convenience class to represent a specific date.
- */
- public static class CalendarDay {
-
- private Calendar calendar;
- int year;
- int month;
- int day;
-
- public CalendarDay() {
- setTime(System.currentTimeMillis());
- }
-
- public CalendarDay(long timeInMillis) {
- setTime(timeInMillis);
- }
-
- public CalendarDay(Calendar calendar) {
- year = calendar.get(Calendar.YEAR);
- month = calendar.get(Calendar.MONTH);
- day = calendar.get(Calendar.DAY_OF_MONTH);
- }
-
- public CalendarDay(int year, int month, int day) {
- setDay(year, month, day);
- }
-
- public void set(CalendarDay date) {
- year = date.year;
- month = date.month;
- day = date.day;
- }
-
- public void setDay(int year, int month, int day) {
- this.year = year;
- this.month = month;
- this.day = day;
- }
-
- private void setTime(long timeInMillis) {
- if (calendar == null) {
- calendar = Calendar.getInstance();
- }
- calendar.setTimeInMillis(timeInMillis);
- month = calendar.get(Calendar.MONTH);
- year = calendar.get(Calendar.YEAR);
- day = calendar.get(Calendar.DAY_OF_MONTH);
- }
- }
-
- public SimpleMonthAdapter(Context context,
- CalendarDatePickerController controller) {
- mContext = context;
- mController = controller;
- init();
- setSelectedDay(mController.getSelectedDay());
- }
-
- /**
- * Updates the selected day and related parameters.
- *
- * @param day The day to highlight
- */
- public void setSelectedDay(CalendarDay day) {
- mSelectedDay = day;
- notifyDataSetChanged();
- }
-
- public CalendarDay getSelectedDay() {
- return mSelectedDay;
- }
+public class SimpleMonthAdapter extends MonthAdapter {
- /**
- * Set up the gesture detector and selected time
- */
- protected void init() {
- mSelectedDay = new CalendarDay(System.currentTimeMillis());
+ public SimpleMonthAdapter(Context context, CalendarDatePickerController controller) {
+ super(context, controller);
}
@Override
- public int getCount() {
- return ((mController.getMaxYear() - mController.getMinYear()) + 1) * MONTHS_IN_YEAR;
- }
-
- @Override
- public Object getItem(int position) {
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @SuppressLint("NewApi")
- @SuppressWarnings("unchecked")
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- SimpleMonthView v;
- HashMap drawingParams = null;
- if (convertView != null) {
- v = (SimpleMonthView) convertView;
- // We store the drawing parameters in the view so it can be recycled
- drawingParams = (HashMap) v.getTag();
- } else {
- v = new SimpleMonthView(mContext);
- // Set up the new view
- LayoutParams params = new LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- v.setLayoutParams(params);
- v.setClickable(true);
- v.setOnDayClickListener(this);
- }
- if (drawingParams == null) {
- drawingParams = new HashMap();
- }
- drawingParams.clear();
-
- final int month = position % MONTHS_IN_YEAR;
- final int year = position / MONTHS_IN_YEAR + mController.getMinYear();
- Log.d(TAG, "Year: " + year + ", Month: " + month);
-
- int selectedDay = -1;
- if (isSelectedDayInMonth(year, month)) {
- selectedDay = mSelectedDay.day;
- }
-
- // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
- // height/number of weeks before being displayed.
- v.reuse();
-
- drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
- drawingParams.put(SimpleMonthView.VIEW_PARAMS_YEAR, year);
- drawingParams.put(SimpleMonthView.VIEW_PARAMS_MONTH, month);
- drawingParams.put(SimpleMonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
- v.setMonthParams(drawingParams);
- v.invalidate();
- return v;
- }
-
- private boolean isSelectedDayInMonth(int year, int month) {
- return mSelectedDay.year == year && mSelectedDay.month == month;
- }
-
-
- @Override
- public void onDayClick(SimpleMonthView view, CalendarDay day) {
- if (day != null) {
- onDayTapped(day);
- }
- }
-
- /**
- * Maintains the same hour/min/sec but moves the day to the tapped day.
- *
- * @param day The day that was tapped
- */
- protected void onDayTapped(CalendarDay day) {
- mController.tryVibrate();
- mController.onDayOfMonthSelected(day.year, day.month, day.day);
- setSelectedDay(day);
+ public MonthView createMonthView(Context context) {
+ return new SimpleMonthView(context);
}
}
diff --git a/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthView.java b/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthView.java
index 92fe0239..d6b9d76c 100644
--- a/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthView.java
+++ b/library/src/com/doomonafireball/betterpickers/calendardatepicker/SimpleMonthView.java
@@ -16,662 +16,32 @@
package com.doomonafireball.betterpickers.calendardatepicker;
-import com.doomonafireball.betterpickers.R;
-import com.doomonafireball.betterpickers.TouchExplorationHelper;
-import com.doomonafireball.betterpickers.Utils;
-import com.doomonafireball.betterpickers.calendardatepicker.SimpleMonthAdapter.CalendarDay;
-
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.graphics.Rect;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.security.InvalidParameterException;
-import java.util.Calendar;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
/**
* A calendar-like view displaying a specified month and the appropriate selectable day numbers within the specified
* month.
*/
-public class SimpleMonthView extends View {
-
- private static final String TAG = "SimpleMonthView";
-
- /**
- * These params can be passed into the view to control how it appears.
- * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
- * values are unlikely to fit most layouts correctly.
- */
- /**
- * This sets the height of this week in pixels
- */
- public static final String VIEW_PARAMS_HEIGHT = "height";
- /**
- * This specifies the position (or weeks since the epoch) of this week, calculated using {@link
- * Utils#getWeeksSinceEpochFromJulianDay}
- */
- public static final String VIEW_PARAMS_MONTH = "month";
- /**
- * This specifies the position (or weeks since the epoch) of this week, calculated using {@link
- * Utils#getWeeksSinceEpochFromJulianDay}
- */
- public static final String VIEW_PARAMS_YEAR = "year";
- /**
- * This sets one of the days in this view as selected {@link android.text.format.Time#SUNDAY} through {@link
- * android.text.format.Time#SATURDAY}.
- */
- public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
- /**
- * Which day the week should start on. {@link android.text.format.Time#SUNDAY} through {@link
- * android.text.format.Time#SATURDAY}.
- */
- public static final String VIEW_PARAMS_WEEK_START = "week_start";
- /**
- * How many days to display at a time. Days will be displayed starting with {@link #mWeekStart}.
- */
- public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
- /**
- * Which month is currently in focus, as defined by {@link android.text.format.Time#month} [0-11].
- */
- public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
- /**
- * If this month should display week numbers. false if 0, true otherwise.
- */
- public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
-
- protected static final int DEFAULT_HEIGHT = 32;
- protected static final int MIN_HEIGHT = 10;
- protected static final int DEFAULT_SELECTED_DAY = -1;
- protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
- protected static final int DEFAULT_NUM_DAYS = 7;
- protected static final int DEFAULT_SHOW_WK_NUM = 0;
- protected static final int DEFAULT_FOCUS_MONTH = -1;
- protected static final int DEFAULT_NUM_ROWS = 6;
- protected static final int MAX_NUM_ROWS = 6;
-
- private static final int SELECTED_CIRCLE_ALPHA = 60;
-
- protected static final int DAY_SEPARATOR_WIDTH = 1;
- protected static int sMiniDayNumberTextSize;
- protected static int sMonthLabelTextSize;
- protected static int sMonthDayLabelTextSize;
- protected static int sMonthHeaderSize;
- protected static int sDaySelectedCircleSize;
-
- // used for scaling to the device density
- protected static float mScale = 0;
-
- // affects the padding on the sides of this view
- protected int mPadding = 0;
-
- private String mDayOfWeekTypeface;
- private String mMonthTitleTypeface;
-
- protected Paint mMonthNumPaint;
- protected Paint mMonthTitlePaint;
- protected Paint mMonthTitleBGPaint;
- protected Paint mSelectedCirclePaint;
- protected Paint mMonthDayLabelPaint;
-
- private final Formatter mFormatter;
- private final StringBuilder mStringBuilder;
-
- // The Julian day of the first day displayed by this item
- protected int mFirstJulianDay = -1;
- // The month of the first day in this week
- protected int mFirstMonth = -1;
- // The month of the last day in this week
- protected int mLastMonth = -1;
-
- protected int mMonth;
-
- protected int mYear;
- // Quick reference to the width of this view, matches parent
- protected int mWidth;
- // The height this view should draw at in pixels, set by height param
- protected int mRowHeight = DEFAULT_HEIGHT;
- // If this view contains the today
- protected boolean mHasToday = false;
- // Which day is selected [0-6] or -1 if no day is selected
- protected int mSelectedDay = -1;
- // Which day is today [0-6] or -1 if no day is today
- protected int mToday = DEFAULT_SELECTED_DAY;
- // Which day of the week to start on [0-6]
- protected int mWeekStart = DEFAULT_WEEK_START;
- // How many days to display
- protected int mNumDays = DEFAULT_NUM_DAYS;
- // The number of days + a spot for week number if it is displayed
- protected int mNumCells = mNumDays;
- // The left edge of the selected day
- protected int mSelectedLeft = -1;
- // The right edge of the selected day
- protected int mSelectedRight = -1;
-
- private final Calendar mCalendar;
- private final Calendar mDayLabelCalendar;
- private final MonthViewNodeProvider mNodeProvider;
-
- private int mNumRows = DEFAULT_NUM_ROWS;
-
- // Optional listener for handling day click actions
- private OnDayClickListener mOnDayClickListener;
- // Whether to prevent setting the accessibility delegate
- private boolean mLockAccessibilityDelegate;
-
- protected int mDayTextColor;
- protected int mTodayNumberColor;
- protected int mMonthTitleColor;
- protected int mMonthTitleBGColor;
+public class SimpleMonthView extends MonthView {
public SimpleMonthView(Context context) {
super(context);
-
- Resources res = context.getResources();
-
- mDayLabelCalendar = Calendar.getInstance();
- mCalendar = Calendar.getInstance();
-
- 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);
-
- mStringBuilder = new StringBuilder(50);
- mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
-
- sMiniDayNumberTextSize = res.getDimensionPixelSize(R.dimen.day_number_size);
- sMonthLabelTextSize = res.getDimensionPixelSize(R.dimen.month_label_size);
- sMonthDayLabelTextSize = res.getDimensionPixelSize(R.dimen.month_day_label_text_size);
- sMonthHeaderSize = res.getDimensionPixelOffset(R.dimen.month_list_item_header_height);
- sDaySelectedCircleSize = res
- .getDimensionPixelSize(R.dimen.day_number_select_circle_radius);
-
- mRowHeight = (res.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height)
- - sMonthHeaderSize) / MAX_NUM_ROWS;
-
- // Set up accessibility components.
- mNodeProvider = new MonthViewNodeProvider(context, this);
- ViewCompat.setAccessibilityDelegate(this, mNodeProvider.getAccessibilityDelegate());
- ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
- mLockAccessibilityDelegate = true;
-
- // Sets up any standard paints that will be used
- initView();
}
@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);
+ public void drawMonthDay(Canvas canvas, int year, int month, int day,
+ int x, int y, int startX, int stopX, int startY, int stopY) {
+ if (mSelectedDay == day) {
+ canvas.drawCircle(x, y - (MINI_DAY_NUMBER_TEXT_SIZE / 3), DAY_SELECTED_CIRCLE_SIZE,
+ mSelectedCirclePaint);
}
- }
- public void setOnDayClickListener(OnDayClickListener listener) {
- mOnDayClickListener = listener;
- }
-
- /* Removed for backwards compatibility with Gingerbread
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- // First right-of-refusal goes the touch exploration helper.
- if (mNodeProvider.onHover(this, event)) {
- return true;
- }
- return super.onHoverEvent(event);
- }*/
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- final CalendarDay day = getDayFromLocation(event.getX(), event.getY());
- if (day != null) {
- onDayClick(day);
- }
- break;
- }
- return true;
- }
-
- /**
- * Sets up the text and style properties for painting. Override this if you want to use a different paint.
- */
- protected void initView() {
- mMonthTitlePaint = new Paint();
- mMonthTitlePaint.setFakeBoldText(true);
- mMonthTitlePaint.setAntiAlias(true);
- mMonthTitlePaint.setTextSize(sMonthLabelTextSize);
- mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
- mMonthTitlePaint.setColor(mDayTextColor);
- mMonthTitlePaint.setTextAlign(Align.CENTER);
- mMonthTitlePaint.setStyle(Style.FILL);
-
- 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(mTodayNumberColor);
- mSelectedCirclePaint.setTextAlign(Align.CENTER);
- mSelectedCirclePaint.setStyle(Style.FILL);
- mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
-
- mMonthDayLabelPaint = new Paint();
- mMonthDayLabelPaint.setAntiAlias(true);
- mMonthDayLabelPaint.setTextSize(sMonthDayLabelTextSize);
- mMonthDayLabelPaint.setColor(mDayTextColor);
- mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
- mMonthDayLabelPaint.setStyle(Style.FILL);
- mMonthDayLabelPaint.setTextAlign(Align.CENTER);
- mMonthDayLabelPaint.setFakeBoldText(true);
-
- mMonthNumPaint = new Paint();
- mMonthNumPaint.setAntiAlias(true);
- mMonthNumPaint.setTextSize(sMiniDayNumberTextSize);
- mMonthNumPaint.setStyle(Style.FILL);
- mMonthNumPaint.setTextAlign(Align.CENTER);
- mMonthNumPaint.setFakeBoldText(false);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- drawMonthTitle(canvas);
- drawMonthDayLabels(canvas);
- drawMonthNums(canvas);
- }
-
- private int mDayOfWeekStart = 0;
-
- /**
- * Sets all the parameters for displaying this week. The only required parameter is the week number. Other
- * 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. See {@link #VIEW_PARAMS_HEIGHT} for more info on
- * parameters.
- *
- * @param params A map of the new parameters, see {@link #VIEW_PARAMS_HEIGHT}
- * @param tz The time zone this view should reference times in
- */
- public void setMonthParams(HashMap params) {
- if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
- throw new InvalidParameterException("You must specify the month and year for this view");
- }
- setTag(params);
- // We keep the current value for any params not present
- if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
- mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
- if (mRowHeight < MIN_HEIGHT) {
- mRowHeight = MIN_HEIGHT;
- }
- }
- if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
- mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
- }
-
- // Allocate space for caching the day numbers and focus values
- mMonth = params.get(VIEW_PARAMS_MONTH);
- mYear = params.get(VIEW_PARAMS_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);
- mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
-
- if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
- mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+ if (mHasToday && mToday == day) {
+ mMonthNumPaint.setColor(mTodayNumberColor);
} else {
- mWeekStart = mCalendar.getFirstDayOfWeek();
- }
-
- mNumCells = Utils.getDaysInMonth(mMonth, mYear);
- for (int i = 0; i < mNumCells; i++) {
- final int day = i + 1;
- if (sameDay(day, today)) {
- mHasToday = true;
- mToday = day;
- }
- }
- mNumRows = calculateNumRows();
-
- // Invalidate cached accessibility information.
- mNodeProvider.invalidateParent();
- }
-
- public void reuse() {
- mNumRows = DEFAULT_NUM_ROWS;
- requestLayout();
- }
-
- private int calculateNumRows() {
- int offset = findDayOffset();
- int dividend = (offset + mNumCells) / mNumDays;
- int remainder = (offset + mNumCells) % mNumDays;
- return (dividend + (remainder > 0 ? 1 : 0));
- }
-
- private boolean sameDay(int day, Time today) {
- return mYear == today.year &&
- mMonth == today.month &&
- day == today.monthDay;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
- + sMonthHeaderSize);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mWidth = w;
-
- // Invalidate cached accessibility information.
- mNodeProvider.invalidateParent();
- }
-
- 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) {
- int x = (mWidth + 2 * mPadding) / 2;
- int y = (sMonthHeaderSize - sMonthDayLabelTextSize) / 2 + (sMonthLabelTextSize / 3);
- canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
- }
-
- private void drawMonthDayLabels(Canvas canvas) {
- int y = sMonthHeaderSize - (sMonthDayLabelTextSize / 2);
- int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
-
- for (int i = 0; i < mNumDays; i++) {
- int calendarDay = (i + mWeekStart) % mNumDays;
- int x = (2 * i + 1) * dayWidthHalf + mPadding;
- mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
- canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
- Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
- mMonthDayLabelPaint);
- }
- }
-
- /**
- * Draws the week and month day numbers for this week. Override this method if you need different placement.
- *
- * @param canvas The canvas to draw on
- */
- protected void drawMonthNums(Canvas canvas) {
- int y = (((mRowHeight + sMiniDayNumberTextSize) / 2) - DAY_SEPARATOR_WIDTH)
- + sMonthHeaderSize;
- int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
- int j = findDayOffset();
- for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
- int x = (2 * j + 1) * dayWidthHalf + mPadding;
- if (mSelectedDay == dayNumber) {
- canvas.drawCircle(x, y - (sMiniDayNumberTextSize / 3), sDaySelectedCircleSize,
- mSelectedCirclePaint);
- }
-
- if (mHasToday && mToday == dayNumber) {
- mMonthNumPaint.setColor(mTodayNumberColor);
- } else {
- mMonthNumPaint.setColor(mDayTextColor);
- }
- canvas.drawText(String.format("%d", dayNumber), x, y, mMonthNumPaint);
- j++;
- if (j == mNumDays) {
- j = 0;
- y += mRowHeight;
- }
- }
- }
-
- private int findDayOffset() {
- return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
- - mWeekStart;
- }
-
-
- /**
- * Calculates the day that the given x position is in, accounting for week number. Returns a Time referencing that
- * day or null if
- *
- * @param x The x position of the touch event
- * @return A time object for the tapped day or null if the position wasn't in a day
- */
- public CalendarDay getDayFromLocation(float x, float y) {
- int dayStart = mPadding;
- if (x < dayStart || x > mWidth - mPadding) {
- return null;
+ mMonthNumPaint.setColor(mDayTextColor);
}
- // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
- int row = (int) (y - sMonthHeaderSize) / mRowHeight;
- int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
-
- int day = column - findDayOffset() + 1;
- day += row * mNumDays;
- if (day < 1 || day > mNumCells) {
- return null;
- }
- return new CalendarDay(mYear, mMonth, day);
- }
-
- /**
- * Called when the user clicks on a day. Handles callbacks to the {@link OnDayClickListener} if one is set.
- *
- * @param day A time object representing the day that was clicked
- */
- private void onDayClick(CalendarDay day) {
- if (mOnDayClickListener != null) {
- mOnDayClickListener.onDayClick(this, day);
- }
-
- // This is a no-op if accessibility is turned off.
- mNodeProvider.sendEventForItem(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
- }
-
- /**
- * @return The date that has accessibility focus, or {@code null} if no date has focus
- */
- public CalendarDay getAccessibilityFocus() {
- return mNodeProvider.getFocusedItem();
- }
-
- /**
- * Clears accessibility focus within the view. No-op if the view does not contain accessibility focus.
- */
- public void clearAccessibilityFocus() {
- mNodeProvider.clearFocusedItem();
- }
-
- /**
- * 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
- */
- public boolean restoreAccessibilityFocus(CalendarDay day) {
- if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
- return false;
- }
-
- mNodeProvider.setFocusedItem(day);
- return true;
- }
-
- /**
- * Provides a virtual view hierarchy for interfacing with an accessibility service.
- */
- private class MonthViewNodeProvider extends TouchExplorationHelper {
-
- private final SparseArray mCachedItems = new SparseArray();
- private final Rect mTempRect = new Rect();
-
- Calendar recycle;
-
- public MonthViewNodeProvider(Context context, View parent) {
- super(context, parent);
- }
-
- @Override
- public void invalidateItem(CalendarDay item) {
- super.invalidateItem(item);
- mCachedItems.delete(getIdForItem(item));
- }
-
- @Override
- public void invalidateParent() {
- super.invalidateParent();
- mCachedItems.clear();
- }
-
- @Override
- protected boolean performActionForItem(CalendarDay item, int action, Bundle arguments) {
- switch (action) {
- case AccessibilityNodeInfoCompat.ACTION_CLICK:
- onDayClick(item);
- return true;
- }
-
- return false;
- }
-
- @Override
- protected void populateEventForItem(CalendarDay item, AccessibilityEvent event) {
- event.setContentDescription(getItemDescription(item));
- }
-
- @Override
- protected void populateNodeForItem(CalendarDay item, AccessibilityNodeInfoCompat node) {
- getItemBounds(item, mTempRect);
-
- node.setContentDescription(getItemDescription(item));
- node.setBoundsInParent(mTempRect);
- node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
-
- if (item.day == mSelectedDay) {
- node.setSelected(true);
- }
- }
-
- @Override
- protected void getVisibleItems(List items) {
- // TODO: Optimize, only return items visible within parent bounds.
- for (int day = 1; day <= mNumCells; day++) {
- items.add(getItemForId(day));
- }
- }
-
- @Override
- protected CalendarDay getItemAt(float x, float y) {
- return getDayFromLocation(x, y);
- }
-
- @Override
- protected int getIdForItem(CalendarDay item) {
- return item.day;
- }
-
- @Override
- protected CalendarDay getItemForId(int id) {
- if ((id < 1) || (id > mNumCells)) {
- return null;
- }
-
- final CalendarDay item;
- if (mCachedItems.indexOfKey(id) >= 0) {
- item = mCachedItems.get(id);
- } else {
- item = new CalendarDay(mYear, mMonth, id);
- mCachedItems.put(id, item);
- }
-
- return item;
- }
-
- /**
- * Calculates the bounding rectangle of a given time object.
- *
- * @param item The time object to calculate bounds for
- * @param rect The rectangle in which to store the bounds
- */
- private void getItemBounds(CalendarDay item, Rect rect) {
- final int offsetX = mPadding;
- final int offsetY = sMonthHeaderSize;
- final int cellHeight = mRowHeight;
- final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
- final int index = ((item.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.
- *
- * @param item The time object to generate a description for
- * @return A description of the time object
- */
- private CharSequence getItemDescription(CalendarDay item) {
- if (recycle == null) {
- recycle = Calendar.getInstance();
- }
- recycle.set(item.year, item.month, item.day);
- CharSequence date = DateFormat.format("dd MMMM yyyy", recycle.getTimeInMillis());
-
- if (item.day == mSelectedDay) {
- return getContext().getString(R.string.item_is_selected, date);
- }
-
- return date;
- }
- }
-
- /**
- * Handles callbacks when the user clicks on a time object.
- */
- public interface OnDayClickListener {
-
- public void onDayClick(SimpleMonthView view, CalendarDay day);
+ canvas.drawText(String.format("%d", day), x, y, mMonthNumPaint);
}
}
diff --git a/library/src/com/doomonafireball/betterpickers/radialtimepicker/AmPmCirclesView.java b/library/src/com/doomonafireball/betterpickers/radialtimepicker/AmPmCirclesView.java
index f9d528c9..54cd37cd 100644
--- a/library/src/com/doomonafireball/betterpickers/radialtimepicker/AmPmCirclesView.java
+++ b/library/src/com/doomonafireball/betterpickers/radialtimepicker/AmPmCirclesView.java
@@ -17,6 +17,7 @@
package com.doomonafireball.betterpickers.radialtimepicker;
import com.doomonafireball.betterpickers.R;
+import com.doomonafireball.betterpickers.Utils;
import android.content.Context;
import android.content.res.Resources;
@@ -36,15 +37,15 @@ public class AmPmCirclesView extends View {
private static final String TAG = "AmPmCirclesView";
- // Alpha level of blue color for selected circle.
- private static final int SELECTED_ALPHA = 51;
- // Alpha level of blue color for pressed circle.
- private static final int PRESSED_ALPHA = 175;
+ // Alpha level for selected circle.
+ private static final int SELECTED_ALPHA = Utils.SELECTED_ALPHA;
+ private static final int SELECTED_ALPHA_THEME_DARK = Utils.SELECTED_ALPHA_THEME_DARK;
private final Paint mPaint = new Paint();
- private int mWhite;
+ private int mSelectedAlpha;
+ private int mUnselectedColor;
private int mAmPmTextColor;
- private int mBlue;
+ private int mSelectedColor;
private float mCircleRadiusMultiplier;
private float mAmPmCircleRadiusMultiplier;
private String mAmText;
@@ -74,9 +75,10 @@ public void initialize(Context context, int amOrPm) {
}
Resources res = context.getResources();
- mWhite = res.getColor(R.color.white);
+ mUnselectedColor = res.getColor(R.color.white);
+ mSelectedColor = res.getColor(R.color.blue);
mAmPmTextColor = res.getColor(R.color.ampm_text_color);
- mBlue = res.getColor(R.color.blue);
+ mSelectedAlpha = SELECTED_ALPHA;
String typefaceFamily = res.getString(R.string.sans_serif);
Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL);
mPaint.setTypeface(tf);
@@ -97,6 +99,21 @@ public void initialize(Context context, int amOrPm) {
mIsInitialized = true;
}
+ /* package */ void setTheme(Context context, boolean themeDark) {
+ Resources res = context.getResources();
+ if (themeDark) {
+ mUnselectedColor = res.getColor(R.color.dark_gray);
+ mSelectedColor = res.getColor(R.color.red);
+ mAmPmTextColor = res.getColor(R.color.white);
+ mSelectedAlpha = SELECTED_ALPHA_THEME_DARK;
+ } else {
+ mUnselectedColor = res.getColor(R.color.white);
+ mSelectedColor = res.getColor(R.color.blue);
+ mAmPmTextColor = res.getColor(R.color.ampm_text_color);
+ mSelectedAlpha = SELECTED_ALPHA;
+ }
+ }
+
public void setAmOrPm(int amOrPm) {
mAmOrPm = amOrPm;
}
@@ -159,23 +176,23 @@ public void onDraw(Canvas canvas) {
// We'll need to draw either a lighter blue (for selection), a darker blue (for touching)
// or white (for not selected).
- int amColor = mWhite;
+ int amColor = mUnselectedColor;
int amAlpha = 255;
- int pmColor = mWhite;
+ int pmColor = mUnselectedColor;
int pmAlpha = 255;
if (mAmOrPm == AM) {
- amColor = mBlue;
- amAlpha = SELECTED_ALPHA;
+ amColor = mSelectedColor;
+ amAlpha = mSelectedAlpha;
} else if (mAmOrPm == PM) {
- pmColor = mBlue;
- pmAlpha = SELECTED_ALPHA;
+ pmColor = mSelectedColor;
+ pmAlpha = mSelectedAlpha;
}
if (mAmOrPmPressed == AM) {
- amColor = mBlue;
- amAlpha = PRESSED_ALPHA;
+ amColor = mSelectedColor;
+ amAlpha = mSelectedAlpha;
} else if (mAmOrPmPressed == PM) {
- pmColor = mBlue;
- pmAlpha = PRESSED_ALPHA;
+ pmColor = mSelectedColor;
+ pmAlpha = mSelectedAlpha;
}
// Draw the two circles.
diff --git a/library/src/com/doomonafireball/betterpickers/radialtimepicker/CircleView.java b/library/src/com/doomonafireball/betterpickers/radialtimepicker/CircleView.java
index 588ba399..4c92302f 100644
--- a/library/src/com/doomonafireball/betterpickers/radialtimepicker/CircleView.java
+++ b/library/src/com/doomonafireball/betterpickers/radialtimepicker/CircleView.java
@@ -34,8 +34,8 @@ public class CircleView extends View {
private final Paint mPaint = new Paint();
private boolean mIs24HourMode;
- private int mWhite;
- private int mBlack;
+ private int mCircleColor;
+ private int mDotColor;
private float mCircleRadiusMultiplier;
private float mAmPmCircleRadiusMultiplier;
private boolean mIsInitialized;
@@ -49,8 +49,8 @@ public CircleView(Context context) {
super(context);
Resources res = context.getResources();
- mWhite = res.getColor(R.color.white);
- mBlack = res.getColor(R.color.numbers_text_color);
+ mCircleColor = res.getColor(R.color.white);
+ mDotColor = res.getColor(R.color.numbers_text_color);
mPaint.setAntiAlias(true);
mIsInitialized = false;
@@ -77,6 +77,16 @@ public void initialize(Context context, boolean is24HourMode) {
mIsInitialized = true;
}
+ /* package */ void setTheme(Context context, boolean dark) {
+ Resources res = context.getResources();
+ if (dark) {
+ mCircleColor = res.getColor(R.color.dark_gray);
+ mDotColor = res.getColor(R.color.light_gray);
+ } else {
+ mCircleColor = res.getColor(R.color.white);
+ mDotColor = res.getColor(R.color.numbers_text_color);
+ }
+ }
@Override
public void onDraw(Canvas canvas) {
@@ -102,11 +112,11 @@ public void onDraw(Canvas canvas) {
}
// Draw the white circle.
- mPaint.setColor(mWhite);
+ mPaint.setColor(mCircleColor);
canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaint);
// Draw a small black circle in the center.
- mPaint.setColor(mBlack);
+ mPaint.setColor(mDotColor);
canvas.drawCircle(mXCenter, mYCenter, 2, mPaint);
}
}
diff --git a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialPickerLayout.java b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialPickerLayout.java
index 55e91fb2..385b640f 100644
--- a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialPickerLayout.java
+++ b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialPickerLayout.java
@@ -16,19 +16,18 @@
package com.doomonafireball.betterpickers.radialtimepicker;
+import com.doomonafireball.betterpickers.HapticFeedbackController;
import com.doomonafireball.betterpickers.R;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.view.ViewHelper;
import android.annotation.SuppressLint;
-import android.app.Service;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
-import android.os.Vibrator;
import android.support.v4.view.accessibility.AccessibilityManagerCompat;
import android.text.format.DateUtils;
import android.text.format.Time;
@@ -48,8 +47,8 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private static final String TAG = "RadialPickerLayout";
- private final int touchSlop;
- private final int tapTimeout;
+ private final int TOUCH_SLOP;
+ private final int TAP_TIMEOUT;
private static final int VISIBLE_DEGREES_STEP_SIZE = 30;
private static final int HOUR_VALUE_TO_DEGREES_STEP_SIZE = VISIBLE_DEGREES_STEP_SIZE;
@@ -61,10 +60,9 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private static final int AM = RadialTimePickerDialog.AM;
private static final int PM = RadialTimePickerDialog.PM;
- private Vibrator mVibrator;
- private long mLastVibrate;
private int mLastValueSelected;
+ private HapticFeedbackController mHapticFeedbackController;
private OnValueSelectedListener mListener;
private boolean mTimeInitialized;
private int mCurrentHoursOfDay;
@@ -104,8 +102,8 @@ public RadialPickerLayout(Context context, AttributeSet attrs) {
setOnTouchListener(this);
ViewConfiguration vc = ViewConfiguration.get(context);
- touchSlop = vc.getScaledTouchSlop();
- tapTimeout = ViewConfiguration.getTapTimeout();
+ TOUCH_SLOP = vc.getScaledTouchSlop();
+ TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
mDoingMove = false;
mCircleView = new CircleView(context);
@@ -127,8 +125,6 @@ public RadialPickerLayout(Context context, AttributeSet attrs) {
// Prepare mapping to snap touchable degrees to selectable degrees.
preparePrefer30sMap();
- mVibrator = (Vibrator) context.getSystemService(Service.VIBRATOR_SERVICE);
- mLastVibrate = 0;
mLastValueSelected = -1;
mInputEnabled = true;
@@ -166,12 +162,14 @@ public void setOnValueSelectedListener(OnValueSelectedListener listener) {
/**
* Initialize the Layout with starting values.
*/
- public void initialize(Context context, int initialHoursOfDay, int initialMinutes,
- boolean is24HourMode) {
+ public void initialize(Context context, HapticFeedbackController hapticFeedbackController, int initialHoursOfDay,
+ int initialMinutes, boolean is24HourMode) {
if (mTimeInitialized) {
Log.e(TAG, "Time has already been initialized.");
return;
}
+
+ mHapticFeedbackController = hapticFeedbackController;
mIs24HourMode = is24HourMode;
mHideAmPm = AccessibilityManagerCompat.isTouchExplorationEnabled(mAccessibilityManager) ? true : mIs24HourMode;
@@ -216,6 +214,15 @@ public void initialize(Context context, int initialHoursOfDay, int initialMinute
mTimeInitialized = true;
}
+ /* package */ void setTheme(Context context, boolean themeDark) {
+ mCircleView.setTheme(context, themeDark);
+ mAmPmCirclesView.setTheme(context, themeDark);
+ mHourRadialTextsView.setTheme(context, themeDark);
+ mMinuteRadialTextsView.setTheme(context, themeDark);
+ mHourRadialSelectorView.setTheme(context, themeDark);
+ mMinuteRadialSelectorView.setTheme(context, themeDark);
+ }
+
public void setTime(int hours, int minutes) {
setItem(HOUR_INDEX, hours);
setItem(MINUTE_INDEX, minutes);
@@ -298,6 +305,7 @@ private void setValueForItem(int index, int value) {
/**
* Set the internal value as either AM or PM, and update the AM/PM circle displays.
+ * @param amOrPm
*/
public void setAmOrPm(int amOrPm) {
mAmPmCirclesView.setAmOrPm(amOrPm);
@@ -555,8 +563,6 @@ public boolean onTouch(View v, MotionEvent event) {
final Boolean[] isInnerCircle = new Boolean[1];
isInnerCircle[0] = false;
- long millis = SystemClock.uptimeMillis();
-
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mInputEnabled) {
@@ -578,7 +584,7 @@ public boolean onTouch(View v, MotionEvent event) {
if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
// If the touch is on AM or PM, set it as "touched" after the tapTimeout
// in case the user moves their finger quickly.
- tryVibrate();
+ mHapticFeedbackController.tryVibrate();
mDownDegrees = -1;
mHandler.postDelayed(new Runnable() {
@Override
@@ -586,7 +592,7 @@ public void run() {
mAmPmCirclesView.setAmOrPmPressed(mIsTouchingAmOrPm);
mAmPmCirclesView.invalidate();
}
- }, tapTimeout);
+ }, TAP_TIMEOUT);
} else {
// If we're in accessibility mode, force the touch to be legal. Otherwise,
// it will only register within the given touch target zone.
@@ -596,7 +602,7 @@ public void run() {
if (mDownDegrees != -1) {
// If it's a legal touch, set that number as "selected" after the
// tapTimeout in case the user moves their finger quickly.
- tryVibrate();
+ mHapticFeedbackController.tryVibrate();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
@@ -606,7 +612,7 @@ public void run() {
mLastValueSelected = value;
mListener.onValueSelected(getCurrentItemShowing(), value, false);
}
- }, tapTimeout);
+ }, TAP_TIMEOUT);
}
}
return true;
@@ -620,7 +626,7 @@ public void run() {
float dY = Math.abs(eventY - mDownY);
float dX = Math.abs(eventX - mDownX);
- if (!mDoingMove && dX <= touchSlop && dY <= touchSlop) {
+ if (!mDoingMove && dX <= TOUCH_SLOP && dY <= TOUCH_SLOP) {
// Hasn't registered down yet, just slight, accidental movement of finger.
break;
}
@@ -651,7 +657,7 @@ public void run() {
if (degrees != -1) {
value = reselectSelector(degrees, isInnerCircle[0], false, true);
if (value != mLastValueSelected) {
- tryVibrate();
+ mHapticFeedbackController.tryVibrate();
mLastValueSelected = value;
mListener.onValueSelected(getCurrentItemShowing(), value, false);
}
@@ -710,21 +716,6 @@ public void run() {
return false;
}
- /**
- * Try to vibrate. To prevent this becoming a single continuous vibration, nothing will happen if we have vibrated
- * very recently.
- */
- 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;
- }
- }
- }
-
/**
* Set touch input as enabled or disabled, for use with keyboard mode.
*/
diff --git a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialSelectorView.java b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialSelectorView.java
index 789ceefb..329ccf0c 100644
--- a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialSelectorView.java
+++ b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialSelectorView.java
@@ -17,6 +17,7 @@
package com.doomonafireball.betterpickers.radialtimepicker;
import com.doomonafireball.betterpickers.R;
+import com.doomonafireball.betterpickers.Utils;
import com.nineoldandroids.animation.Keyframe;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.PropertyValuesHolder;
@@ -38,6 +39,12 @@ public class RadialSelectorView extends View {
private static final String TAG = "RadialSelectorView";
+ // Alpha level for selected circle.
+ private static final int SELECTED_ALPHA = Utils.SELECTED_ALPHA;
+ private static final int SELECTED_ALPHA_THEME_DARK = Utils.SELECTED_ALPHA_THEME_DARK;
+ // Alpha level for the line.
+ private static final int FULL_ALPHA = Utils.FULL_ALPHA;
+
private final Paint mPaint = new Paint();
private boolean mIsInitialized;
@@ -52,6 +59,7 @@ public class RadialSelectorView extends View {
private float mAnimationRadiusMultiplier;
private boolean mIs24HourMode;
private boolean mHasInnerCircle;
+ private int mSelectionAlpha;
private int mXCenter;
private int mYCenter;
@@ -96,6 +104,7 @@ public void initialize(Context context, boolean is24HourMode, boolean hasInnerCi
int blue = res.getColor(R.color.blue);
mPaint.setColor(blue);
mPaint.setAntiAlias(true);
+ mSelectionAlpha = SELECTED_ALPHA;
// Calculate values for the circle radius size.
mIs24HourMode = is24HourMode;
@@ -133,6 +142,19 @@ public void initialize(Context context, boolean is24HourMode, boolean hasInnerCi
mIsInitialized = true;
}
+ /* package */ void setTheme(Context context, boolean themeDark) {
+ Resources res = context.getResources();
+ int color;
+ if (themeDark) {
+ color = res.getColor(R.color.red);
+ mSelectionAlpha = SELECTED_ALPHA_THEME_DARK;
+ } else {
+ color = res.getColor(R.color.blue);
+ mSelectionAlpha = SELECTED_ALPHA;
+ }
+ mPaint.setColor(color);
+ }
+
/**
* Set the selection.
*
@@ -277,12 +299,12 @@ public void onDraw(Canvas canvas) {
int pointY = mYCenter - (int) (mLineLength * Math.cos(mSelectionRadians));
// Draw the selection circle.
- mPaint.setAlpha(51);
+ mPaint.setAlpha(mSelectionAlpha);
canvas.drawCircle(pointX, pointY, mSelectionRadius, mPaint);
if (mForceDrawDot | mSelectionDegrees % 30 != 0) {
// We're not on a direct tick (or we've been told to draw the dot anyway).
- mPaint.setAlpha(255);
+ mPaint.setAlpha(FULL_ALPHA);
canvas.drawCircle(pointX, pointY, (mSelectionRadius * 2 / 7), mPaint);
} else {
// We're not drawing the dot, so shorten the line to only go as far as the edge of the
diff --git a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTextsView.java b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTextsView.java
index 14082fbe..b3bf880a 100644
--- a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTextsView.java
+++ b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTextsView.java
@@ -144,6 +144,17 @@ public void initialize(Resources res, String[] texts, String[] innerTexts,
mIsInitialized = true;
}
+ /* package */ void setTheme(Context context, boolean themeDark) {
+ Resources res = context.getResources();
+ int textColor;
+ if (themeDark) {
+ textColor = res.getColor(R.color.white);
+ } else {
+ textColor = res.getColor(R.color.numbers_text_color);
+ }
+ mPaint.setColor(textColor);
+ }
+
/**
* Allows for smoother animation.
*/
diff --git a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTimePickerDialog.java b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTimePickerDialog.java
index 83b8f4f9..0765cc75 100644
--- a/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTimePickerDialog.java
+++ b/library/src/com/doomonafireball/betterpickers/radialtimepicker/RadialTimePickerDialog.java
@@ -16,6 +16,7 @@
package com.doomonafireball.betterpickers.radialtimepicker;
+import com.doomonafireball.betterpickers.HapticFeedbackController;
import com.doomonafireball.betterpickers.R;
import com.doomonafireball.betterpickers.Utils;
import com.doomonafireball.betterpickers.radialtimepicker.RadialPickerLayout.OnValueSelectedListener;
@@ -24,6 +25,7 @@
import android.app.ActionBar.LayoutParams;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
@@ -56,6 +58,7 @@ public class RadialTimePickerDialog extends DialogFragment implements OnValueSel
private static final String KEY_CURRENT_ITEM_SHOWING = "current_item_showing";
private static final String KEY_IN_KB_MODE = "in_kb_mode";
private static final String KEY_TYPED_TIMES = "typed_times";
+ private static final String KEY_DARK_THEME = "dark_theme";
public static final int HOUR_INDEX = 0;
public static final int MINUTE_INDEX = 1;
@@ -72,6 +75,8 @@ public class RadialTimePickerDialog extends DialogFragment implements OnValueSel
private OnDialogDismissListener mDimissCallback;
private OnTimeSetListener mCallback;
+ private HapticFeedbackController mHapticFeedbackController;
+
private TextView mDoneButton;
private TextView mHourView;
private TextView mHourSpaceView;
@@ -81,8 +86,8 @@ public class RadialTimePickerDialog extends DialogFragment implements OnValueSel
private View mAmPmHitspace;
private RadialPickerLayout mTimePicker;
- private int mBlue;
- private int mBlack;
+ private int mSelectedColor;
+ private int mUnselectedColor;
private String mAmText;
private String mPmText;
private String mDoneText;
@@ -91,6 +96,7 @@ public class RadialTimePickerDialog extends DialogFragment implements OnValueSel
private int mInitialHourOfDay;
private int mInitialMinute;
private boolean mIs24HourMode;
+ private boolean mThemeDark;
// For hardware IME input.
private char mPlaceholderText;
@@ -150,6 +156,18 @@ public void initialize(OnTimeSetListener callback,
mInitialMinute = minute;
mIs24HourMode = is24HourMode;
mInKbMode = false;
+ mThemeDark = false;
+ }
+
+ /**
+ * Set a dark or light theme. NOTE: this will only take effect for the next onCreateView.
+ */
+ public void setThemeDark(boolean dark) {
+ mThemeDark = dark;
+ }
+
+ public boolean isThemeDark() {
+ return mThemeDark;
}
public void setOnDismissListener(OnDialogDismissListener ondialogdismisslistener) {
@@ -166,7 +184,7 @@ public void setStartTime(int hourOfDay, int minute) {
mInKbMode = false;
}
- public void setText(String text) {
+ public void setDoneText(String text) {
mDoneText = text;
}
@@ -188,6 +206,7 @@ public void onCreate(Bundle savedInstanceState) {
mInitialMinute = savedInstanceState.getInt(KEY_MINUTE);
mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE);
+ mThemeDark = savedInstanceState.getBoolean(KEY_DARK_THEME);
}
}
@@ -205,8 +224,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
mSelectHours = res.getString(R.string.select_hours);
mMinutePickerDescription = res.getString(R.string.minute_picker_description);
mSelectMinutes = res.getString(R.string.select_minutes);
- mBlue = res.getColor(R.color.blue);
- mBlack = res.getColor(R.color.numbers_text_color);
+ mSelectedColor = res.getColor(mThemeDark ? R.color.red : R.color.blue);
+ mUnselectedColor = res.getColor(mThemeDark ? R.color.white : R.color.numbers_text_color);
mHourView = (TextView) view.findViewById(R.id.hours);
mHourView.setOnKeyListener(keyboardListener);
@@ -220,10 +239,14 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
mAmText = amPmTexts[0];
mPmText = amPmTexts[1];
+ mHapticFeedbackController = new HapticFeedbackController(getActivity());
+
mTimePicker = (RadialPickerLayout) view.findViewById(R.id.time_picker);
mTimePicker.setOnValueSelectedListener(this);
mTimePicker.setOnKeyListener(keyboardListener);
- mTimePicker.initialize(getActivity(), mInitialHourOfDay, mInitialMinute, mIs24HourMode);
+ mTimePicker.initialize(getActivity(), mHapticFeedbackController, mInitialHourOfDay,
+ mInitialMinute, mIs24HourMode);
+
int currentItemShowing = HOUR_INDEX;
if (savedInstanceState != null &&
savedInstanceState.containsKey(KEY_CURRENT_ITEM_SHOWING)) {
@@ -236,14 +259,14 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Override
public void onClick(View v) {
setCurrentItemShowing(HOUR_INDEX, true, false, true);
- mTimePicker.tryVibrate();
+ tryVibrate();
}
});
mMinuteView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setCurrentItemShowing(MINUTE_INDEX, true, false, true);
- mTimePicker.tryVibrate();
+ tryVibrate();
}
});
@@ -257,7 +280,7 @@ public void onClick(View v) {
if (mInKbMode && isTypedTimeFullyLegal()) {
finishKbMode(false);
} else {
- mTimePicker.tryVibrate();
+ tryVibrate();
}
if (mCallback != null) {
mCallback.onTimeSet(mTimePicker,
@@ -284,7 +307,7 @@ public void onClick(View v) {
mAmPmHitspace.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- mTimePicker.tryVibrate();
+ tryVibrate();
int amOrPm = mTimePicker.getIsCurrentlyAmOrPm();
if (amOrPm == AM) {
amOrPm = PM;
@@ -315,9 +338,50 @@ public void onClick(View v) {
mTypedTimes = new ArrayList();
}
+ // Set the theme at the end so that the initialize()s above don't counteract the theme.
+ mTimePicker.setTheme(getActivity().getApplicationContext(), mThemeDark);
+ // Prepare some colors to use.
+ int white = res.getColor(R.color.white);
+ int circleBackground = res.getColor(R.color.circle_background);
+ int line = res.getColor(R.color.line_background);
+ int timeDisplay = res.getColor(R.color.numbers_text_color);
+ ColorStateList doneTextColor = res.getColorStateList(R.color.done_text_color);
+ int doneBackground = R.drawable.done_background_color;
+
+ int darkGray = res.getColor(R.color.dark_gray);
+ int lightGray = res.getColor(R.color.light_gray);
+ int darkLine = res.getColor(R.color.line_dark);
+ ColorStateList darkDoneTextColor = res.getColorStateList(R.color.done_text_color_dark);
+ int darkDoneBackground = R.drawable.done_background_color_dark;
+
+ // Set the colors for each view based on the theme.
+ view.findViewById(R.id.time_display_background).setBackgroundColor(mThemeDark ? darkGray : white);
+ view.findViewById(R.id.time_display).setBackgroundColor(mThemeDark ? darkGray : white);
+ ((TextView) view.findViewById(R.id.separator)).setTextColor(mThemeDark ? white : timeDisplay);
+ ((TextView) view.findViewById(R.id.ampm_label)).setTextColor(mThemeDark ? white : timeDisplay);
+ view.findViewById(R.id.line).setBackgroundColor(mThemeDark ? darkLine : line);
+ mDoneButton.setTextColor(mThemeDark ? darkDoneTextColor : doneTextColor);
+ mTimePicker.setBackgroundColor(mThemeDark ? lightGray : circleBackground);
+ mDoneButton.setBackgroundResource(mThemeDark ? darkDoneBackground : doneBackground);
return view;
}
+ @Override
+ public void onResume() {
+ super.onResume();
+ mHapticFeedbackController.start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHapticFeedbackController.stop();
+ }
+
+ public void tryVibrate() {
+ mHapticFeedbackController.tryVibrate();
+ }
+
private void updateAmPmDisplay(int amOrPm) {
if (amOrPm == AM) {
mAmPmTextView.setText(mAmText);
@@ -343,6 +407,7 @@ public void onSaveInstanceState(Bundle outState) {
if (mInKbMode) {
outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes);
}
+ outState.putBoolean(KEY_DARK_THEME, mThemeDark);
}
}
@@ -357,10 +422,13 @@ public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance)
if (mAllowAutoAdvance && autoAdvance) {
setCurrentItemShowing(MINUTE_INDEX, true, true, false);
announcement += ". " + mSelectMinutes;
+ } else {
+ mTimePicker.setContentDescription(mHourPickerDescription + ": " + newValue);
}
Utils.tryAccessibilityAnnounce(mTimePicker, announcement);
} else if (pickerIndex == MINUTE_INDEX) {
setMinute(newValue);
+ mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue);
} else if (pickerIndex == AMPM_INDEX) {
updateAmPmDisplay(newValue);
} else if (pickerIndex == ENABLE_PICKER_INDEX) {
@@ -426,8 +494,8 @@ private void setCurrentItemShowing(int index, boolean animateCircle, boolean del
labelToAnimate = mMinuteView;
}
- int hourColor = (index == HOUR_INDEX) ? mBlue : mBlack;
- int minuteColor = (index == MINUTE_INDEX) ? mBlue : mBlack;
+ int hourColor = (index == HOUR_INDEX) ? mSelectedColor : mUnselectedColor;
+ int minuteColor = (index == MINUTE_INDEX) ? mSelectedColor : mUnselectedColor;
mHourView.setTextColor(hourColor);
mMinuteView.setTextColor(minuteColor);
@@ -645,10 +713,10 @@ private void updateDisplay(boolean allowEmptyDisplay) {
String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
mHourView.setText(hourStr);
mHourSpaceView.setText(hourStr);
- mHourView.setTextColor(mBlack);
+ mHourView.setTextColor(mUnselectedColor);
mMinuteView.setText(minuteStr);
mMinuteSpaceView.setText(minuteStr);
- mMinuteView.setTextColor(mBlack);
+ mMinuteView.setTextColor(mUnselectedColor);
if (!mIs24HourMode) {
updateAmPmDisplay(values[2]);
}
diff --git a/pom.xml b/pom.xml
index 7ff8f1f3..00f46079 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,11 +65,11 @@
UTF-81.6
- 4.1.1.4
+ 4.4_r11919.0.1
- 3.8.2
+ 3.9.0-rc.1
diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml
index 80456823..7760c6e1 100644
--- a/sample/AndroidManifest.xml
+++ b/sample/AndroidManifest.xml
@@ -299,6 +299,14 @@
+
+
+
+
+
+
diff --git a/sample/res/layout-land/text_and_button.xml b/sample/res/layout-land/text_and_button.xml
new file mode 100644
index 00000000..7eada790
--- /dev/null
+++ b/sample/res/layout-land/text_and_button.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/com/doomonafireball/betterpickers/sample/activity/calendardatepicker/SampleCalendarDateDefault.java b/sample/src/com/doomonafireball/betterpickers/sample/activity/calendardatepicker/SampleCalendarDateDefault.java
index 5208a445..a6e1aa89 100644
--- a/sample/src/com/doomonafireball/betterpickers/sample/activity/calendardatepicker/SampleCalendarDateDefault.java
+++ b/sample/src/com/doomonafireball/betterpickers/sample/activity/calendardatepicker/SampleCalendarDateDefault.java
@@ -18,6 +18,8 @@
public class SampleCalendarDateDefault extends BaseSampleActivity
implements CalendarDatePickerDialog.OnDateSetListener {
+ private static final String FRAG_TAG_DATE_PICKER = "fragment_date_picker_name";
+
private TextView text;
private Button button;
@@ -39,7 +41,7 @@ public void onClick(View v) {
CalendarDatePickerDialog calendarDatePickerDialog = CalendarDatePickerDialog
.newInstance(SampleCalendarDateDefault.this, now.getYear(), now.getMonthOfYear() - 1,
now.getDayOfMonth());
- calendarDatePickerDialog.show(fm, "fragment_date_picker_name");
+ calendarDatePickerDialog.show(fm, FRAG_TAG_DATE_PICKER);
}
});
}
@@ -48,4 +50,15 @@ public void onClick(View v) {
public void onDateSet(CalendarDatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth) {
text.setText("Year: " + year + "\nMonth: " + monthOfYear + "\nDay: " + dayOfMonth);
}
+
+ @Override
+ public void onResume() {
+ // Example of reattaching to the fragment
+ super.onResume();
+ CalendarDatePickerDialog calendarDatePickerDialog = (CalendarDatePickerDialog) getSupportFragmentManager()
+ .findFragmentByTag(FRAG_TAG_DATE_PICKER);
+ if (calendarDatePickerDialog != null) {
+ calendarDatePickerDialog.setOnDateSetListener(this);
+ }
+ }
}
diff --git a/sample/src/com/doomonafireball/betterpickers/sample/activity/radialtimepicker/SampleRadialTimeDark.java b/sample/src/com/doomonafireball/betterpickers/sample/activity/radialtimepicker/SampleRadialTimeDark.java
new file mode 100644
index 00000000..9fa0894b
--- /dev/null
+++ b/sample/src/com/doomonafireball/betterpickers/sample/activity/radialtimepicker/SampleRadialTimeDark.java
@@ -0,0 +1,67 @@
+package com.doomonafireball.betterpickers.sample.activity.radialtimepicker;
+
+import com.doomonafireball.betterpickers.radialtimepicker.RadialPickerLayout;
+import com.doomonafireball.betterpickers.radialtimepicker.RadialTimePickerDialog;
+import com.doomonafireball.betterpickers.sample.R;
+import com.doomonafireball.betterpickers.sample.activity.BaseSampleActivity;
+
+import org.joda.time.DateTime;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.text.format.DateFormat;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * User: derek Date: 3/17/13 Time: 3:59 PM
+ */
+public class SampleRadialTimeDark extends BaseSampleActivity
+ implements RadialTimePickerDialog.OnTimeSetListener {
+
+ private static final String FRAG_TAG_TIME_PICKER = "timePickerDialogFragment";
+
+ private TextView text;
+ private Button button;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.text_and_button);
+
+ text = (TextView) findViewById(R.id.text);
+ button = (Button) findViewById(R.id.button);
+
+ text.setText("--");
+ button.setText("Set Time");
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ FragmentManager fm = getSupportFragmentManager();
+ DateTime now = DateTime.now();
+ RadialTimePickerDialog timePickerDialog = RadialTimePickerDialog
+ .newInstance(SampleRadialTimeDark.this, now.getHourOfDay(), now.getMinuteOfHour(),
+ DateFormat.is24HourFormat(SampleRadialTimeDark.this));
+ timePickerDialog.setThemeDark(true);
+ timePickerDialog.show(fm, FRAG_TAG_TIME_PICKER);
+ }
+ });
+ }
+
+ @Override
+ public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute) {
+ text.setText("" + hourOfDay + ":" + minute);
+ }
+
+ @Override
+ public void onResume() {
+ // Example of reattaching to the fragment
+ super.onResume();
+ RadialTimePickerDialog rtpd = (RadialTimePickerDialog) getSupportFragmentManager().findFragmentByTag(
+ FRAG_TAG_TIME_PICKER);
+ if (rtpd != null) {
+ rtpd.setOnTimeSetListener(this);
+ }
+ }
+}
diff --git a/sample/src/com/doomonafireball/betterpickers/sample/activity/radialtimepicker/SampleRadialTimeDefault.java b/sample/src/com/doomonafireball/betterpickers/sample/activity/radialtimepicker/SampleRadialTimeDefault.java
index 1f713a24..2284ebea 100644
--- a/sample/src/com/doomonafireball/betterpickers/sample/activity/radialtimepicker/SampleRadialTimeDefault.java
+++ b/sample/src/com/doomonafireball/betterpickers/sample/activity/radialtimepicker/SampleRadialTimeDefault.java
@@ -20,6 +20,8 @@
public class SampleRadialTimeDefault extends BaseSampleActivity
implements RadialTimePickerDialog.OnTimeSetListener {
+ private static final String FRAG_TAG_TIME_PICKER = "timePickerDialogFragment";
+
private TextView text;
private Button button;
@@ -41,7 +43,7 @@ public void onClick(View v) {
RadialTimePickerDialog timePickerDialog = RadialTimePickerDialog
.newInstance(SampleRadialTimeDefault.this, now.getHourOfDay(), now.getMinuteOfHour(),
DateFormat.is24HourFormat(SampleRadialTimeDefault.this));
- timePickerDialog.show(fm, "fragment_time_picker_name");
+ timePickerDialog.show(fm, FRAG_TAG_TIME_PICKER);
}
});
}
@@ -50,4 +52,15 @@ public void onClick(View v) {
public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute) {
text.setText("" + hourOfDay + ":" + minute);
}
+
+ @Override
+ public void onResume() {
+ // Example of reattaching to the fragment
+ super.onResume();
+ RadialTimePickerDialog rtpd = (RadialTimePickerDialog) getSupportFragmentManager().findFragmentByTag(
+ FRAG_TAG_TIME_PICKER);
+ if (rtpd != null) {
+ rtpd.setOnTimeSetListener(this);
+ }
+ }
}
diff --git a/sample/src/com/doomonafireball/betterpickers/sample/activity/recurrencepicker/SampleRecurrenceDefault.java b/sample/src/com/doomonafireball/betterpickers/sample/activity/recurrencepicker/SampleRecurrenceDefault.java
index a2ac064c..d2ffbcc1 100644
--- a/sample/src/com/doomonafireball/betterpickers/sample/activity/recurrencepicker/SampleRecurrenceDefault.java
+++ b/sample/src/com/doomonafireball/betterpickers/sample/activity/recurrencepicker/SampleRecurrenceDefault.java
@@ -75,6 +75,17 @@ public void onRecurrenceSet(String rrule) {
populateRepeats();
}
+ @Override
+ public void onResume() {
+ // Example of reattaching to the fragment
+ super.onResume();
+ RecurrencePickerDialog rpd = (RecurrencePickerDialog) getSupportFragmentManager().findFragmentByTag(
+ FRAG_TAG_RECUR_PICKER);
+ if (rpd != null) {
+ rpd.setOnRecurrenceSetListener(this);
+ }
+ }
+
private void populateRepeats() {
Resources r = getResources();
String repeatString = "";