├── .gitignore
├── .idea
├── gradle.xml
└── runConfigurations.xml
├── README.md
├── SpinnerDatePickerExample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── tsongkha
│ │ └── spinnerdatepickerexample
│ │ ├── MainActivityTest.java
│ │ └── NumberPickers.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── tsongkha
│ │ └── spinnerdatepickerexample
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── SpinnerDatePickerLib
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── tsongkha
│ │ └── spinnerdatepicker
│ │ ├── CommonDateUtils.java
│ │ ├── DatePicker.java
│ │ ├── DatePickerDialog.java
│ │ ├── DateUtils.java
│ │ ├── ICU.java
│ │ ├── NumberPickers.java
│ │ ├── OnDateChangedListener.java
│ │ ├── SpinnerDatePickerDialogBuilder.java
│ │ └── TwoDigitFormatter.java
│ └── res
│ ├── layout
│ ├── date_picker.xml
│ ├── date_picker_container.xml
│ ├── date_picker_dialog.xml
│ ├── date_picker_dialog_container.xml
│ ├── number_picker_day_month.xml
│ └── number_picker_year.xml
│ └── values
│ └── strings.xml
├── build.gradle
├── github
├── material_design_datepicker_example.png
└── spinner_datepicker_example.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | /SpinnerDatePickerExample/google-services.json
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | gradle.properties
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://android-arsenal.com/api?level=18) [](https://android-arsenal.com/details/1/6319) [](https://jitpack.io/#drawers/SpinnerDatePicker)
2 |
3 | ~~Spinner DatePicker~~
4 | -----
5 |
6 | # Deprecation
7 |
8 | This repo is no longer being maintained due to time constraints. #sorrynotsorry
9 |
10 | If you wish to transfer ownership then please contact the author via issues.
11 |
12 | ## Summary
13 |
14 | The old "spinner" style DatePicker for newer devices.
15 |
16 | 
17 |
18 | ## Motivation
19 |
20 | The default Material Design DatePicker has poor usability for choosing a date of birth. It seems it is hard for users to find the "year" button and they will often simply swipe left or right through the months in order to find their date of birth.
21 |
22 | 
23 |
24 | The previous Holo DatePicker with sliding NumberPickers is much more suitable for this use case however it is no longer available for Marshmallow devices and up.
25 |
26 | This library is heavily based on the latest [Android Open Source Project](https://source.android.com/) DatePicker (source code [here](http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/java/android/widget/DatePickerSpinnerDelegate.java)) with the addition of being able to style the NumberPickers (the dials/spinners in the DatePicker).
27 |
28 | ## Adding styles
29 |
30 | The DatePicker is the simple aggregation of three NumberPickers. You can style the DatePicker easily with a NumberPicker style (in styles.xml in the values folder):
31 |
32 |
37 |
38 | where `colorControlNormal` is the color of the horizontal bars (dividers) in the `NumberPicker`. See [this StackOverflow question](https://stackoverflow.com/q/20148671/5241933)
39 |
40 | And then:
41 |
42 | new SpinnerDatePickerDialogBuilder()
43 | .context(MainActivity.this)
44 | .callback(MainActivity.this)
45 | .spinnerTheme(R.style.NumberPickerStyle)
46 | .showTitle(true)
47 | .customTitle("My custom title")
48 | .showDaySpinner(true)
49 | .defaultDate(2017, 0, 1)
50 | .maxDate(2020, 0, 1)
51 | .minDate(2000, 0, 1)
52 | .build()
53 | .show();
54 |
55 | The example project should make it clear - get it by cloning the repo.
56 |
57 | Note that full support is only for API >= 18. API < 18 you'll get the DatePicker but there is no easy way to style it correctly.
58 |
59 | ## Usage in a project
60 |
61 | Add the following to your **project** level `build.gradle`:
62 |
63 | ```gradle
64 | allprojects {
65 | repositories {
66 | maven { url "https://jitpack.io" }
67 | }
68 | }
69 | ```
70 |
71 | Add this to your **app level** `build.gradle`:
72 |
73 | ```gradle
74 | dependencies {
75 | compile 'com.github.drawers:SpinnerDatePicker:2.0.1'
76 | }
77 | ```
78 |
79 | Philosophy
80 | ==========
81 |
82 | The aim of this project is to produce a lightweight and robust DatePicker with an API similar to that of the standard Android DatePicker. Hence the library has no external dependencies and no fancy features. Espresso automated UI testing is performed on the sample project using Firebase test lab.
83 |
84 | Contributing
85 | ============
86 |
87 | Please open an issue first before making a pull request. Pull requests should be accompanied by tests if possible.
88 |
89 | License
90 | =======
91 |
92 | Copyright 2017 AOSP, David Rawson
93 |
94 | Licensed under the Apache License, Version 2.0 (the "License");
95 | you may not use this file except in compliance with the License.
96 | You may obtain a copy of the License at
97 |
98 | http://www.apache.org/licenses/LICENSE-2.0
99 |
100 | Unless required by applicable law or agreed to in writing, software
101 | distributed under the License is distributed on an "AS IS" BASIS,
102 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
103 | See the License for the specific language governing permissions and
104 | limitations under the License.
105 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.tsongkha.spinnerdatepickerexample"
7 | minSdkVersion 16
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0.1"
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | androidTestCompile 'androidx.test.espresso:espresso-core:3.1.1'
23 | androidTestCompile 'androidx.test:rules:1.1.1'
24 |
25 | compile project(':SpinnerDatePickerLib')
26 | compile 'androidx.appcompat:appcompat:1.0.0'
27 | compile 'androidx.constraintlayout:constraintlayout:1.1.2'
28 | compile 'com.google.android.material:material:1.0.0'
29 | }
30 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/rawsond/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/androidTest/java/com/tsongkha/spinnerdatepickerexample/MainActivityTest.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepickerexample;
2 |
3 | import androidx.test.espresso.matcher.ViewMatchers;
4 | import androidx.test.rule.ActivityTestRule;
5 | import androidx.test.runner.AndroidJUnit4;
6 |
7 | import com.tsongkha.spinnerdatepicker.SpinnerDatePickerDialogBuilder;
8 |
9 | import org.junit.Before;
10 | import org.junit.Rule;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 |
14 | import static androidx.test.espresso.Espresso.onView;
15 | import static androidx.test.espresso.action.ViewActions.click;
16 | import static androidx.test.espresso.assertion.ViewAssertions.matches;
17 | import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
18 | import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
19 | import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
20 | import static androidx.test.espresso.matcher.ViewMatchers.withId;
21 | import static androidx.test.espresso.matcher.ViewMatchers.withText;
22 | import static org.hamcrest.Matchers.containsString;
23 |
24 |
25 | @RunWith(AndroidJUnit4.class)
26 | public class MainActivityTest {
27 |
28 | public static final int SCROLL_UP = 40;
29 | public static final int SCROLL_DOWN = -40;
30 |
31 | @Rule
32 | public ActivityTestRule mainActivityActivityTestRule = new ActivityTestRule<>(
33 | MainActivity.class, true, false);
34 |
35 | MainActivity mainActivity;
36 |
37 | @Before
38 | public void setUp() throws Exception {
39 | mainActivity = mainActivityActivityTestRule.launchActivity(null);
40 | }
41 |
42 | @Test
43 | public void testDefaultDatePickerDialogDisplays() {
44 | //act
45 | onView(withId(R.id.set_date_button)).perform(click());
46 |
47 | //assert
48 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.datePickerContainer))
49 | .check(matches(isCompletelyDisplayed()));
50 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.parent))
51 | .check(matches(isCompletelyDisplayed()));
52 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.year))
53 | .check(NumberPickers.isDisplayed("1980"));
54 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.month))
55 | .check(NumberPickers.isDisplayed("Jan"));
56 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.day))
57 | .check(NumberPickers.isDisplayed("1"));
58 | onView(withClassName(containsString("DialogTitle"))).check(
59 | matches(withText("January 1, 1980")));
60 | }
61 |
62 | @Test
63 | public void testDaySpinner() throws Exception {
64 | //act
65 | mainActivity.runOnUiThread(new Runnable() {
66 | @Override
67 | public void run() {
68 | mainActivity.showDate(1980, 0, 1, R.style.DatePickerSpinner);
69 | }
70 | });
71 |
72 | //assert
73 | onView(withId(R.id.day)).perform(NumberPickers.scroll(SCROLL_DOWN)).check(
74 | NumberPickers.isDisplayed("2"));
75 | onView(withId(R.id.day)).perform(NumberPickers.scroll(SCROLL_UP)).check(
76 | NumberPickers.isDisplayed("1"));
77 | onView(withId(R.id.day)).perform(NumberPickers.scroll(SCROLL_UP)).check(
78 | NumberPickers.isDisplayed("31"));
79 | onView(withClassName(containsString("DialogTitle"))).check(
80 | matches(withText("December 31, 1979")));
81 | }
82 |
83 | @Test
84 | public void testDaySpinnerNotShown() throws Exception {
85 | //act
86 | mainActivity.runOnUiThread(new Runnable() {
87 | @Override
88 | public void run() {
89 | new SpinnerDatePickerDialogBuilder()
90 | .context(mainActivity)
91 | .showDaySpinner(false)
92 | .spinnerTheme(R.style.DatePickerSpinner)
93 | .build()
94 | .show();
95 | }
96 | });
97 |
98 | //assert
99 | onView(withId(R.id.day)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
100 | onView(withId(R.id.month)).perform(NumberPickers.scroll(SCROLL_UP)).check(
101 | NumberPickers.isDisplayed("Dec"));
102 | }
103 |
104 | @Test
105 | public void testMonthSpinner() throws Exception {
106 | //act
107 | mainActivity.runOnUiThread(new Runnable() {
108 | @Override
109 | public void run() {
110 | mainActivity.showDate(1980, 0, 1, R.style.DatePickerSpinner);
111 | }
112 | });
113 |
114 | //assert
115 | onView(withId(R.id.month)).perform(NumberPickers.scroll(SCROLL_DOWN)).check(
116 | NumberPickers.isDisplayed("Feb"));
117 | onView(withId(R.id.month)).perform(NumberPickers.scroll(SCROLL_UP)).check(
118 | NumberPickers.isDisplayed("Jan"));
119 | onView(withId(R.id.month)).perform(NumberPickers.scroll(SCROLL_UP)).check(
120 | NumberPickers.isDisplayed("Dec"));
121 | onView(withClassName(containsString("DialogTitle"))).check(
122 | matches(withText("December 1, 1979")));
123 | }
124 |
125 | @Test
126 | public void testYearSpinner() throws Exception {
127 | //act
128 | mainActivity.runOnUiThread(new Runnable() {
129 | @Override
130 | public void run() {
131 | mainActivity.showDate(1980, 0, 1, R.style.DatePickerSpinner);
132 | }
133 | });
134 |
135 | //assert
136 | onView(withId(R.id.year)).perform(NumberPickers.scroll(SCROLL_UP)).check(
137 | NumberPickers.isDisplayed("1979"));
138 | onView(withId(R.id.year)).perform(NumberPickers.scroll(SCROLL_DOWN)).check(
139 | NumberPickers.isDisplayed("1980"));
140 | onView(withId(R.id.year)).perform(NumberPickers.scroll(SCROLL_DOWN)).check(
141 | NumberPickers.isDisplayed("1981"));
142 | onView(withClassName(containsString("DialogTitle"))).check(
143 | matches(withText("January 1, 1981")));
144 | }
145 |
146 | @Test
147 | public void testCustomDate() throws Exception {
148 | //act
149 | mainActivity.runOnUiThread(new Runnable() {
150 | @Override
151 | public void run() {
152 | mainActivity.showDate(1970, 11, 31, R.style.DatePickerSpinner);
153 | }
154 | });
155 |
156 | //assert
157 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.datePickerContainer))
158 | .check(matches(isCompletelyDisplayed()));
159 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.parent))
160 | .check(matches(isCompletelyDisplayed()));
161 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.year))
162 | .check(NumberPickers.isDisplayed("1970"));
163 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.month))
164 | .check(NumberPickers.isDisplayed("Dec"));
165 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.day))
166 | .check(NumberPickers.isDisplayed("31"));
167 | onView(withClassName(containsString("DialogTitle"))).check(
168 | matches(withText("December 31, 1970")));
169 | }
170 |
171 | @Test
172 | public void testDaysInMonthDecreaseViaMonthChange() throws Exception {
173 | mainActivity.runOnUiThread(new Runnable() {
174 | @Override
175 | public void run() {
176 | mainActivity.showDate(1980, 0, 31, R.style.DatePickerSpinner);
177 | }
178 | });
179 |
180 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.month))
181 | .perform(NumberPickers.scroll(SCROLL_DOWN));
182 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.day))
183 | .check(NumberPickers.isDisplayed("29"));
184 | onView(withClassName(containsString("DialogTitle"))).check(
185 | matches(withText("February 29, 1980")));
186 | }
187 |
188 | @Test
189 | public void testDaysInMonthDecreaseViaYearChange() throws Exception {
190 | mainActivity.runOnUiThread(new Runnable() {
191 | @Override
192 | public void run() {
193 | mainActivity.showDate(1980, 1, 29, R.style.DatePickerSpinner);
194 | }
195 | });
196 |
197 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.year))
198 | .perform(NumberPickers.scroll(SCROLL_DOWN));
199 | onView(withId(com.tsongkha.spinnerdatepicker.R.id.day))
200 | .check(NumberPickers.isDisplayed("1"));
201 | onView(withClassName(containsString("DialogTitle"))).check(
202 | matches(withText("March 1, 1981")));
203 | }
204 |
205 | @Test
206 | public void testTitleNotShown() throws Exception {
207 | //act
208 | mainActivity.runOnUiThread(new Runnable() {
209 | @Override
210 | public void run() {
211 | new SpinnerDatePickerDialogBuilder()
212 | .context(mainActivity)
213 | .showTitle(false)
214 | .spinnerTheme(R.style.DatePickerSpinner)
215 | .build()
216 | .show();
217 | }
218 | });
219 |
220 | //assert
221 | onView(withClassName(containsString("DialogTitle"))).check(
222 | matches(withText(" ")));
223 | }
224 |
225 | @Test
226 | public void testIsCustomTitleShown() throws Exception {
227 | //act
228 | mainActivity.runOnUiThread(new Runnable() {
229 | @Override
230 | public void run() {
231 | new SpinnerDatePickerDialogBuilder()
232 | .context(mainActivity)
233 | .showTitle(true)
234 | .customTitle("My custom title")
235 | .spinnerTheme(R.style.DatePickerSpinner)
236 | .build()
237 | .show();
238 | }
239 | });
240 |
241 | //assert
242 | onView(withClassName(containsString("DialogTitle"))).check(
243 | matches(withText("My custom title")));
244 | }
245 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/androidTest/java/com/tsongkha/spinnerdatepickerexample/NumberPickers.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepickerexample;
2 |
3 | import androidx.test.espresso.NoMatchingViewException;
4 | import androidx.test.espresso.UiController;
5 | import androidx.test.espresso.ViewAction;
6 | import androidx.test.espresso.ViewAssertion;
7 | import androidx.test.espresso.action.ViewActions;
8 | import androidx.test.espresso.matcher.ViewMatchers;
9 | import android.view.View;
10 | import android.widget.EditText;
11 | import android.widget.NumberPicker;
12 |
13 | import org.hamcrest.Matcher;
14 |
15 | import java.lang.reflect.Field;
16 |
17 | import static androidx.test.espresso.Espresso.onView;
18 | import static junit.framework.Assert.assertEquals;
19 |
20 | /**
21 | * Created by rawsond on 27/08/17.
22 | */
23 |
24 | class NumberPickers {
25 |
26 | @Deprecated
27 | public static ViewAction setNumber(final int num) {
28 | return new ViewAction() {
29 | @Override
30 | public void perform(UiController uiController, View view) {
31 | NumberPicker np = (NumberPicker) view;
32 | np.setValue(num);
33 | getOnValueChangeListener(np).onValueChange(np, num, num);
34 | }
35 |
36 | @Override
37 | public String getDescription() {
38 | return "Set the passed number into the NumberPicker";
39 | }
40 |
41 | @Override
42 | public Matcher getConstraints() {
43 | return ViewMatchers.isAssignableFrom(NumberPicker.class);
44 | }
45 | };
46 | }
47 |
48 | public static ViewAction typeInNumberPicker(final String value) {
49 | return new ViewAction() {
50 | @Override
51 | public void perform(UiController uiController, View view) {
52 | NumberPicker np = (NumberPicker) view;
53 | EditText et = com.tsongkha.spinnerdatepicker.NumberPickers.findEditText(np);
54 | ViewActions.typeText(value).perform(uiController, et);
55 | ViewActions.closeSoftKeyboard();
56 | }
57 |
58 | @Override
59 | public String getDescription() {
60 | return "Set the passed number into the NumberPicker";
61 | }
62 |
63 | @Override
64 | public Matcher getConstraints() {
65 | return ViewMatchers.isAssignableFrom(NumberPicker.class);
66 | }
67 | };
68 | }
69 |
70 | public static ViewAction scroll(final int yOffsetInDp) {
71 | return new ViewAction() {
72 | @Override
73 | public void perform(UiController uiController, View view) {
74 | NumberPicker np = (NumberPicker) view;
75 | int yOffsetInPx = (int) (yOffsetInDp * view.getResources().getDisplayMetrics().density);
76 | np.scrollBy(0, yOffsetInPx);
77 | }
78 |
79 | @Override
80 | public String getDescription() {
81 | return "Scroll up or down a given yOffset in dp";
82 | }
83 |
84 | @Override
85 | public Matcher getConstraints() {
86 | return ViewMatchers.isAssignableFrom(NumberPicker.class);
87 | }
88 | };
89 | }
90 |
91 | public static ViewAssertion isDisplayed(final String expectedPick) {
92 | return new ViewAssertion() {
93 | @Override
94 | public void check(View view, NoMatchingViewException noViewFoundException) {
95 | NumberPicker numberPicker = (NumberPicker) view;
96 | String[] displayedValues = numberPicker.getDisplayedValues();
97 | String actualPicked = null;
98 | if (displayedValues == null) {
99 | actualPicked = Integer.toString(numberPicker.getValue());
100 | } else {
101 | actualPicked = numberPicker.getDisplayedValues()[numberPicker.getValue()];
102 | }
103 | assertEquals(expectedPick, actualPicked);
104 | }
105 | };
106 | }
107 |
108 | public static NumberPicker.OnValueChangeListener getOnValueChangeListener(NumberPicker numberPicker) {
109 | try {
110 | Field onValueChangedListener = NumberPicker.class.getDeclaredField(
111 | "mOnValueChangeListener");
112 | onValueChangedListener.setAccessible(true);
113 | return (NumberPicker.OnValueChangeListener) onValueChangedListener.get(numberPicker);
114 | } catch (NoSuchFieldException | IllegalAccessException e) {
115 | e.printStackTrace();
116 | return null;
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/java/com/tsongkha/spinnerdatepickerexample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepickerexample;
2 |
3 | import android.os.Bundle;
4 | import androidx.annotation.Nullable;
5 | import androidx.annotation.VisibleForTesting;
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import android.view.View;
8 | import android.widget.Button;
9 | import android.widget.TextView;
10 |
11 | import com.tsongkha.spinnerdatepicker.DatePicker;
12 | import com.tsongkha.spinnerdatepicker.DatePickerDialog;
13 | import com.tsongkha.spinnerdatepicker.SpinnerDatePickerDialogBuilder;
14 |
15 | import java.text.SimpleDateFormat;
16 | import java.util.Calendar;
17 | import java.util.GregorianCalendar;
18 | import java.util.Locale;
19 |
20 | /**
21 | * Created by rawsond on 25/08/17.
22 | */
23 |
24 | public class MainActivity extends AppCompatActivity implements DatePickerDialog.OnDateSetListener , DatePickerDialog.OnDateCancelListener{
25 |
26 | TextView dateTextView;
27 | Button dateButton;
28 | SimpleDateFormat simpleDateFormat;
29 |
30 | @Override
31 | protected void onCreate(@Nullable Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_main);
34 | dateButton = (Button) findViewById(R.id.set_date_button);
35 | dateTextView = (TextView) findViewById(R.id.date_textview);
36 | simpleDateFormat = new SimpleDateFormat("dd MM yyyy", Locale.US);
37 | dateButton.setOnClickListener(new View.OnClickListener() {
38 | @Override
39 | public void onClick(View view) {
40 | showDate(1980, 0, 1, R.style.DatePickerSpinner);
41 | }
42 | });
43 | }
44 |
45 | @Override
46 | public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
47 | Calendar calendar = new GregorianCalendar(year, monthOfYear, dayOfMonth);
48 | dateTextView.setText(simpleDateFormat.format(calendar.getTime()));
49 | }
50 |
51 | @Override
52 | public void onCancelled(DatePicker view) {
53 | dateTextView.setText(R.string.cancelled);
54 | }
55 |
56 |
57 | @VisibleForTesting
58 | void showDate(int year, int monthOfYear, int dayOfMonth, int spinnerTheme) {
59 | new SpinnerDatePickerDialogBuilder()
60 | .context(MainActivity.this)
61 | .callback(MainActivity.this)
62 | .onCancel(MainActivity.this)
63 | .spinnerTheme(spinnerTheme)
64 | .defaultDate(year, monthOfYear, dayOfMonth)
65 | .build()
66 | .show();
67 | }
68 |
69 |
70 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/SpinnerDatePickerExample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @color/material_teal
4 | @color/material_teal_dark
5 | @color/material_teal_light
6 | #009688
7 | #52c7b8
8 | #00675b
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SlideDatePicker
3 | MainActivity
4 | Cancelled!
5 |
6 |
--------------------------------------------------------------------------------
/SpinnerDatePickerExample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 |
4 | group = 'com.github.drawers'
5 | version = '1.0.6'
6 |
7 | android {
8 | compileSdkVersion 28
9 |
10 | defaultConfig {
11 | minSdkVersion 16
12 | targetSdkVersion 28
13 | versionCode 210
14 | versionName "2.1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | lintOptions {
27 | abortOnError false
28 | }
29 |
30 | testOptions {
31 | unitTests.returnDefaultValues = true
32 | }
33 | }
34 |
35 |
36 | dependencies {
37 | compile 'androidx.appcompat:appcompat:1.0.0'
38 | }
39 |
40 | task sourcesJar(type: Jar) {
41 | from android.sourceSets.main.java.srcDirs
42 | classifier = 'sources'
43 | }
44 |
45 | task javadoc(type: Javadoc) {
46 | options.charSet = 'UTF-8'
47 | failOnError false
48 | source = android.sourceSets.main.java.sourceFiles
49 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
50 | classpath += configurations.compile
51 | }
52 |
53 | task javadocJar(type: Jar, dependsOn: javadoc) {
54 | classifier = 'javadoc'
55 | from javadoc.destinationDir
56 | }
57 |
58 | artifacts {
59 | archives sourcesJar
60 | archives javadocJar
61 | }
62 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/rawsond/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/CommonDateUtils.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;/*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | import java.text.SimpleDateFormat;
17 | import java.util.Locale;
18 | /**
19 | * Common date utilities.
20 | */
21 | public class CommonDateUtils {
22 | // All the SimpleDateFormats in this class use the UTC timezone
23 | public static final SimpleDateFormat NO_YEAR_DATE_FORMAT =
24 | new SimpleDateFormat("--MM-dd", Locale.US);
25 | public static final SimpleDateFormat FULL_DATE_FORMAT =
26 | new SimpleDateFormat("yyyy-MM-dd", Locale.US);
27 | public static final SimpleDateFormat DATE_AND_TIME_FORMAT =
28 | new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
29 | public static final SimpleDateFormat NO_YEAR_DATE_AND_TIME_FORMAT =
30 | new SimpleDateFormat("--MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
31 | /**
32 | * Exchange requires 8:00 for birthdays
33 | */
34 | public final static int DEFAULT_HOUR = 8;
35 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/DatePicker.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;
2 | /* Fork of Oreo DatePickerSpinnerDelegate
3 | *
4 | * Original class is Copyright (C) 2016 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.content.Context;
20 | import android.content.res.Configuration;
21 | import android.os.Parcel;
22 | import android.os.Parcelable;
23 | import android.text.InputType;
24 | import android.text.format.DateFormat;
25 | import android.view.ContextThemeWrapper;
26 | import android.view.LayoutInflater;
27 | import android.view.View;
28 | import android.view.ViewGroup;
29 | import android.view.accessibility.AccessibilityEvent;
30 | import android.view.inputmethod.EditorInfo;
31 | import android.view.inputmethod.InputMethodManager;
32 | import android.widget.EditText;
33 | import android.widget.FrameLayout;
34 | import android.widget.LinearLayout;
35 | import android.widget.NumberPicker;
36 | import android.widget.NumberPicker.OnValueChangeListener;
37 | import android.widget.TextView;
38 |
39 | import java.text.DateFormatSymbols;
40 | import java.text.SimpleDateFormat;
41 | import java.util.Arrays;
42 | import java.util.Calendar;
43 | import java.util.Locale;
44 |
45 | /**
46 | * A delegate implementing the basic DatePicker
47 | */
48 | public class DatePicker extends FrameLayout {
49 |
50 | private static final String DATE_FORMAT = "MM/dd/yyyy";
51 |
52 | private static final boolean DEFAULT_ENABLED_STATE = true;
53 |
54 | private LinearLayout mPickerContainer;
55 |
56 | private NumberPicker mDaySpinner;
57 |
58 | private NumberPicker mMonthSpinner;
59 |
60 | private NumberPicker mYearSpinner;
61 |
62 | private EditText mDaySpinnerInput;
63 |
64 | private EditText mMonthSpinnerInput;
65 |
66 | private EditText mYearSpinnerInput;
67 |
68 | private Context mContext;
69 |
70 | private OnDateChangedListener mOnDateChangedListener;
71 |
72 | private String[] mShortMonths;
73 |
74 | private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
75 |
76 | private int mNumberOfMonths;
77 |
78 | private Calendar mTempDate;
79 |
80 | private Calendar mMinDate;
81 |
82 | private Calendar mMaxDate;
83 |
84 | private Calendar mCurrentDate;
85 |
86 | private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
87 |
88 | private boolean mIsDayShown = true;
89 |
90 | DatePicker(ViewGroup root, int numberPickerStyle) {
91 | super(root.getContext());
92 | mContext = root.getContext();
93 |
94 | // initialization based on locale
95 | setCurrentLocale(Locale.getDefault());
96 |
97 | LayoutInflater inflater = (LayoutInflater) new ContextThemeWrapper(mContext,
98 | numberPickerStyle).getSystemService(
99 | Context.LAYOUT_INFLATER_SERVICE);
100 | inflater.inflate(R.layout.date_picker_container, this, true);
101 |
102 | mPickerContainer = findViewById(R.id.parent);
103 |
104 | OnValueChangeListener onChangeListener = new OnValueChangeListener() {
105 | public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
106 | updateInputState();
107 | mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
108 | // take care of wrapping of days and months to update greater fields
109 | if (picker == mDaySpinner) {
110 | int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
111 | if (oldVal == maxDayOfMonth && newVal == 1) {
112 | mTempDate.add(Calendar.DAY_OF_MONTH, 1);
113 | } else if (oldVal == 1 && newVal == maxDayOfMonth) {
114 | mTempDate.add(Calendar.DAY_OF_MONTH, -1);
115 | } else {
116 | mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
117 | }
118 | } else if (picker == mMonthSpinner) {
119 | if (oldVal == 11 && newVal == 0) {
120 | mTempDate.add(Calendar.MONTH, 1);
121 | } else if (oldVal == 0 && newVal == 11) {
122 | mTempDate.add(Calendar.MONTH, -1);
123 | } else {
124 | mTempDate.add(Calendar.MONTH, newVal - oldVal);
125 | }
126 | } else if (picker == mYearSpinner) {
127 | mTempDate.set(Calendar.YEAR, newVal);
128 | } else {
129 | throw new IllegalArgumentException();
130 | }
131 | // now set the date to the adjusted one
132 | setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
133 | mTempDate.get(Calendar.DAY_OF_MONTH));
134 | updateSpinners();
135 | notifyDateChanged();
136 | }
137 | };
138 |
139 | // day
140 | mDaySpinner = (NumberPicker) inflater.inflate(R.layout.number_picker_day_month,
141 | mPickerContainer, false);
142 | mDaySpinner.setId(R.id.day);
143 | mDaySpinner.setFormatter(new TwoDigitFormatter());
144 | mDaySpinner.setOnLongPressUpdateInterval(100);
145 | mDaySpinner.setOnValueChangedListener(onChangeListener);
146 | mDaySpinnerInput = NumberPickers.findEditText(mDaySpinner);
147 |
148 |
149 | // month
150 | mMonthSpinner = (NumberPicker) inflater.inflate(R.layout.number_picker_day_month,
151 | mPickerContainer, false);
152 | mMonthSpinner.setId(R.id.month);
153 | mMonthSpinner.setMinValue(0);
154 | mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
155 | mMonthSpinner.setDisplayedValues(mShortMonths);
156 | mMonthSpinner.setOnLongPressUpdateInterval(200);
157 | mMonthSpinner.setOnValueChangedListener(onChangeListener);
158 | mMonthSpinnerInput = NumberPickers.findEditText(mMonthSpinner);
159 |
160 | // year
161 | mYearSpinner = (NumberPicker) inflater.inflate(R.layout.number_picker_year,
162 | mPickerContainer, false);
163 | mYearSpinner.setId(R.id.year);
164 | mYearSpinner.setOnLongPressUpdateInterval(100);
165 | mYearSpinner.setOnValueChangedListener(onChangeListener);
166 | mYearSpinnerInput = NumberPickers.findEditText(mYearSpinner);
167 |
168 | // initialize to current date
169 | mCurrentDate.setTimeInMillis(System.currentTimeMillis());
170 |
171 | // re-order the number spinners to match the current date format
172 | reorderSpinners();
173 |
174 | // If not explicitly specified this view is important for accessibility.
175 | if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
176 | setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
177 | }
178 |
179 | root.addView(this);
180 | }
181 |
182 | void init(int year, int monthOfYear, int dayOfMonth,
183 | boolean isDayShown, OnDateChangedListener onDateChangedListener) {
184 | mIsDayShown = isDayShown;
185 | setDate(year, monthOfYear, dayOfMonth);
186 | updateSpinners();
187 | mOnDateChangedListener = onDateChangedListener;
188 | notifyDateChanged();
189 | }
190 |
191 | void updateDate(int year, int month, int dayOfMonth) {
192 | if (!isNewDate(year, month, dayOfMonth)) {
193 | return;
194 | }
195 | setDate(year, month, dayOfMonth);
196 | updateSpinners();
197 | notifyDateChanged();
198 | }
199 |
200 | int getYear() {
201 | return mCurrentDate.get(Calendar.YEAR);
202 | }
203 |
204 | int getMonth() {
205 | return mCurrentDate.get(Calendar.MONTH);
206 | }
207 |
208 | int getDayOfMonth() {
209 | return mCurrentDate.get(Calendar.DAY_OF_MONTH);
210 | }
211 |
212 | void setMinDate(long minDate) {
213 | mTempDate.setTimeInMillis(minDate);
214 | if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
215 | && mTempDate.get(Calendar.DAY_OF_YEAR) == mMinDate.get(Calendar.DAY_OF_YEAR)) {
216 | // Same day, no-op.
217 | return;
218 | }
219 | mMinDate.setTimeInMillis(minDate);
220 | if (mCurrentDate.before(mMinDate)) {
221 | mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
222 | }
223 | updateSpinners();
224 | }
225 |
226 | void setMaxDate(long maxDate) {
227 | mTempDate.setTimeInMillis(maxDate);
228 | if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
229 | && mTempDate.get(Calendar.DAY_OF_YEAR) == mMaxDate.get(Calendar.DAY_OF_YEAR)) {
230 | // Same day, no-op.
231 | return;
232 | }
233 | mMaxDate.setTimeInMillis(maxDate);
234 | if (mCurrentDate.after(mMaxDate)) {
235 | mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
236 | }
237 | updateSpinners();
238 | }
239 |
240 | @Override
241 | public void setEnabled(boolean enabled) {
242 | mDaySpinner.setEnabled(enabled);
243 | mMonthSpinner.setEnabled(enabled);
244 | mYearSpinner.setEnabled(enabled);
245 | mIsEnabled = enabled;
246 | }
247 |
248 | @Override
249 | public boolean isEnabled() {
250 | return mIsEnabled;
251 | }
252 |
253 | @Override
254 | public void onConfigurationChanged(Configuration newConfig) {
255 | setCurrentLocale(newConfig.locale);
256 | }
257 |
258 | @Override
259 | public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
260 | onPopulateAccessibilityEvent(event);
261 | return true;
262 | }
263 |
264 | /**
265 | * Sets the current locale.
266 | *
267 | * @param locale The current locale.
268 | */
269 | protected void setCurrentLocale(Locale locale) {
270 | mTempDate = getCalendarForLocale(mTempDate, locale);
271 | mMinDate = getCalendarForLocale(mMinDate, locale);
272 | mMaxDate = getCalendarForLocale(mMaxDate, locale);
273 | mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
274 |
275 | mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
276 | mShortMonths = new DateFormatSymbols().getShortMonths();
277 |
278 | if (usingNumericMonths()) {
279 | // We're in a locale where a date should either be all-numeric, or all-text.
280 | // All-text would require custom NumberPicker formatters for day and year.
281 | mShortMonths = new String[mNumberOfMonths];
282 | for (int i = 0; i < mNumberOfMonths; ++i) {
283 | mShortMonths[i] = String.format("%d", i + 1);
284 | }
285 | }
286 | }
287 |
288 | /**
289 | * Tests whether the current locale is one where there are no real month names,
290 | * such as Chinese, Japanese, or Korean locales.
291 | */
292 | private boolean usingNumericMonths() {
293 | return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
294 | }
295 |
296 | /**
297 | * Gets a calendar for locale bootstrapped with the value of a given calendar.
298 | *
299 | * @param oldCalendar The old calendar.
300 | * @param locale The locale.
301 | */
302 | private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
303 | if (oldCalendar == null) {
304 | return Calendar.getInstance(locale);
305 | } else {
306 | final long currentTimeMillis = oldCalendar.getTimeInMillis();
307 | Calendar newCalendar = Calendar.getInstance(locale);
308 | newCalendar.setTimeInMillis(currentTimeMillis);
309 | return newCalendar;
310 | }
311 | }
312 |
313 | /**
314 | * Reorders the spinners according to the date format that is
315 | * explicitly set by the user and if no such is set fall back
316 | * to the current locale's default format.
317 | */
318 | private void reorderSpinners() {
319 | mPickerContainer.removeAllViews();
320 | // We use numeric spinners for year and day, but textual months. Ask icu4c what
321 | // order the user's locale uses for that combination. http://b/7207103.
322 | String pattern = null;
323 | if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
324 | pattern = getOrderJellyBeanMr2();
325 | } else {
326 | pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
327 | }
328 | char[] order = ICU.getDateFormatOrder(pattern);
329 | final int spinnerCount = order.length;
330 | for (int i = 0; i < spinnerCount; i++) {
331 | switch (order[i]) {
332 | case 'd':
333 | mPickerContainer.addView(mDaySpinner);
334 | setImeOptions(mDaySpinner, spinnerCount, i);
335 | break;
336 | case 'M':
337 | mPickerContainer.addView(mMonthSpinner);
338 | setImeOptions(mMonthSpinner, spinnerCount, i);
339 | break;
340 | case 'y':
341 | mPickerContainer.addView(mYearSpinner);
342 | setImeOptions(mYearSpinner, spinnerCount, i);
343 | break;
344 | default:
345 | throw new IllegalArgumentException(Arrays.toString(order));
346 | }
347 | }
348 | }
349 |
350 |
351 | //see http://androidxref.com/4.1.1/xref/packages/apps/Contacts/src/com/android/contacts/datepicker/DatePicker.java
352 | private String getOrderJellyBeanMr2() {
353 | java.text.DateFormat format;
354 | String order;
355 | if (mShortMonths[0].startsWith("1")) {
356 | format = DateFormat.getDateFormat(getContext());
357 | } else {
358 | format = DateFormat.getMediumDateFormat(getContext());
359 | }
360 |
361 | if (format instanceof SimpleDateFormat) {
362 | order = ((SimpleDateFormat) format).toPattern();
363 | } else {
364 | // Shouldn't happen, but just in case.
365 | order = new String(DateFormat.getDateFormatOrder(getContext()));
366 | }
367 | return order;
368 | }
369 |
370 | private boolean isNewDate(int year, int month, int dayOfMonth) {
371 | return (mCurrentDate.get(Calendar.YEAR) != year
372 | || mCurrentDate.get(Calendar.MONTH) != month
373 | || mCurrentDate.get(Calendar.DAY_OF_MONTH) != dayOfMonth);
374 | }
375 |
376 | private void setDate(int year, int month, int dayOfMonth) {
377 | mCurrentDate.set(year, month, dayOfMonth);
378 | if (mCurrentDate.before(mMinDate)) {
379 | mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
380 | } else if (mCurrentDate.after(mMaxDate)) {
381 | mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
382 | }
383 | }
384 |
385 | private void updateSpinners() {
386 | // set the spinner ranges respecting the min and max dates
387 | mDaySpinner.setVisibility(mIsDayShown ? View.VISIBLE : View.GONE);
388 | if (mCurrentDate.equals(mMinDate)) {
389 | mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
390 | mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
391 | mDaySpinner.setWrapSelectorWheel(false);
392 | mMonthSpinner.setDisplayedValues(null);
393 | mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
394 | mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
395 | mMonthSpinner.setWrapSelectorWheel(false);
396 | } else if (mCurrentDate.equals(mMaxDate)) {
397 | mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
398 | mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
399 | mDaySpinner.setWrapSelectorWheel(false);
400 | mMonthSpinner.setDisplayedValues(null);
401 | mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
402 | mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
403 | mMonthSpinner.setWrapSelectorWheel(false);
404 | } else {
405 | mDaySpinner.setMinValue(1);
406 | mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
407 | mDaySpinner.setWrapSelectorWheel(true);
408 | mMonthSpinner.setDisplayedValues(null);
409 | mMonthSpinner.setMinValue(0);
410 | mMonthSpinner.setMaxValue(11);
411 | mMonthSpinner.setWrapSelectorWheel(true);
412 | }
413 |
414 | // make sure the month names are a zero based array
415 | // with the months in the month spinner
416 | String[] displayedValues = Arrays.copyOfRange(mShortMonths,
417 | mMonthSpinner.getMinValue(),
418 | mMonthSpinner.getMaxValue() + 1);
419 | mMonthSpinner.setDisplayedValues(displayedValues);
420 |
421 | // year spinner range does not change based on the current date
422 | mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
423 | mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
424 | mYearSpinner.setWrapSelectorWheel(false);
425 |
426 | // set the spinner values
427 | mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
428 | mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
429 | mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
430 |
431 | if (usingNumericMonths()) {
432 | mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
433 | }
434 | }
435 |
436 |
437 | /**
438 | * Notifies the listener, if such, for a change in the selected date.
439 | */
440 | private void notifyDateChanged() {
441 | sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
442 | if (mOnDateChangedListener != null) {
443 | mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(),
444 | getDayOfMonth());
445 | }
446 | }
447 |
448 | /**
449 | * Sets the IME options for a spinner based on its ordering.
450 | *
451 | * @param spinner The spinner.
452 | * @param spinnerCount The total spinner count.
453 | * @param spinnerIndex The index of the given spinner.
454 | */
455 | private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
456 | final int imeOptions;
457 | if (spinnerIndex < spinnerCount - 1) {
458 | imeOptions = EditorInfo.IME_ACTION_NEXT;
459 | } else {
460 | imeOptions = EditorInfo.IME_ACTION_DONE;
461 | }
462 | TextView input = NumberPickers.findEditText(spinner);
463 | input.setImeOptions(imeOptions);
464 | }
465 |
466 | private void updateInputState() {
467 | // Make sure that if the user changes the value and the IME is active
468 | // for one of the inputs if this widget, the IME is closed. If the user
469 | // changed the value via the IME and there is a next input the IME will
470 | // be shown, otherwise the user chose another means of changing the
471 | // value and having the IME up makes no sense.
472 | InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(
473 | Context.INPUT_METHOD_SERVICE);
474 | if (inputMethodManager != null) {
475 | if (inputMethodManager.isActive(mYearSpinnerInput)) {
476 | mYearSpinnerInput.clearFocus();
477 | inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
478 | } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
479 | mMonthSpinnerInput.clearFocus();
480 | inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
481 | } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
482 | mDaySpinnerInput.clearFocus();
483 | inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
484 | }
485 | }
486 | }
487 |
488 | @Override
489 | protected Parcelable onSaveInstanceState() {
490 | Parcelable superState = super.onSaveInstanceState();
491 |
492 | return new SavedState(superState, mCurrentDate, mMinDate, mMaxDate, mIsDayShown);
493 | }
494 |
495 | @Override
496 | protected void onRestoreInstanceState(Parcelable state) {
497 | SavedState ss = (SavedState) state;
498 | super.onRestoreInstanceState(ss.getSuperState());
499 | mCurrentDate = Calendar.getInstance();
500 | mCurrentDate.setTimeInMillis(ss.currentDate);
501 | mMinDate = Calendar.getInstance();
502 | mMinDate.setTimeInMillis(ss.minDate);
503 | mMaxDate = Calendar.getInstance();
504 | mMaxDate.setTimeInMillis(ss.maxDate);
505 | updateSpinners();
506 | }
507 |
508 | private static class SavedState extends BaseSavedState {
509 |
510 | @SuppressWarnings("unused") public static final Parcelable.Creator CREATOR = new Creator() {
511 |
512 | @Override
513 | public SavedState createFromParcel(Parcel in) {
514 | return new SavedState(in);
515 | }
516 |
517 | @Override
518 | public SavedState[] newArray(int size) {
519 | return new SavedState[size];
520 | }
521 | };
522 | final long currentDate;
523 | final long minDate;
524 | final long maxDate;
525 | final boolean isDaySpinnerShown;
526 |
527 | /**
528 | * Constructor called from {@link DatePicker#onSaveInstanceState()}
529 | */
530 | SavedState(Parcelable superState,
531 | Calendar currentDate,
532 | Calendar minDate,
533 | Calendar maxDate,
534 | boolean isDaySpinnerShown) {
535 | super(superState);
536 | this.currentDate = currentDate.getTimeInMillis();
537 | this.minDate = minDate.getTimeInMillis();
538 | this.maxDate = maxDate.getTimeInMillis();
539 | this.isDaySpinnerShown = isDaySpinnerShown;
540 | }
541 |
542 | /**
543 | * Constructor called from {@link #CREATOR}
544 | */
545 | private SavedState(Parcel in) {
546 | super(in);
547 | this.currentDate = in.readLong();
548 | this.minDate = in.readLong();
549 | this.maxDate = in.readLong();
550 | this.isDaySpinnerShown = in.readByte() != 0;
551 | }
552 |
553 | @Override
554 | public void writeToParcel(Parcel dest, int flags) {
555 | super.writeToParcel(dest, flags);
556 | dest.writeLong(currentDate);
557 | dest.writeLong(minDate);
558 | dest.writeLong(maxDate);
559 | dest.writeByte(isDaySpinnerShown ? (byte) 1 : (byte) 0);
560 | }
561 | }
562 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/DatePickerDialog.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;
2 |
3 | import android.content.Context;
4 | import android.content.DialogInterface;
5 | import android.content.DialogInterface.OnClickListener;
6 | import android.os.Bundle;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import androidx.appcompat.app.AlertDialog;
11 | import java.text.DateFormat;
12 | import java.util.Calendar;
13 |
14 | /**
15 | * A fork of the Android Open Source Project DatePickerDialog class
16 | */
17 | public class DatePickerDialog extends AlertDialog implements OnClickListener,
18 | OnDateChangedListener {
19 |
20 | private static final String YEAR = "year";
21 | private static final String MONTH = "month";
22 | private static final String DAY = "day";
23 | private static final String TITLE_SHOWN = "title_enabled";
24 | private static final String CUSTOM_TITLE = "custom_title";
25 |
26 | private final DatePicker mDatePicker;
27 | private final OnDateSetListener mCallBack;
28 | private final OnDateCancelListener mOnCancel;
29 | private final DateFormat mTitleDateFormat;
30 |
31 | private boolean mIsDayShown = true;
32 | private boolean mIsTitleShown = true;
33 | private String mCustomTitle = "";
34 |
35 | /**
36 | * The callback used to indicate the user is done filling in the date.
37 | */
38 | public interface OnDateSetListener {
39 | /**
40 | * @param view The view associated with this listener.
41 | * @param year The year that was set
42 | * @param monthOfYear The month that was set (0-11) for compatibility
43 | * with {@link java.util.Calendar}.
44 | * @param dayOfMonth The day of the month that was set.
45 | */
46 | void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth);
47 | }
48 |
49 | /**
50 | * Callback for when things are cancelled
51 | */
52 | public interface OnDateCancelListener {
53 | /**
54 | * Called when cancel happens.
55 | *
56 | * @param view The view associated with this listener.
57 | */
58 | void onCancelled(DatePicker view);
59 | }
60 |
61 | DatePickerDialog(Context context,
62 | int theme,
63 | int spinnerTheme,
64 | OnDateSetListener callBack,
65 | OnDateCancelListener onCancel,
66 | Calendar defaultDate,
67 | Calendar minDate,
68 | Calendar maxDate,
69 | boolean isDayShown,
70 | boolean isTitleShown,
71 | String customTitle) {
72 | super(context, theme);
73 |
74 | mCallBack = callBack;
75 | mOnCancel = onCancel;
76 | mTitleDateFormat = DateFormat.getDateInstance(DateFormat.LONG);
77 | mIsDayShown = isDayShown;
78 | mIsTitleShown = isTitleShown;
79 | mCustomTitle = customTitle;
80 |
81 | updateTitle(defaultDate);
82 |
83 | setButton(BUTTON_POSITIVE, context.getText(android.R.string.ok),
84 | this);
85 | setButton(BUTTON_NEGATIVE, context.getText(android.R.string.cancel),
86 | this);
87 |
88 | LayoutInflater inflater =
89 | (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
90 | View view = inflater.inflate(R.layout.date_picker_dialog_container, null);
91 | setView(view);
92 | mDatePicker = new DatePicker((ViewGroup) view, spinnerTheme);
93 | mDatePicker.setMinDate(minDate.getTimeInMillis());
94 | mDatePicker.setMaxDate(maxDate.getTimeInMillis());
95 | mDatePicker.init(defaultDate.get(Calendar.YEAR), defaultDate.get(Calendar.MONTH), defaultDate.get(Calendar.DAY_OF_MONTH), isDayShown, this);
96 |
97 | }
98 |
99 | @Override
100 | public void onClick(DialogInterface dialog, int which) {
101 | switch (which) {
102 | case BUTTON_POSITIVE: {
103 | if (mCallBack != null) {
104 | mDatePicker.clearFocus();
105 | mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
106 | mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
107 | }
108 | break;
109 | }
110 | case BUTTON_NEGATIVE: {
111 | if (mOnCancel != null) {
112 | mDatePicker.clearFocus();
113 | mOnCancel.onCancelled(mDatePicker);
114 | }
115 | break;
116 | }
117 | }
118 | }
119 |
120 | @Override
121 | public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
122 | Calendar updatedDate = Calendar.getInstance();
123 | updatedDate.set(Calendar.YEAR, year);
124 | updatedDate.set(Calendar.MONTH, monthOfYear);
125 | updatedDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
126 | updateTitle(updatedDate);
127 | }
128 |
129 | private void updateTitle(Calendar updatedDate) {
130 | if (mIsTitleShown && mCustomTitle != null && !mCustomTitle.isEmpty()) {
131 | setTitle(mCustomTitle);
132 | } else if (mIsTitleShown) {
133 | final DateFormat dateFormat = mTitleDateFormat;
134 | setTitle(dateFormat.format(updatedDate.getTime()));
135 | } else {
136 | setTitle(" ");
137 | }
138 | }
139 |
140 | @Override
141 | public Bundle onSaveInstanceState() {
142 | Bundle state = super.onSaveInstanceState();
143 | state.putInt(YEAR, mDatePicker.getYear());
144 | state.putInt(MONTH, mDatePicker.getMonth());
145 | state.putInt(DAY, mDatePicker.getDayOfMonth());
146 | state.putBoolean(TITLE_SHOWN, mIsTitleShown);
147 | state.putString(CUSTOM_TITLE, mCustomTitle);
148 | return state;
149 | }
150 |
151 | @Override
152 | public void onRestoreInstanceState(Bundle savedInstanceState) {
153 | super.onRestoreInstanceState(savedInstanceState);
154 | int year = savedInstanceState.getInt(YEAR);
155 | int month = savedInstanceState.getInt(MONTH);
156 | int day = savedInstanceState.getInt(DAY);
157 | mIsTitleShown = savedInstanceState.getBoolean(TITLE_SHOWN);
158 | mCustomTitle = savedInstanceState.getString(CUSTOM_TITLE);
159 | Calendar c = Calendar.getInstance();
160 | c.set(Calendar.YEAR, year);
161 | c.set(Calendar.MONTH, month);
162 | c.set(Calendar.DAY_OF_MONTH, day);
163 | updateTitle(c);
164 | mDatePicker.init(year, month, day, mIsDayShown, this);
165 | }
166 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/DateUtils.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;/*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import android.content.Context;
17 | import android.text.format.DateFormat;
18 | import android.text.format.Time;
19 |
20 | import java.text.ParsePosition;
21 | import java.text.SimpleDateFormat;
22 | import java.util.Calendar;
23 | import java.util.Date;
24 | import java.util.GregorianCalendar;
25 | import java.util.Locale;
26 | import java.util.TimeZone;
27 | /**
28 | * Utility methods for processing dates.
29 | */
30 | public class DateUtils {
31 | public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
32 | /**
33 | * When parsing a date without a year, the system assumes 1970, which wasn't a leap-year.
34 | * Let's add a one-off hack for that day of the year
35 | */
36 | public static final String NO_YEAR_DATE_FEB29TH = "--02-29";
37 | // Variations of ISO 8601 date format. Do not change the order - it does affect the
38 | // result in ambiguous cases.
39 | private static final SimpleDateFormat[] DATE_FORMATS = {
40 | CommonDateUtils.FULL_DATE_FORMAT,
41 | CommonDateUtils.DATE_AND_TIME_FORMAT,
42 | new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US),
43 | new SimpleDateFormat("yyyyMMdd", Locale.US),
44 | new SimpleDateFormat("yyyyMMdd'T'HHmmssSSS'Z'", Locale.US),
45 | new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US),
46 | new SimpleDateFormat("yyyyMMdd'T'HHmm'Z'", Locale.US),
47 | };
48 | static {
49 | for (SimpleDateFormat format : DATE_FORMATS) {
50 | format.setLenient(true);
51 | format.setTimeZone(UTC_TIMEZONE);
52 | }
53 | CommonDateUtils.NO_YEAR_DATE_FORMAT.setTimeZone(UTC_TIMEZONE);
54 | }
55 | /**
56 | * Parses the supplied string to see if it looks like a date.
57 | *
58 | * @param string The string representation of the provided date
59 | * @param mustContainYear If true, the string is parsed as a date containing a year. If false,
60 | * the string is parsed into a valid date even if the year field is missing.
61 | * @return A Calendar object corresponding to the date if the string is successfully parsed.
62 | * If not, null is returned.
63 | */
64 | public static Calendar parseDate(String string, boolean mustContainYear) {
65 | ParsePosition parsePosition = new ParsePosition(0);
66 | Date date;
67 | if (!mustContainYear) {
68 | final boolean noYearParsed;
69 | // Unfortunately, we can't parse Feb 29th correctly, so let's handle this day seperately
70 | if (NO_YEAR_DATE_FEB29TH.equals(string)) {
71 | return getUtcDate(0, Calendar.FEBRUARY, 29);
72 | } else {
73 | synchronized (CommonDateUtils.NO_YEAR_DATE_FORMAT) {
74 | date = CommonDateUtils.NO_YEAR_DATE_FORMAT.parse(string, parsePosition);
75 | }
76 | noYearParsed = parsePosition.getIndex() == string.length();
77 | }
78 | if (noYearParsed) {
79 | return getUtcDate(date, true);
80 | }
81 | }
82 | for (int i = 0; i < DATE_FORMATS.length; i++) {
83 | SimpleDateFormat f = DATE_FORMATS[i];
84 | synchronized (f) {
85 | parsePosition.setIndex(0);
86 | date = f.parse(string, parsePosition);
87 | if (parsePosition.getIndex() == string.length()) {
88 | return getUtcDate(date, false);
89 | }
90 | }
91 | }
92 | return null;
93 | }
94 | private static final Calendar getUtcDate(Date date, boolean noYear) {
95 | final Calendar calendar = Calendar.getInstance(UTC_TIMEZONE, Locale.US);
96 | calendar.setTime(date);
97 | if (noYear) {
98 | calendar.set(Calendar.YEAR, 0);
99 | }
100 | return calendar;
101 | }
102 | private static final Calendar getUtcDate(int year, int month, int dayOfMonth) {
103 | final Calendar calendar = Calendar.getInstance(UTC_TIMEZONE, Locale.US);
104 | calendar.clear();
105 | calendar.set(Calendar.YEAR, year);
106 | calendar.set(Calendar.MONTH, month);
107 | calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
108 | return calendar;
109 | }
110 | public static boolean isYearSet(Calendar cal) {
111 | // use the Calendar.YEAR field to track whether or not the year is set instead of
112 | // Calendar.isSet() because doing Calendar.get() causes Calendar.isSet() to become
113 | // true irregardless of what the previous value was
114 | return cal.get(Calendar.YEAR) > 1;
115 | }
116 | /**
117 | * Same as {@link #formatDate(Context context, String string, boolean longForm)}, with
118 | * longForm set to {@code true} by default.
119 | *
120 | * @param context Valid context
121 | * @param string String representation of a date to parse
122 | * @return Returns the same date in a cleaned up format. If the supplied string does not look
123 | * like a date, return it unchanged.
124 | */
125 | public static String formatDate(Context context, String string) {
126 | return formatDate(context, string, true);
127 | }
128 | /**
129 | * Parses the supplied string to see if it looks like a date.
130 | *
131 | * @param context Valid context
132 | * @param string String representation of a date to parse
133 | * @param longForm If true, return the date formatted into its long string representation.
134 | * If false, return the date formatted using its short form representation (i.e. 12/11/2012)
135 | * @return Returns the same date in a cleaned up format. If the supplied string does not look
136 | * like a date, return it unchanged.
137 | */
138 | public static String formatDate(Context context, String string, boolean longForm) {
139 | if (string == null) {
140 | return null;
141 | }
142 | string = string.trim();
143 | if (string.length() == 0) {
144 | return string;
145 | }
146 | final Calendar cal = parseDate(string, false);
147 | // we weren't able to parse the string successfully so just return it unchanged
148 | if (cal == null) {
149 | return string;
150 | }
151 | final boolean isYearSet = isYearSet(cal);
152 | final java.text.DateFormat outFormat;
153 | if (!isYearSet) {
154 | outFormat = getLocalizedDateFormatWithoutYear(context);
155 | } else {
156 | outFormat =
157 | longForm ? DateFormat.getLongDateFormat(context) :
158 | DateFormat.getDateFormat(context);
159 | }
160 | synchronized (outFormat) {
161 | outFormat.setTimeZone(UTC_TIMEZONE);
162 | return outFormat.format(cal.getTime());
163 | }
164 | }
165 | public static boolean isMonthBeforeDay(Context context) {
166 | char[] dateFormatOrder = DateFormat.getDateFormatOrder(context);
167 | for (int i = 0; i < dateFormatOrder.length; i++) {
168 | if (dateFormatOrder[i] == 'd') {
169 | return false;
170 | }
171 | if (dateFormatOrder[i] == 'M') {
172 | return true;
173 | }
174 | }
175 | return false;
176 | }
177 | /**
178 | * Returns a SimpleDateFormat object without the year fields by using a regular expression
179 | * to eliminate the year in the string pattern. In the rare occurence that the resulting
180 | * pattern cannot be reconverted into a SimpleDateFormat, it uses the provided context to
181 | * determine whether the month field should be displayed before the day field, and returns
182 | * either "MMMM dd" or "dd MMMM" converted into a SimpleDateFormat.
183 | */
184 | public static java.text.DateFormat getLocalizedDateFormatWithoutYear(Context context) {
185 | final String pattern = ((SimpleDateFormat) SimpleDateFormat.getDateInstance(
186 | java.text.DateFormat.LONG)).toPattern();
187 | // Determine the correct regex pattern for year.
188 | // Special case handling for Spanish locale by checking for "de"
189 | final String yearPattern = pattern.contains(
190 | "de") ? "[^Mm]*[Yy]+[^Mm]*" : "[^DdMm]*[Yy]+[^DdMm]*";
191 | try {
192 | // Eliminate the substring in pattern that matches the format for that of year
193 | return new SimpleDateFormat(pattern.replaceAll(yearPattern, ""));
194 | } catch (IllegalArgumentException e) {
195 | return new SimpleDateFormat(
196 | DateUtils.isMonthBeforeDay(context) ? "MMMM dd" : "dd MMMM");
197 | }
198 | }
199 | /**
200 | * Given a calendar (possibly containing only a day of the year), returns the earliest possible
201 | * anniversary of the date that is equal to or after the current point in time if the date
202 | * does not contain a year, or the date converted to the local time zone (if the date contains
203 | * a year.
204 | *
205 | * @param target The date we wish to convert(in the UTC time zone).
206 | * @return If date does not contain a year (year < 1900), returns the next earliest anniversary
207 | * that is after the current point in time (in the local time zone). Otherwise, returns the
208 | * adjusted Date in the local time zone.
209 | */
210 | public static Date getNextAnnualDate(Calendar target) {
211 | final Calendar today = Calendar.getInstance();
212 | today.setTime(new Date());
213 | // Round the current time to the exact start of today so that when we compare
214 | // today against the target date, both dates are set to exactly 0000H.
215 | today.set(Calendar.HOUR_OF_DAY, 0);
216 | today.set(Calendar.MINUTE, 0);
217 | today.set(Calendar.SECOND, 0);
218 | today.set(Calendar.MILLISECOND, 0);
219 | final boolean isYearSet = isYearSet(target);
220 | final int targetYear = target.get(Calendar.YEAR);
221 | final int targetMonth = target.get(Calendar.MONTH);
222 | final int targetDay = target.get(Calendar.DAY_OF_MONTH);
223 | final boolean isFeb29 = (targetMonth == Calendar.FEBRUARY && targetDay == 29);
224 | final GregorianCalendar anniversary = new GregorianCalendar();
225 | // Convert from the UTC date to the local date. Set the year to today's year if the
226 | // there is no provided year (targetYear < 1900)
227 | anniversary.set(!isYearSet ? today.get(Calendar.YEAR) : targetYear,
228 | targetMonth, targetDay);
229 | // If the anniversary's date is before the start of today and there is no year set,
230 | // increment the year by 1 so that the returned date is always equal to or greater than
231 | // today. If the day is a leap year, keep going until we get the next leap year anniversary
232 | // Otherwise if there is already a year set, simply return the exact date.
233 | if (!isYearSet) {
234 | int anniversaryYear = today.get(Calendar.YEAR);
235 | if (anniversary.before(today) ||
236 | (isFeb29 && !anniversary.isLeapYear(anniversaryYear))) {
237 | // If the target date is not Feb 29, then set the anniversary to the next year.
238 | // Otherwise, keep going until we find the next leap year (this is not guaranteed
239 | // to be in 4 years time).
240 | do {
241 | anniversaryYear +=1;
242 | } while (isFeb29 && !anniversary.isLeapYear(anniversaryYear));
243 | anniversary.set(anniversaryYear, targetMonth, targetDay);
244 | }
245 | }
246 | return anniversary.getTime();
247 | }
248 | /**
249 | * Determine the difference, in days between two dates. Uses similar logic as the
250 | * {android.text.format.DateUtils.getRelativeTimeSpanString} method.
251 | *
252 | * @param time Instance of time object to use for calculations.
253 | * @param date1 First date to check.
254 | * @param date2 Second date to check.
255 | * @return The absolute difference in days between the two dates.
256 | */
257 | public static int getDayDifference(Time time, long date1, long date2) {
258 | time.set(date1);
259 | int startDay = Time.getJulianDay(date1, time.gmtoff);
260 | time.set(date2);
261 | int currentDay = Time.getJulianDay(date2, time.gmtoff);
262 | return Math.abs(currentDay - startDay);
263 | }
264 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/ICU.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;
2 |
3 | public class ICU {
4 |
5 | /**
6 | * This method is directly copied from {libcore.icu.ICU}. The method is simple enough
7 | * that it probably won't change.
8 | */
9 | public static char[] getDateFormatOrder(String pattern) {
10 | char[] result = new char[3];
11 | int resultIndex = 0;
12 | boolean sawDay = false;
13 | boolean sawMonth = false;
14 | boolean sawYear = false;
15 |
16 | for (int i = 0; i < pattern.length(); ++i) {
17 | char ch = pattern.charAt(i);
18 | if (ch == 'd' || ch == 'L' || ch == 'M' || ch == 'y') {
19 | if (ch == 'd' && !sawDay) {
20 | result[resultIndex++] = 'd';
21 | sawDay = true;
22 | } else if ((ch == 'L' || ch == 'M') && !sawMonth) {
23 | result[resultIndex++] = 'M';
24 | sawMonth = true;
25 | } else if ((ch == 'y') && !sawYear) {
26 | result[resultIndex++] = 'y';
27 | sawYear = true;
28 | }
29 | } else if (ch == 'G') {
30 | // Ignore the era specifier, if present.
31 | } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
32 | throw new IllegalArgumentException("Bad pattern character '"
33 | + ch + "' in " + pattern);
34 | } else if (ch == '\'') {
35 | if (i < pattern.length() - 1 && pattern.charAt(i + 1) == '\'') {
36 | ++i;
37 | } else {
38 | i = pattern.indexOf('\'', i + 1);
39 | if (i == -1) {
40 | throw new IllegalArgumentException("Bad quoting in " + pattern);
41 | }
42 | ++i;
43 | }
44 | } else {
45 | // Ignore spaces and punctuation.
46 | }
47 | }
48 | return result;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/NumberPickers.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;
2 |
3 | import android.widget.EditText;
4 | import android.widget.NumberPicker;
5 |
6 | /**
7 | * Created by David on 26/11/2017.
8 | */
9 |
10 | public class NumberPickers {
11 |
12 | //inefficient way of obtaining EditText from inside NumberPicker - not too bad here as View
13 | //hierarchy is very small -
14 | public static EditText findEditText(NumberPicker np) {
15 | for (int i = 0; i < np.getChildCount(); i++) {
16 | if (np.getChildAt(i) instanceof EditText) {
17 | return (EditText) np.getChildAt(i);
18 | }
19 | }
20 | return null;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/OnDateChangedListener.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;
2 |
3 | /**
4 | * Created by David on 25/11/2017.
5 | */
6 | public interface OnDateChangedListener {
7 | /**
8 | * Called upon a date change.
9 | *
10 | * @param view The view associated with this listener.
11 | * @param year The year that was set.
12 | * @param monthOfYear The month that was set (0-11) for compatibility
13 | * with {@link java.util.Calendar}.
14 | * @param dayOfMonth The day of the month that was set.
15 | */
16 | void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
17 | }
18 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/SpinnerDatePickerDialogBuilder.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.Calendar;
6 | import java.util.GregorianCalendar;
7 |
8 | public class SpinnerDatePickerDialogBuilder {
9 |
10 | private Context context;
11 | private DatePickerDialog.OnDateSetListener callBack;
12 | private DatePickerDialog.OnDateCancelListener onCancel;
13 | private boolean isDayShown = true;
14 | private boolean isTitleShown = true;
15 | private String customTitle = "";
16 | private int theme = 0; //default theme
17 | private int spinnerTheme = 0; //default theme
18 | private Calendar defaultDate = new GregorianCalendar(1980, 0, 1);
19 | private Calendar minDate = new GregorianCalendar(1900, 0, 1);
20 | private Calendar maxDate = new GregorianCalendar(2100, 0, 1);
21 |
22 |
23 | public SpinnerDatePickerDialogBuilder context(Context context) {
24 | this.context = context;
25 | return this;
26 | }
27 |
28 | public SpinnerDatePickerDialogBuilder callback(DatePickerDialog.OnDateSetListener callBack) {
29 | this.callBack = callBack;
30 | return this;
31 | }
32 |
33 | public SpinnerDatePickerDialogBuilder onCancel(DatePickerDialog.OnDateCancelListener onCancel) {
34 | this.onCancel = onCancel;
35 | return this;
36 | }
37 |
38 | public SpinnerDatePickerDialogBuilder dialogTheme(int theme) {
39 | this.theme = theme;
40 | return this;
41 | }
42 |
43 | public SpinnerDatePickerDialogBuilder spinnerTheme(int spinnerTheme) {
44 | this.spinnerTheme = spinnerTheme;
45 | return this;
46 | }
47 |
48 | public SpinnerDatePickerDialogBuilder defaultDate(int year, int monthIndexedFromZero, int day) {
49 | this.defaultDate = new GregorianCalendar(year, monthIndexedFromZero, day);
50 | return this;
51 | }
52 |
53 | public SpinnerDatePickerDialogBuilder minDate(int year, int monthIndexedFromZero, int day) {
54 | this.minDate = new GregorianCalendar(year, monthIndexedFromZero, day);
55 | return this;
56 | }
57 |
58 | public SpinnerDatePickerDialogBuilder maxDate(int year, int monthIndexedFromZero, int day) {
59 | this.maxDate = new GregorianCalendar(year, monthIndexedFromZero, day);
60 | return this;
61 | }
62 |
63 | public SpinnerDatePickerDialogBuilder showDaySpinner(boolean showDaySpinner) {
64 | this.isDayShown = showDaySpinner;
65 | return this;
66 | }
67 |
68 | public SpinnerDatePickerDialogBuilder showTitle(boolean showTitle) {
69 | this.isTitleShown = showTitle;
70 | return this;
71 | }
72 |
73 | public SpinnerDatePickerDialogBuilder customTitle(String title) {
74 | this.customTitle = title;
75 | return this;
76 | }
77 |
78 | public DatePickerDialog build() {
79 | if (context == null) throw new IllegalArgumentException("Context must not be null");
80 | if (maxDate.getTime().getTime() <= minDate.getTime().getTime()) throw new IllegalArgumentException("Max date is not after Min date");
81 |
82 | return new DatePickerDialog(context, theme, spinnerTheme, callBack, onCancel, defaultDate, minDate, maxDate, isDayShown, isTitleShown, customTitle);
83 | }
84 | }
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/java/com/tsongkha/spinnerdatepicker/TwoDigitFormatter.java:
--------------------------------------------------------------------------------
1 | package com.tsongkha.spinnerdatepicker;
2 |
3 | import android.widget.NumberPicker;
4 |
5 | import java.text.DecimalFormatSymbols;
6 | import java.util.Locale;
7 |
8 | /**
9 | * Copy of {android.widget.NumberPicker.TwoDigitFormatter}, modified
10 | * so that it doesn't use libcore.
11 | *
12 | * Use a custom NumberPicker formatting callback to use two-digit minutes
13 | * strings like "01". Keeping a static formatter etc. is the most efficient
14 | * way to do this; it avoids creating temporary objects on every call to
15 | * format().
16 | */
17 | public class TwoDigitFormatter implements NumberPicker.Formatter {
18 | final StringBuilder mBuilder = new StringBuilder();
19 |
20 | char mZeroDigit;
21 | java.util.Formatter mFmt;
22 |
23 | final Object[] mArgs = new Object[1];
24 |
25 | public TwoDigitFormatter() {
26 | final Locale locale = Locale.getDefault();
27 | init(locale);
28 | }
29 |
30 | private void init(Locale locale) {
31 | mFmt = createFormatter(locale);
32 | mZeroDigit = getZeroDigit(locale);
33 | }
34 |
35 | public String format(int value) {
36 | final Locale currentLocale = Locale.getDefault();
37 | if (mZeroDigit != getZeroDigit(currentLocale)) {
38 | init(currentLocale);
39 | }
40 | mArgs[0] = value;
41 | mBuilder.delete(0, mBuilder.length());
42 | mFmt.format("%02d", mArgs);
43 | return mFmt.toString();
44 | }
45 |
46 | private static char getZeroDigit(Locale locale) {
47 | // The original TwoDigitFormatter directly referenced LocaleData's value. Instead,
48 | // we need to use the public DecimalFormatSymbols API.
49 | return DecimalFormatSymbols.getInstance(locale).getZeroDigit();
50 | }
51 |
52 | private java.util.Formatter createFormatter(Locale locale) {
53 | return new java.util.Formatter(mBuilder, locale);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/res/layout/date_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
24 |
25 |
31 |
32 |
33 |
44 |
45 |
46 |
57 |
58 |
59 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/res/layout/date_picker_container.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/res/layout/date_picker_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
26 |
27 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/res/layout/date_picker_dialog_container.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/res/layout/number_picker_day_month.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/res/layout/number_picker_year.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SpinnerDatePickerLib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SpinnerDatePicker
3 |
4 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.3.0'
9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | jcenter()
17 | }
18 | }
19 |
20 | task clean(type: Delete) {
21 | delete rootProject.buildDir
22 | }
23 |
--------------------------------------------------------------------------------
/github/material_design_datepicker_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/github/material_design_datepicker_example.png
--------------------------------------------------------------------------------
/github/spinner_datepicker_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/github/spinner_datepicker_example.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drawers/SpinnerDatePicker/2f5a179f035a6f6cefb10fcbf1f79f1b35684ce2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Feb 05 16:56:21 NZDT 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':SpinnerDatePickerLib', ':SpinnerDatePickerExample'
2 | rootProject.name = 'SpinnerDatePicker'
3 |
--------------------------------------------------------------------------------