summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.classpath2
-rw-r--r--AndroidManifest.xml10
-rw-r--r--res/color/date_picker_selector.xml23
-rw-r--r--res/layout-land/date_picker_dialog.xml57
-rw-r--r--res/layout/date_picker_dialog.xml40
-rw-r--r--res/layout/date_picker_done_button.xml29
-rw-r--r--res/layout/date_picker_selected_date.xml67
-rw-r--r--res/layout/month_text_view.xml25
-rw-r--r--res/values-land/dimens.xml2
-rw-r--r--res/values-sw600dp-land/dimens.xml8
-rw-r--r--res/values-sw600dp/dimens.xml26
-rw-r--r--res/values/colors.xml34
-rw-r--r--res/values/dimens.xml53
-rw-r--r--res/values/strings.xml2
-rw-r--r--res/values/styles.xml2
-rw-r--r--src/com/android/datetimepicker/Utils.java72
-rw-r--r--src/com/android/datetimepicker/date/DatePickerController.java39
-rw-r--r--src/com/android/datetimepicker/date/DatePickerDialog.java371
-rw-r--r--src/com/android/datetimepicker/date/DayPickerView.java394
-rw-r--r--src/com/android/datetimepicker/date/MonthPickerView.java94
-rw-r--r--src/com/android/datetimepicker/date/SelectableTextView.java67
-rw-r--r--src/com/android/datetimepicker/date/SimpleMonthAdapter.java224
-rw-r--r--src/com/android/datetimepicker/date/SimpleMonthView.java418
-rw-r--r--src/com/android/datetimepicker/date/YearPickerView.java68
24 files changed, 2093 insertions, 34 deletions
diff --git a/.classpath b/.classpath
index 3f9691c..2308ad7 100644
--- a/.classpath
+++ b/.classpath
@@ -2,7 +2,7 @@
<classpath>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
- <classpathentry kind="src" path="src"/>
+ <classpathentry excluding="com/android/datetimepicker/DayOfMonthPickerView.java" kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8ca36f9..3eb6352 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,11 +13,14 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.datetimepicker"
- android:versionCode="1" >
- <uses-sdk android:maxSdkVersion="17" android:minSdkVersion="15"/>
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.VIBRATE" />
</manifest>
diff --git a/res/color/date_picker_selector.xml b/res/color/date_picker_selector.xml
new file mode 100644
index 0000000..0d373eb
--- /dev/null
+++ b/res/color/date_picker_selector.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true" android:color="@color/selected_text"/>
+ <item android:state_selected="true" android:color="@color/selected_text"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/calendar_selected_date_text"/>
+
+</selector> \ No newline at end of file
diff --git a/res/layout-land/date_picker_dialog.xml b/res/layout-land/date_picker_dialog.xml
new file mode 100644
index 0000000..22d4e6c
--- /dev/null
+++ b/res/layout-land/date_picker_dialog.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/date_picker_dialog_height"
+ android:gravity="center"
+ android:orientation="horizontal" >
+
+ <LinearLayout
+ android:layout_width="@dimen/date_picker_width"
+ android:layout_height="@dimen/date_picker_dialog_height"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ android:background="@color/white" >
+
+ <include layout="@layout/date_picker_selected_date" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="@color/black_20" />
+
+ <include layout="@layout/date_picker_done_button" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="1dip"
+ android:layout_height="match_parent"
+ android:background="@color/black_20" />
+
+ <FrameLayout
+ android:layout_width="@dimen/pager_width"
+ android:layout_height="match_parent"
+ android:background="@color/calendar_pager"
+ android:layout_gravity="center">
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/pager"
+ android:layout_width="@dimen/pager_width"
+ android:layout_height="@dimen/pager_height"
+ android:layout_gravity="center"
+ android:gravity="center" />
+ </FrameLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/date_picker_dialog.xml b/res/layout/date_picker_dialog.xml
new file mode 100644
index 0000000..ece59fc
--- /dev/null
+++ b/res/layout/date_picker_dialog.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/date_picker_width"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/calendar_pager" >
+
+ <include layout="@layout/date_picker_selected_date"
+ android:id="@+id/date_picker_selected_date_layout" />
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/pager"
+ android:layout_width="@dimen/pager_width"
+ android:layout_height="@dimen/pager_height"
+ android:gravity="center"
+ android:background="@color/calendar_pager" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="@color/black_20" />
+
+ <include layout="@layout/date_picker_done_button"
+ android:id="@+id/date_picker_done_button" />
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/date_picker_done_button.xml b/res/layout/date_picker_done_button.xml
new file mode 100644
index 0000000..8d61f4b
--- /dev/null
+++ b/res/layout/date_picker_done_button.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout="@+id/done_layout"
+ android:orientation="vertical" >
+
+ <Button
+ android:id="@+id/done"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/done_label" />
+</LinearLayout>
diff --git a/res/layout/date_picker_selected_date.xml b/res/layout/date_picker_selected_date.xml
new file mode 100644
index 0000000..639a7a8
--- /dev/null
+++ b/res/layout/date_picker_selected_date.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/day_picker_selected_date_layout"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/selected_date_layout_height"
+ android:layout_gravity="center"
+ android:background="@color/white"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/date_picker_header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/date_picker_header_height"
+ android:background="@color/calendar_header"
+ android:gravity="center"
+ android:includeFontPadding="false"
+ android:textColor="@color/white"
+ android:textSize="@dimen/date_picker_header_text_size" />
+
+ <TextView
+ android:id="@+id/date_picker_month"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:gravity="center_horizontal|bottom"
+ android:includeFontPadding="false"
+ android:textColor="@color/date_picker_selector"
+ android:textSize="@dimen/selected_date_month_size" />
+
+ <TextView
+ android:id="@+id/date_picker_day"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginBottom="-10dip"
+ android:layout_marginTop="-10dip"
+ android:gravity="center"
+ android:includeFontPadding="false"
+ android:textColor="@color/date_picker_selector"
+ android:textSize="@dimen/selected_date_day_size" />
+
+ <TextView
+ android:id="@+id/date_picker_year"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:gravity="center_horizontal|top"
+ android:includeFontPadding="false"
+ android:textColor="@color/date_picker_selector"
+ android:textSize="@dimen/selected_date_year_size" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/month_text_view.xml b/res/layout/month_text_view.xml
new file mode 100644
index 0000000..df2bfde
--- /dev/null
+++ b/res/layout/month_text_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.datetimepicker.date.SelectableTextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/month_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:padding="@dimen/month_list_item_padding"
+ android:textColor="@color/calendar_day_number"
+ android:textSize="@dimen/month_list_item_size" />
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index c8e4f98..09cf316 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -20,6 +20,8 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
xmlns:android="http://schemas.android.com/apk/res/android" >
+
<dimen name="dialog_height">300dip</dimen>
<dimen name="left_side_width">270dip</dimen>
+ <dimen name="date_picker_dialog_height">282dip</dimen>
</resources> \ No newline at end of file
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index 70c7b73..58a6c67 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -17,9 +17,9 @@
*/
-->
-<resources
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
- xmlns:android="http://schemas.android.com/apk/res/android" >
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
<dimen name="time_label_right_padding">16sp</dimen>
<dimen name="time_label_size">64sp</dimen>
<dimen name="ampm_label_size">21sp</dimen>
@@ -28,4 +28,6 @@
<dimen name="separator_padding">5dip</dimen>
<dimen name="header_height">96dip</dimen>
<dimen name="footer_height">48dip</dimen>
+ <dimen name="date_picker_dialog_height">410dip</dimen>
+
</resources> \ No newline at end of file
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 7efc81d..83bea40 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -17,9 +17,9 @@
*/
-->
-<resources
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
- xmlns:android="http://schemas.android.com/apk/res/android" >
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
<dimen name="time_label_right_padding">34sp</dimen>
<dimen name="time_label_size">96sp</dimen>
<dimen name="ampm_label_size">32sp</dimen>
@@ -28,4 +28,24 @@
<dimen name="separator_padding">8dip</dimen>
<dimen name="header_height">144dip</dimen>
<dimen name="footer_height">72dip</dimen>
+
+ <dimen name="pager_height">400dp</dimen>
+ <dimen name="pager_width">400dp</dimen>
+ <dimen name="date_picker_width">400dp</dimen>
+ <dimen name="selected_date_layout_height">360dp</dimen>
+ <dimen name="done_button_height">42dp</dimen>
+ <dimen name="date_picker_header_height">45dp</dimen>
+ <dimen name="month_list_item_header_height">75dp</dimen>
+ <dimen name="month_list_item_padding">24dp</dimen>
+ <dimen name="day_number_select_circle_radius">24dp</dimen>
+ <dimen name="month_select_circle_radius">36dp</dimen>
+ <dimen name="selected_date_year_size">45dp</dimen>
+ <dimen name="selected_date_day_size">150dp</dimen>
+ <dimen name="selected_date_month_size">45dp</dimen>
+ <dimen name="date_picker_header_text_size">21dp</dimen>
+ <dimen name="month_list_item_size">24sp</dimen>
+ <dimen name="month_label_size">24sp</dimen>
+ <dimen name="day_number_size">24sp</dimen>
+ <dimen name="month_day_label_text_size">15sp</dimen>
+
</resources> \ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 8ce587e..3568ed4 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!--
+ 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.
@@ -13,8 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<resources>
+
<color name="white">#ffffff</color>
<color name="black_03">#08000000</color>
<color name="black_20">#33000000</color>
@@ -22,4 +23,33 @@
<color name="black_60">#99000000</color>
<color name="black_80">#CC000000</color>
<color name="blue">#33b5e5</color>
+
+ <color name="calendar_header">#999999</color>
+ <color name="calendar_week_labels">#FFDBDCDE</color>
+ <color name="calendar_pager">#FFECEDEE</color>
+ <color name="calendar_day_number">#FF999999</color>
+ <color name="calendar_selected_date_text">#FFD1D2D4</color>
+ <color name="done_button_color">#FFECEDEE</color>
+ <color name="selected_text">#FF33B5E5</color>
+ <color name="mini_month_bg_color">#FFFFFFFF</color>
+ <color name="mini_month_today_outline_color">#FF000000</color>
+ <color name="month_day_number">#999999</color>
+ <color name="month_mini_day_number">#FF000000</color>
+ <color name="month_day_number_other">#FF999999</color>
+ <color name="month_week_num_color">#FF999999</color>
+ <color name="month_day_names_color">#FF999999</color>
+ <color name="month_today_number">#FF333333</color>
+ <color name="month_event_color">#FF333333</color>
+ <color name="month_event_extra_color">#FF666666</color>
+ <color name="month_event_other_color">#FF666666</color>
+ <color name="month_event_extra_other_color">#4C999999</color>
+ <color name="month_saturday">#80333333</color>
+ <color name="month_sunday">#80333333</color>
+ <color name="month_grid_lines">#ffCCCCCC</color>
+ <color name="month_other_month_day_number">#ff939497</color>
+ <color name="month_other_bgcolor">#FFEEEEEE</color>
+ <color name="month_selected_week_bgcolor">#FFDDDDDD</color>
+ <color name="month_today_bgcolor">#FFFFFFFF</color>
+ <color name="month_bgcolor">#FFE5E5E5</color>
+
</resources> \ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 25090b2..affa7ba 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -1,25 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-/*
-** Copyright 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.
-*/
--->
+ 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
-<resources
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
+ 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
xmlns:android="http://schemas.android.com/apk/res/android" >
+
<item name="circle_radius_multiplier" format="float" type="string">0.82</item>
<item name="circle_radius_multiplier_24HourMode" format="float" type="string">0.85</item>
<item name="selection_radius_multiplier" format="float" type="string">0.14</item>
@@ -30,6 +27,7 @@
<item name="text_size_multiplier_normal" format="float" type="string">0.17</item>
<item name="text_size_multiplier_inner" format="float" type="string">0.14</item>
<item name="text_size_multiplier_outer" format="float" type="string">0.11</item>
+
<dimen name="time_label_right_padding">12sp</dimen>
<dimen name="time_label_size">60sp</dimen>
<dimen name="ampm_label_size">16sp</dimen>
@@ -38,4 +36,23 @@
<dimen name="separator_padding">4dip</dimen>
<dimen name="header_height">96dip</dimen>
<dimen name="footer_height">48dip</dimen>
+ <dimen name="pager_height">270dp</dimen>
+ <dimen name="pager_width">270dp</dimen>
+ <dimen name="date_picker_width">270dp</dimen>
+ <dimen name="selected_date_layout_height">240dp</dimen>
+ <dimen name="done_button_height">42dp</dimen>
+ <dimen name="date_picker_header_height">30dp</dimen>
+ <dimen name="month_list_item_header_height">50dp</dimen>
+ <dimen name="month_list_item_padding">16dp</dimen>
+ <dimen name="month_day_label_text_size">10sp</dimen>
+ <dimen name="day_number_select_circle_radius">16dp</dimen>
+ <dimen name="month_select_circle_radius">24dp</dimen>
+ <dimen name="selected_date_year_size">30dp</dimen>
+ <dimen name="selected_date_day_size">100dp</dimen>
+ <dimen name="selected_date_month_size">30dp</dimen>
+ <dimen name="date_picker_header_text_size">14dp</dimen>
+ <dimen name="month_label_size">16sp</dimen>
+ <dimen name="day_number_size">16sp</dimen>
+ <dimen name="month_list_item_size">16sp</dimen>
+
</resources> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0b46e21..e3871be 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -13,9 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name">TimePicker</string>
<string name="done_label">Done</string>
<string name="hour_picker_description">Hours circular slider</string>
<string name="minute_picker_description">Minutes circular slider</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b53c851..c8fcfa6 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -15,6 +15,7 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
@@ -46,5 +47,4 @@
<item name="android:textAllCaps">true</item>
<item name="android:textColor">@color/black_50</item>
</style>
-
</resources> \ No newline at end of file
diff --git a/src/com/android/datetimepicker/Utils.java b/src/com/android/datetimepicker/Utils.java
index 8772fb8..9bc5952 100644
--- a/src/com/android/datetimepicker/Utils.java
+++ b/src/com/android/datetimepicker/Utils.java
@@ -17,9 +17,79 @@
package com.android.datetimepicker;
import android.os.Build;
+import android.text.format.Time;
+import java.util.Calendar;
+
+/**
+ * Utility helper functions for time and date pickers.
+ */
public class Utils {
+
+ public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
+
+ static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
+
public static boolean isJellybeanOrLater() {
- return Build.VERSION.SDK_INT >= 16;
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+ }
+
+ public static int getDaysInMonth(int month, int year) {
+ switch (month) {
+ case Calendar.JANUARY:
+ case Calendar.MARCH:
+ case Calendar.MAY:
+ case Calendar.JULY:
+ case Calendar.AUGUST:
+ case Calendar.OCTOBER:
+ case Calendar.DECEMBER:
+ return 31;
+ case Calendar.APRIL:
+ case Calendar.JUNE:
+ case Calendar.SEPTEMBER:
+ case Calendar.NOVEMBER:
+ return 30;
+ case Calendar.FEBRUARY:
+ return (year % 4 == 0) ? 29 : 28;
+ default:
+ throw new IllegalArgumentException("Invalid Month");
+ }
+ }
+
+ /**
+ * 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 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
+ */
+ public static int getJulianMondayFromWeeksSinceEpoch(int week) {
+ return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
+ }
+
+ /**
+ * Returns the week since {@link 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 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 Time#SUNDAY}
+ * @return Weeks since the epoch
+ */
+ public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
+ int diff = Time.THURSDAY - firstDayOfWeek;
+ if (diff < 0) {
+ diff += 7;
+ }
+ int refDay = Time.EPOCH_JULIAN_DAY - diff;
+ return (julianDay - refDay) / 7;
}
}
diff --git a/src/com/android/datetimepicker/date/DatePickerController.java b/src/com/android/datetimepicker/date/DatePickerController.java
new file mode 100644
index 0000000..f92d687
--- /dev/null
+++ b/src/com/android/datetimepicker/date/DatePickerController.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay;
+
+/**
+ * Controller class to communicate among the various components of the date picker dialog.
+ */
+public interface DatePickerController {
+
+ public void onYearPickerSelectionChanged(int year);
+
+ public void onMonthPickerSelectionChanged(int month);
+
+ public void onDayPickerSelectionChanged(int year, int month, int day);
+
+ public CalendarDay getSelectedDay();
+
+ public int getFirstDayOfWeek();
+
+ public int getMinYear();
+
+ public int getMaxYear();
+}
diff --git a/src/com/android/datetimepicker/date/DatePickerDialog.java b/src/com/android/datetimepicker/date/DatePickerDialog.java
new file mode 100644
index 0000000..7c46f69
--- /dev/null
+++ b/src/com/android/datetimepicker/date/DatePickerDialog.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.os.Bundle;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.datetimepicker.R;
+import com.android.datetimepicker.Utils;
+import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * Dialog allowing users to select a date.
+ */
+public class DatePickerDialog extends DialogFragment implements
+ OnPageChangeListener, OnClickListener, DatePickerController {
+
+ private static final String TAG = "DatePickerDialog";
+
+ private static final int TOTAL_VIEWS = 3;
+
+ private static final int MONTH_VIEW = 0;
+ private static final int DAY_VIEW = 1;
+ private static final int YEAR_VIEW = 2;
+
+ private static String KEY_SELECTED_YEAR = "year";
+ private static String KEY_SELECTED_MONTH = "month";
+ private static String KEY_SELECTED_DAY = "day";
+ private static String KEY_LIST_POSITION = "position";
+ private static String KEY_WEEK_START = "week_start";
+ private static String KEY_YEAR_START = "year_start";
+ private static String KEY_YEAR_END = "year_end";
+
+ private static final int DEFAULT_START_YEAR = 1900;
+ private static final int DEFAULT_END_YEAR = 2100;
+
+ private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
+ private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault());
+
+ private final Calendar mCalendar = Calendar.getInstance();
+ private OnDateSetListener mCallBack;
+
+ private ViewPager mPager;
+
+ private TextView mDayOfWeekView;
+ private TextView mMonthView;
+ private TextView mDayOfMonthView;
+ private TextView mYearView;
+ private MonthPickerView mMonthPickerView;
+ private DayPickerView mDayPickerView;
+ private YearPickerView mYearPickerView;
+ private Button mDoneButton;
+
+ private int mWeekStart = mCalendar.getFirstDayOfWeek();
+ private int mMinYear = DEFAULT_START_YEAR;
+ private int mMaxYear = DEFAULT_END_YEAR;
+
+ private final View[] mViews = new View[TOTAL_VIEWS];
+
+ /**
+ * The callback used to indicate the user is done filling in the date.
+ */
+ public interface OnDateSetListener {
+
+ /**
+ * @param view 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.
+ */
+ void onDateSet(DatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth);
+ }
+
+ public DatePickerDialog() {
+ // Empty constructor required for dialog fragment.
+ }
+
+ /**
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param monthOfYear The initial month of the dialog.
+ * @param dayOfMonth The initial day of the dialog.
+ */
+ public static DatePickerDialog newInstance(OnDateSetListener callBack, int year,
+ int monthOfYear,
+ int dayOfMonth) {
+ DatePickerDialog ret = new DatePickerDialog();
+ ret.initialize(callBack, year, monthOfYear, dayOfMonth);
+ return ret;
+ }
+
+ public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
+ mCallBack = callBack;
+ mCalendar.set(Calendar.YEAR, year);
+ mCalendar.set(Calendar.MONTH, monthOfYear);
+ mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR));
+ mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH));
+ mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR));
+ outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH));
+ outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH));
+ outState.putInt(KEY_LIST_POSITION, mDayPickerView.getFirstVisiblePosition());
+ outState.putInt(KEY_WEEK_START, mWeekStart);
+ outState.putInt(KEY_YEAR_START, mMinYear);
+ outState.putInt(KEY_YEAR_END, mMaxYear);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ Log.d(TAG, "onCreateView: ");
+ getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+
+ View view = inflater.inflate(R.layout.date_picker_dialog, null);
+
+ mDayOfWeekView = (TextView) view.findViewById(R.id.date_picker_header);
+ mMonthView = (TextView) view.findViewById(R.id.date_picker_month);
+ mMonthView.setOnClickListener(this);
+ mDayOfMonthView = (TextView) view.findViewById(R.id.date_picker_day);
+ mDayOfMonthView.setOnClickListener(this);
+ mYearView = (TextView) view.findViewById(R.id.date_picker_year);
+ mYearView.setOnClickListener(this);
+ final Activity activity = getActivity();
+
+ mMonthPickerView = new MonthPickerView(activity, this);
+ mDayPickerView = new DayPickerView(activity, this);
+ if (savedInstanceState != null) {
+ Log.d(TAG,
+ "Setting first visible position: "
+ + savedInstanceState.getInt(KEY_LIST_POSITION));
+ mDayPickerView.setSelectionFromTop(savedInstanceState.getInt(KEY_LIST_POSITION),
+ DayPickerView.LIST_TOP_OFFSET);
+ mWeekStart = savedInstanceState.getInt(KEY_WEEK_START);
+ mMinYear = savedInstanceState.getInt(KEY_YEAR_START);
+ mMaxYear = savedInstanceState.getInt(KEY_YEAR_END);
+ }
+ mYearPickerView = new YearPickerView(activity, this);
+
+ mViews[MONTH_VIEW] = mMonthPickerView;
+ mViews[DAY_VIEW] = mDayPickerView;
+ mViews[YEAR_VIEW] = mYearPickerView;
+
+ mPager = (ViewPager) view.findViewById(R.id.pager);
+ mPager.setAdapter(new DatePickerPagerAdapter());
+ mPager.setOnPageChangeListener(this);
+ mPager.setCurrentItem(DAY_VIEW);
+
+ mDoneButton = (Button) view.findViewById(R.id.done);
+ mDoneButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mCallBack != null) {
+ mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR),
+ mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
+ }
+ dismiss();
+ }
+ });
+
+ updateDisplay();
+
+ return view;
+ }
+
+ private void updateDisplay() {
+ mDayOfWeekView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()));
+ mMonthView.setText(mCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()));
+ mDayOfMonthView.setText(DAY_FORMAT.format(mCalendar.getTime()));
+ mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime()));
+ }
+
+ public void setFirstDayOfWeek(int startOfWeek) {
+ if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) {
+ throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " +
+ "Calendar.SATURDAY");
+ }
+ mWeekStart = startOfWeek;
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ public void setYearRange(int startYear, int endYear) {
+ if (endYear <= startYear) {
+ throw new IllegalArgumentException("Year end must be larger than year start");
+ }
+ mMinYear = startYear;
+ mMaxYear = endYear;
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ mYearPickerView.onChange();
+ }
+ }
+
+ public void setOnDateSetListener(OnDateSetListener listener) {
+ mCallBack = listener;
+ }
+
+ private class DatePickerPagerAdapter extends PagerAdapter {
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ container.addView(mViews[position]);
+ return mViews[position];
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object view) {
+ container.removeView((View) view);
+ }
+
+ @Override
+ public int getCount() {
+ return mViews.length;
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+ }
+
+ // If the newly selected month / year does not contain the currently selected day number,
+ // change the selected day number to the last day of the selected month or year.
+ // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
+ // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
+ private void adjustDayInMonthIfNeeded(int month, int year) {
+ int day = mCalendar.get(Calendar.DAY_OF_MONTH);
+ int daysInMonth = Utils.getDaysInMonth(month, year);
+ if (day > daysInMonth) {
+ mCalendar.set(Calendar.DAY_OF_MONTH, daysInMonth);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onPageScrolled(int arg0, float arg1, int arg2) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void onPageSelected(int index) {
+ switch (index) {
+ case MONTH_VIEW:
+ mMonthView.setSelected(true);
+ mDayOfMonthView.setSelected(false);
+ mYearView.setSelected(false);
+ break;
+ case DAY_VIEW:
+ mMonthView.setSelected(false);
+ mDayOfMonthView.setSelected(true);
+ mYearView.setSelected(false);
+ break;
+ case YEAR_VIEW:
+ mMonthView.setSelected(false);
+ mDayOfMonthView.setSelected(false);
+ mYearView.setSelected(true);
+ break;
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.date_picker_year) {
+ mPager.setCurrentItem(YEAR_VIEW);
+ } else if (v.getId() == R.id.date_picker_month) {
+ mPager.setCurrentItem(MONTH_VIEW);
+ } else if (v.getId() == R.id.date_picker_day) {
+ mPager.setCurrentItem(DAY_VIEW);
+ }
+ }
+
+ @Override
+ public void onYearPickerSelectionChanged(int year) {
+ adjustDayInMonthIfNeeded(mCalendar.get(Calendar.MONTH), year);
+ mCalendar.set(Calendar.YEAR, year);
+ mDayPickerView.setCalendarDate(getSelectedDay());
+ updateDisplay();
+ }
+
+ @Override
+ public void onMonthPickerSelectionChanged(int month) {
+ adjustDayInMonthIfNeeded(month, mCalendar.get(Calendar.YEAR));
+ mCalendar.set(Calendar.MONTH, month);
+ mDayPickerView.setCalendarDate(getSelectedDay());
+ mPager.setCurrentItem(DAY_VIEW);
+ updateDisplay();
+ }
+
+ @Override
+ public void onDayPickerSelectionChanged(int year, int month, int day) {
+ mCalendar.set(Calendar.YEAR, year);
+ mCalendar.set(Calendar.MONTH, month);
+ mCalendar.set(Calendar.DAY_OF_MONTH, day);
+ mYearPickerView.setValue(mCalendar.get(Calendar.YEAR));
+ updateDisplay();
+ }
+
+
+ @Override
+ public CalendarDay getSelectedDay() {
+ return new CalendarDay(mCalendar);
+ }
+
+ @Override
+ public int getMinYear() {
+ return mMinYear;
+ }
+
+ @Override
+ public int getMaxYear() {
+ return mMaxYear;
+ }
+
+ @Override
+ public int getFirstDayOfWeek() {
+ return mWeekStart;
+ }
+}
diff --git a/src/com/android/datetimepicker/date/DayPickerView.java b/src/com/android/datetimepicker/date/DayPickerView.java
new file mode 100644
index 0000000..76dcf4f
--- /dev/null
+++ b/src/com/android/datetimepicker/date/DayPickerView.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.ListView;
+
+import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * This displays a list of months in a calendar format with selectable days.
+ */
+public class DayPickerView extends ListView implements OnScrollListener {
+
+ private static final String TAG = "MonthFragment";
+
+ // Affects when the month selection will change while scrolling up
+ protected static final int SCROLL_HYST_WEEKS = 2;
+ // How long the GoTo fling animation should last
+ protected static final int GOTO_SCROLL_DURATION = 250;
+ // How long to wait after receiving an onScrollStateChanged notification
+ // before acting on it
+ protected static final int SCROLL_CHANGE_DELAY = 40;
+ // The number of days to display in each week
+ public static final int DAYS_PER_WEEK = 7;
+ public static int LIST_TOP_OFFSET = -1; // so that the top line will be
+ // under the separator
+ // You can override these numbers to get a different appearance
+ protected int mNumWeeks = 6;
+ protected boolean mShowWeekNumber = false;
+ protected int mDaysPerWeek = 7;
+
+ // These affect the scroll speed and feel
+ protected float mFriction = 1.0f;
+
+ protected Context mContext;
+ protected Handler mHandler;
+
+ protected float mMinimumFlingVelocity;
+
+ // highlighted time
+ protected CalendarDay mSelectedDay = new CalendarDay();
+ protected SimpleMonthAdapter mAdapter;
+
+ protected CalendarDay mTempDay = new CalendarDay();
+
+ private static float mScale = 0;
+ // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
+ protected int mFirstDayOfWeek;
+ // The first day that is visible in the view
+ protected Time mFirstVisibleDay = new Time();
+ // The last name announced by accessibility
+ protected CharSequence mPrevMonthName;
+ // which month should be displayed/highlighted [0-11]
+ protected int mCurrentMonthDisplayed;
+ // used for tracking during a scroll
+ protected long mPreviousScrollPosition;
+ // used for tracking what state listview is in
+ protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ // used for tracking what state listview is in
+ protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ private final DatePickerController mController;
+
+ // This causes an update of the view at midnight
+ protected Runnable mTodayUpdater = new Runnable() {
+ @Override
+ public void run() {
+ Time midnight = new Time(mFirstVisibleDay.timezone);
+ midnight.setToNow();
+ long currentMillis = midnight.toMillis(true);
+
+ midnight.hour = 0;
+ midnight.minute = 0;
+ midnight.second = 0;
+ midnight.monthDay++;
+ long millisToMidnight = midnight.normalize(true) - currentMillis;
+ mHandler.postDelayed(this, millisToMidnight);
+
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ // This allows us to update our position when a day is tapped
+ protected DataSetObserver mObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ CalendarDay day = mAdapter.getSelectedDay();
+ if (day.year != mSelectedDay.year || day.day != mSelectedDay.day) {
+ goTo(day, true, true, false);
+ }
+ }
+ };
+
+ public DayPickerView(Context context, DatePickerController controller) {
+ super(context);
+ mHandler = new Handler();
+ mController = controller;
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setDrawSelectorOnTop(false);
+ init(context);
+ setCalendarDate(controller.getSelectedDay());
+ }
+
+ public void setCalendarDate(CalendarDay day) {
+ goTo(day, false, true, true);
+ }
+
+ public void init(Context context) {
+ mContext = context;
+ String tz = Time.getCurrentTimezone();
+ ViewConfiguration viewConfig = ViewConfiguration.get(context);
+ mMinimumFlingVelocity = viewConfig.getScaledMinimumFlingVelocity();
+
+ mFirstVisibleDay.timezone = tz;
+ mFirstVisibleDay.normalize(true);
+
+ setUpListView();
+ setUpAdapter();
+ setAdapter(mAdapter);
+ }
+
+ public void onChange() {
+ setUpAdapter();
+ setAdapter(mAdapter);
+ }
+
+ /**
+ * Creates a new adapter if necessary and sets up its parameters. Override
+ * this method to provide a custom adapter.
+ */
+ protected void setUpAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new SimpleMonthAdapter(getContext(), mController);
+ mAdapter.registerDataSetObserver(mObserver);
+ } else {
+ mAdapter.setSelectedDay(mSelectedDay);
+ mAdapter.notifyDataSetChanged();
+ }
+ // refresh the view with the new parameters
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /*
+ * Sets all the required fields for the list view. Override this method to
+ * set a different list view behavior.
+ */
+ protected void setUpListView() {
+ // Transparent background on scroll
+ setCacheColorHint(0);
+ // No dividers
+ setDivider(null);
+ // Items are clickable
+ setItemsCanFocus(true);
+ // The thumb gets in the way, so disable it
+ setFastScrollEnabled(false);
+ setVerticalScrollBarEnabled(false);
+ setOnScrollListener(this);
+ setFadingEdgeLength(0);
+ // Make the scrolling behavior nicer
+ setFriction(ViewConfiguration.getScrollFriction() * mFriction);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ setUpAdapter();
+ doResumeUpdates();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mHandler.removeCallbacks(mTodayUpdater);
+ }
+
+ // @Override
+ // public void onSaveInstanceState(Bundle outState) {
+ // outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true));
+ // }
+
+ /**
+ * Updates the user preference fields. Override this to use a different
+ * preference space.
+ */
+ protected void doResumeUpdates() {
+ // Get default week start based on locale, subtracting one for use with
+ // android Time.
+ Calendar cal = Calendar.getInstance(Locale.getDefault());
+ mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
+
+ mShowWeekNumber = false;
+
+ goTo(mSelectedDay, false, false, false);
+ mAdapter.setSelectedDay(mSelectedDay);
+ mTodayUpdater.run();
+ }
+
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param time The time to move to
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location
+ * @param setSelected Whether to set the given time as selected
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible
+ * @return Whether or not the view animated to the new location
+ */
+ public boolean goTo(CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {
+
+ // Set the selected day
+ if (setSelected) {
+ mSelectedDay.set(day);
+ }
+
+ mTempDay.set(day);
+ // Get the week we're going to
+ // TODO push Util function into Calendar public api.
+ int position = (day.year - mController.getMinYear())
+ * SimpleMonthAdapter.MONTHS_IN_YEAR + day.month;
+
+ View child;
+ int i = 0;
+ int top = 0;
+ // Find a child that's completely in the view
+ do {
+ child = getChildAt(i++);
+ if (child == null) {
+ break;
+ }
+ top = child.getTop();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "child at " + (i - 1) + " has top " + top);
+ }
+ } while (top < 0);
+
+ // Compute the first and last position visible
+ int selectedPosition;
+ if (child != null) {
+ selectedPosition = getPositionForView(child);
+ } else {
+ selectedPosition = 0;
+ }
+
+ if (setSelected) {
+ mAdapter.setSelectedDay(mSelectedDay);
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "GoTo position " + position);
+ }
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position != selectedPosition || forceScroll) {
+ setMonthDisplayed(mTempDay);
+ mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
+ if (animate) {
+ smoothScrollToPositionFromTop(
+ position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
+ return true;
+ } else {
+ setSelectionFromTop(position, LIST_TOP_OFFSET);
+ onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
+ }
+ } else if (setSelected) {
+ setMonthDisplayed(mSelectedDay);
+ }
+ return false;
+ }
+
+ /**
+ * Updates the title and selected month if the view has moved to a new
+ * month.
+ */
+ @Override
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ SimpleMonthView child = (SimpleMonthView) view.getChildAt(0);
+ if (child == null) {
+ return;
+ }
+
+ // Figure out where we are
+ long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+ mPreviousScrollPosition = currScroll;
+ mPreviousScrollState = mCurrentScrollState;
+ }
+
+ /**
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
+ */
+ protected void setMonthDisplayed(CalendarDay date) {
+ mCurrentMonthDisplayed = date.month;
+ invalidateViews();
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // use a post to prevent re-entering onScrollStateChanged before it
+ // exits
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
+
+ protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
+
+ protected class ScrollStateRunnable implements Runnable {
+ private int mNewState;
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ public void doScrollStateChange(AbsListView view, int scrollState) {
+ mHandler.removeCallbacks(this);
+ mNewState = scrollState;
+ mHandler.postDelayed(this, SCROLL_CHANGE_DELAY);
+ }
+
+ @Override
+ public void run() {
+ mCurrentScrollState = mNewState;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
+ }
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ mPreviousScrollState = mNewState;
+ int i = 0;
+ View child = getChildAt(i);
+ while (child != null && child.getBottom() <= 0) {
+ child = getChildAt(++i);
+ }
+ if (child == null) {
+ // The view is no longer visible, just return
+ return;
+ }
+ int firstPosition = getFirstVisiblePosition();
+ int lastPosition = getLastVisiblePosition();
+ boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1;
+ final int top = child.getTop();
+ final int bottom = child.getBottom();
+ final int midpoint = getHeight() / 2;
+ if (scroll && top < LIST_TOP_OFFSET) {
+ if (bottom > midpoint) {
+ smoothScrollBy(top, GOTO_SCROLL_DURATION);
+ } else {
+ smoothScrollBy(bottom, GOTO_SCROLL_DURATION);
+ }
+ }
+ } else {
+ mPreviousScrollState = mNewState;
+ }
+ }
+ }
+}
diff --git a/src/com/android/datetimepicker/date/MonthPickerView.java b/src/com/android/datetimepicker/date/MonthPickerView.java
new file mode 100644
index 0000000..81f10ab
--- /dev/null
+++ b/src/com/android/datetimepicker/date/MonthPickerView.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import android.content.Context;
+import android.graphics.drawable.StateListDrawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.SimpleAdapter;
+
+import com.android.datetimepicker.R;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Displays a selectable list of months in a grid format.
+ */
+public class MonthPickerView extends GridView implements OnItemClickListener {
+
+ private static final int NUM_COLUMNS = 3;
+ private static final int NUM_MONTHS = 12;
+
+ private final Calendar mCalendar = Calendar.getInstance();
+ private final DatePickerController mController;
+
+ /**
+ * @param context
+ */
+ public MonthPickerView(Context context, DatePickerController controller) {
+ super(context);
+ ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ setLayoutParams(frame);
+ setNumColumns(NUM_COLUMNS);
+ init(context);
+ mController = controller;
+ setOnItemClickListener(this);
+ setSelector(new StateListDrawable());
+ }
+
+ private void init(Context context) {
+ ArrayList<String> months = new ArrayList<String>();
+ mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+ for (int i = 0; i < NUM_MONTHS; i++) {
+ mCalendar.set(Calendar.MONTH, i);
+ months.add(mCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()));
+ }
+ setAdapter(new ArrayAdapter<String>(context, R.layout.month_text_view, months));
+ }
+
+ public class MonthPickerAdapter extends SimpleAdapter {
+
+ /**
+ * @param context
+ * @param data
+ * @param resource
+ * @param from
+ * @param to
+ */
+ public MonthPickerAdapter(Context context,
+ List<? extends Map<String, ?>> data,
+ int resource, String[] from, int[] to) {
+ super(context, data, resource, from, to);
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mController.onMonthPickerSelectionChanged(position);
+ }
+}
diff --git a/src/com/android/datetimepicker/date/SelectableTextView.java b/src/com/android/datetimepicker/date/SelectableTextView.java
new file mode 100644
index 0000000..0da34fd
--- /dev/null
+++ b/src/com/android/datetimepicker/date/SelectableTextView.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.datetimepicker.R;
+
+/**
+ * A text view which, when pressed or selected, displays a blue circle around the text.
+ */
+public class SelectableTextView extends TextView {
+
+ private static final int SELECTED_CIRCLE_ALPHA = 60;
+
+ Paint mCirclePaint = new Paint();
+
+ private final int mRadius;
+ private final int mCircleColor;
+
+ public SelectableTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ Resources res = context.getResources();
+ mCircleColor = res.getColor(R.color.selected_text);
+ mRadius = res.getDimensionPixelOffset(R.dimen.month_select_circle_radius);
+ init();
+ }
+
+ private void init() {
+ mCirclePaint.setFakeBoldText(true);
+ mCirclePaint.setAntiAlias(true);
+ mCirclePaint.setColor(mCircleColor);
+ mCirclePaint.setTextAlign(Align.CENTER);
+ mCirclePaint.setStyle(Style.FILL);
+ mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (isPressed() || isSelected()) {
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius,
+ mCirclePaint);
+ }
+ super.onDraw(canvas);
+ }
+}
diff --git a/src/com/android/datetimepicker/date/SimpleMonthAdapter.java b/src/com/android/datetimepicker/date/SimpleMonthAdapter.java
new file mode 100644
index 0000000..789966c
--- /dev/null
+++ b/src/com/android/datetimepicker/date/SimpleMonthAdapter.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.android.datetimepicker.date;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+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 OnTouchListener {
+
+ private static final String TAG = "SimpleMonthAdapter";
+
+ private final Context mContext;
+ private final DatePickerController mController;
+
+ private GestureDetector mGestureDetector;
+ 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;
+ 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,
+ DatePickerController controller) {
+ mContext = context;
+ mController = controller;
+ init();
+ setSelectedDay(mController.getSelectedDay());
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param selectedTime The time 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() {
+ mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
+ mSelectedDay = new CalendarDay(System.currentTimeMillis());
+ }
+
+ @Override
+ public int getCount() {
+ return (mController.getMaxYear() - mController.getMinYear()) * MONTHS_IN_YEAR;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ SimpleMonthView v;
+ HashMap<String, Integer> drawingParams = null;
+ if (convertView != null) {
+ v = (SimpleMonthView) convertView;
+ // We store the drawing parameters in the view so it can be recycled
+ drawingParams = (HashMap<String, Integer>) 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.setOnTouchListener(this);
+ }
+ if (drawingParams == null) {
+ drawingParams = new HashMap<String, Integer>();
+ }
+ 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 boolean onTouch(View v, MotionEvent event) {
+ if (mGestureDetector.onTouchEvent(event)) {
+ CalendarDay day = ((SimpleMonthView) v).getDayFromLocation(event.getX(), event.getY());
+ if (day != null) {
+ onDayTapped(day);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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.onDayPickerSelectionChanged(day.year, day.month, day.day);
+ setSelectedDay(day);
+ }
+
+ /**
+ * This is here so we can identify single tap events and set the selected
+ * day correctly
+ */
+ protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+ }
+}
diff --git a/src/com/android/datetimepicker/date/SimpleMonthView.java b/src/com/android/datetimepicker/date/SimpleMonthView.java
new file mode 100644
index 0000000..2ce6647
--- /dev/null
+++ b/src/com/android/datetimepicker/date/SimpleMonthView.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.text.format.Time;
+import android.view.View;
+
+import com.android.datetimepicker.R;
+import com.android.datetimepicker.Utils;
+import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay;
+
+import java.security.InvalidParameterException;
+import java.util.Calendar;
+import java.util.HashMap;
+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 {
+
+ /**
+ * 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 Time#SUNDAY}
+ * through {@link Time#SATURDAY}.
+ */
+ public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
+ /**
+ * Which day the week should start on. {@link Time#SUNDAY} through
+ * {@link 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 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;
+
+ protected Rect r = new Rect();
+ protected Paint mMonthNumPaint;
+ protected Paint mMonthTitlePaint;
+ protected Paint mSelectedCirclePaint;
+ protected Paint mMonthDayLabelPaint;
+
+ // 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 int mNumRows = DEFAULT_NUM_ROWS;
+
+ protected int mBGColor;
+ protected int mFocusMonthColor;
+ protected int mOtherMonthColor;
+ protected int mDaySeparatorColor;
+ protected int mTodayOutlineColor;
+ protected int mWeekNumColor;
+
+ protected int mDayTextColor;
+ protected int mMonthTitleTextColor;
+
+ public SimpleMonthView(Context context) {
+ super(context);
+
+ Resources res = context.getResources();
+
+ mDayLabelCalendar = Calendar.getInstance();
+ mCalendar = Calendar.getInstance();
+ mBGColor = res.getColor(R.color.month_bgcolor);
+ mFocusMonthColor = res.getColor(R.color.month_mini_day_number);
+ mOtherMonthColor = res.getColor(R.color.month_other_month_day_number);
+ mDaySeparatorColor = res.getColor(R.color.month_grid_lines);
+ mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color);
+ mDayTextColor = res.getColor(R.color.calendar_day_number);
+ mMonthTitleTextColor = res.getColor(R.color.selected_text);
+ mWeekNumColor = res.getColor(R.color.month_week_num_color);
+
+ 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.pager_height) - MONTH_HEADER_SIZE)
+ / MAX_NUM_ROWS;
+ // Sets up any standard paints that will be used
+ initView();
+ }
+
+ /**
+ * 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.setColor(mMonthTitleTextColor);
+ mMonthTitlePaint.setTextAlign(Align.CENTER);
+ mMonthTitlePaint.setStyle(Style.FILL);
+ mMonthTitlePaint.setFakeBoldText(true);
+
+ mSelectedCirclePaint = new Paint();
+ mSelectedCirclePaint.setFakeBoldText(true);
+ mSelectedCirclePaint.setAntiAlias(true);
+ mSelectedCirclePaint.setColor(mMonthTitleTextColor);
+ 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.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}
+ * @param tz The time zone this view should reference times in
+ */
+ public void setMonthParams(HashMap<String, Integer> 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);
+ } 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();
+ }
+
+ 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;
+ }
+
+ private void drawMonthTitle(Canvas canvas) {
+ int x = (mWidth + 2 * mPadding) / 2;
+ int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2;
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append(mCalendar.getDisplayName(Calendar.MONTH, Calendar.LONG,
+ Locale.getDefault()));
+ sbuf.append(" ");
+ sbuf.append(mYear);
+ canvas.drawText(sbuf.toString(), 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;
+ if (mSelectedDay == dayNumber) {
+ canvas.drawCircle(x, y - (MINI_DAY_NUMBER_TEXT_SIZE / 3), DAY_SELECTED_CIRCLE_SIZE,
+ mSelectedCirclePaint);
+ }
+
+ if (mHasToday && mToday == dayNumber) {
+ mMonthNumPaint.setColor(mMonthTitleTextColor);
+ } else {
+ mMonthNumPaint.setColor(mDayTextColor);
+ }
+ canvas.drawText(Integer.valueOf(dayNumber).toString(), 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;
+ }
+ // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+ int row = (int) (y - mRowHeight) / 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);
+ }
+
+}
diff --git a/src/com/android/datetimepicker/date/YearPickerView.java b/src/com/android/datetimepicker/date/YearPickerView.java
new file mode 100644
index 0000000..916437e
--- /dev/null
+++ b/src/com/android/datetimepicker/date/YearPickerView.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.datetimepicker.date;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnValueChangeListener;
+
+/**
+ * A number picker allowing a user to choose a specific year.
+ */
+public class YearPickerView extends FrameLayout implements OnValueChangeListener {
+
+ private final NumberPicker mPicker;
+ private final DatePickerController mController;
+
+ public YearPickerView(Context context, DatePickerController controller) {
+ super(context);
+ mController = controller;
+ ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ setLayoutParams(frame);
+ mPicker = new NumberPicker(context);
+ LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ params.gravity = Gravity.CENTER;
+ mPicker.setLayoutParams(params);
+ mPicker.setOnLongPressUpdateInterval(100);
+ mPicker.setMinValue(controller.getMinYear());
+ mPicker.setMaxValue(controller.getMaxYear());
+ mPicker.setWrapSelectorWheel(false);
+ mPicker.setValue(controller.getSelectedDay().year);
+ mPicker.setOnValueChangedListener(this);
+ addView(mPicker);
+ }
+
+ public void setValue(int value) {
+ mPicker.setValue(value);
+ }
+
+ public void onChange() {
+ mPicker.setMinValue(mController.getMinYear());
+ mPicker.setMaxValue(mController.getMaxYear());
+ requestLayout();
+ }
+
+ @Override
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ mController.onYearPickerSelectionChanged(newVal);
+ }
+}