├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── resources
├── Date.png
└── Time.png
├── sample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── layernet
│ │ └── datetimepickerexample
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── layernet
│ │ └── datetimepickerexample
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── settings.gradle
├── supported_languages.txt
└── thai-datetimepicker
├── build.gradle
├── gradle.properties
└── src
└── main
├── AndroidManifest.xml
└── java
└── com
└── layernet
└── thaidatetimepicker
├── AccessibleLinearLayout.java
├── AccessibleTextView.java
├── HapticFeedbackController.java
├── TypefaceHelper.java
├── Utils.java
├── date
├── AccessibleDateAnimator.java
├── DatePickerController.java
├── DatePickerDialog.java
├── DayPickerView.java
├── MonthAdapter.java
├── MonthView.java
├── SimpleDayPickerView.java
├── SimpleMonthAdapter.java
├── SimpleMonthView.java
├── TextViewWithCircularIndicator.java
└── YearPickerView.java
└── time
├── AmPmCirclesView.java
├── CircleView.java
├── RadialPickerLayout.java
├── RadialSelectorView.java
├── RadialTextsView.java
├── TimePickerController.java
├── TimePickerDialog.java
└── Timepoint.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # IntelliJ IDEA
19 | .idea/
20 | *.iml
21 | *.iws
22 | *.ipr
23 |
24 | # Gradle
25 | .gradle
26 | build/
27 |
28 | # System files
29 | .DS_Store
30 | .directory
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [2014] Wouter Dullaert
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Material DateTime Picker - Select a time/date in style [Forked for Thai Buddhist calendar Edition]
2 |
3 | [  ](https://bintray.com/layerlre/maven/ThaiDateTimePicker/_latestVersion)
4 |
5 | Material DateTime Picker tries to offer you the date and time pickers as shown in [the Material Design spec](http://www.google.com/design/spec/components/pickers.html), with an
6 | easy themable API.
7 | The library uses [the code from the Android frameworks](https://android.googlesource.com/platform/frameworks/opt/datetimepicker/) as a base and tweaked it to be as close as possible to Material Design example.
8 |
9 | Support for Android 4.0 and up.
10 |
11 | Feel free to fork or issue pull requests on github. Issues can be reported on the github issue tracker.
12 |
13 | Date Picker | Time Picker
14 | ---- | ----
15 |  | 
16 |
17 |
18 | ## Table of Contents
19 | 1. [Setup](#setup)
20 | 2. [Using Material Date/Time Pickers](#using-material-datetime-pickers)
21 | 1. [Implement Listeners](#implement-an-ontimesetlistenerondatesetlistener)
22 | 2. [Create Pickers](#create-a-timepickerdialogdatepickerdialog-using-the-supplied-factory)
23 | 3. [Theme the Pickers](#theme-the-pickers)
24 | 3. [Additional Options](#additional-options)
25 | 4. [FAQ](#faq)
26 | 5. [Potential Improvements](#potential-improvements)
27 | 6. [License](#license)
28 |
29 |
30 | ## Setup
31 | The easiest way to add the Material DateTime Picker (Thai Edition) library to your project is by adding it as a dependency to your `build.gradle`
32 | ```java
33 | dependencies {
34 | compile 'com.layer-net:thai-datetimepicker:1.0.3'
35 | }
36 | ```
37 |
38 | You may also add the library as an Android Library to your project. All the library files live in ```library```.
39 |
40 |
41 | ## Using Material Date/Time Pickers
42 | The library follows the same API as other pickers in the Android framework.
43 | For a basic implementation, you'll need to
44 |
45 | 1. Implement an `OnTimeSetListener`/`OnDateSetListener`
46 | 2. Create a `TimePickerDialog`/`DatePickerDialog` using the supplied factory
47 | 3. Theme the pickers
48 |
49 | ### Implement an `OnTimeSetListener`/`OnDateSetListener`
50 | In order to receive the date or time set in the picker, you will need to implement the `OnTimeSetListener` or
51 | `OnDateSetListener` interfaces. Typically this will be the `Activity` or `Fragment` that creates the Pickers. The callbacks use the same API as the standard Android pickers.
52 | ```java
53 | @Override
54 | public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute, int second) {
55 | String time = "You picked the following time: "+hourOfDay+"h"+minute+"m"+second;
56 | timeTextView.setText(time);
57 | }
58 |
59 | @Override
60 | public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
61 | String date = "You picked the following date: "+dayOfMonth+"/"+(monthOfYear+1)+"/"+year;
62 | dateTextView.setText(date);
63 | }
64 | ```
65 |
66 | ### Create a `TimePickerDialog`/`DatePickerDialog` using the supplied factory
67 | You will need to create a new instance of `TimePickerDialog` or `DatePickerDialog` using the static `newInstance()` method, supplying proper default values and a callback. Once the dialogs are configured, you can call `show()`.
68 | ```java
69 | Calendar now = Calendar.getInstance();
70 | DatePickerDialog dpd = DatePickerDialog.newInstance(
71 | MainActivity.this,
72 | now.get(Calendar.YEAR),
73 | now.get(Calendar.MONTH),
74 | now.get(Calendar.DAY_OF_MONTH)
75 | );
76 | dpd.show(getFragmentManager(), "Datepickerdialog");
77 | ```
78 |
79 | ### Theme the pickers
80 | The pickers will be themed automatically based on the current theme where they are created, based on the current `colorAccent`. You can also theme the dialogs via the `setAccentColor(int color)` method. Alternatively, you can theme the pickers by overwriting the color resources `mdtp_accent_color` and `mdtp_accent_color_dark` in your project.
81 | ```xml
82 | #009688
83 | #00796b
84 | ```
85 |
86 | The exact order in which colors are selected is as follows:
87 |
88 | 1. `setAccentColor(int color)` in java code
89 | 2. `android.R.attr.colorAccent` (if android 5.0+)
90 | 3. `R.attr.colorAccent` (eg. when using AppCompat)
91 | 4. `R.color.mdtp_accent_color` and `R.color.mdtp_accent_color_dark` if none of the others are set in your project
92 |
93 | The pickers also have a dark theme. This can be specified globablly using the `mdtp_theme_dark` attribute in your theme or the `setThemeDark(boolean themeDark)` functions. The function calls overwrite the XML setting.
94 | ```xml
95 | true
96 | ```
97 |
98 |
99 | ## Additional Options
100 | * `TimePickerDialog` dark theme
101 | The `TimePickerDialog` has a dark theme that can be set by calling
102 | ```java
103 | tpd.setThemeDark(true);
104 | ```
105 |
106 | * `DatePickerDialog` dark theme
107 | The `DatePickerDialog` has a dark theme that can be set by calling
108 | ```java
109 | dpd.setThemeDark(true);
110 | ```
111 |
112 | * `setAccentColor(String color)` and `setAccentColor(int color)`
113 | Set the accentColor to be used by the Dialog. The String version parses the color out using `Color.parseColor()`. The int version requires a ColorInt bytestring. It will explicitly set the color to fully opaque.
114 |
115 | * `TimePickerDialog` `setTitle(String title)`
116 | Shows a title at the top of the `TimePickerDialog`
117 |
118 | * `DatePickerDialog` `setTitle(String title)`
119 | Shows a title at the top of the `DatePickerDialog` instead of the day of the week
120 |
121 | * `setOkText()` and `setCancelText()`
122 | Set a custom text for the dialog Ok and Cancel labels. Can take a resourceId of a String. Works in both the DatePickerDialog and TimePickerDialog
123 |
124 | * `setMinTime(Timepoint time)`
125 | Set the minimum valid time to be selected. Time values earlier in the day will be deactivated
126 |
127 | * `setMaxTime(Timepoint time)`
128 | Set the maximum valid time to be selected. Time values later in the day will be deactivated
129 |
130 | * `setSelectableTimes(Timepoint[] times)`
131 | You can pass in an array of `Timepoints`. These values are the only valid selections in the picker. `setMinTime(Timepoint time)` and `setMaxTime(Timepoint time)` will further trim this list down.
132 |
133 | * `setTimeInterval(int hourInterval, int minuteInterval, int secondInterval)`
134 | Set the interval for selectable times in the TimePickerDialog. This is a convenience wrapper around `setSelectableTimes`
135 |
136 | * `setSelectableDays(Calendar[] days)`
137 | You can pass a `Calendar[]` to the `DatePickerDialog`. The values in this list are the only acceptable dates for the picker. It takes precedence over `setMinDate(Calendar day)` and `setMaxDate(Calendar day)`
138 |
139 | * `setDisabledDays(Calendar[] days)`
140 | The values in this `Calendar[]` are explicitly disabled (not selectable). This option can be used together with `setSelectableDays(Calendar[] days)`: in case there is a clash `setDisabledDays(Calendar[] days)` will take precedence over `setSelectableDays(Calendar[] days)`
141 |
142 | * `setHighlightedDays(Calendar[] days)`
143 | You can pass a `Calendar[]` of days to highlight. They will be rendered in bold. You can tweak the color of the highlighted days by overwriting `mdtp_date_picker_text_highlighted`
144 |
145 | * `showYearPickerFirst(boolean yearPicker)`
146 | Show the year picker first, rather than the month and day picker.
147 |
148 | * `OnDismissListener` and `OnCancelListener`
149 | Both pickers can be passed a `DialogInterface.OnDismissLisener` or `DialogInterface.OnCancelListener` which allows you to run code when either of these events occur.
150 | ```java
151 | tpd.setOnCancelListener(new DialogInterface.OnCancelListener() {
152 | @Override
153 | public void onCancel(DialogInterface dialogInterface) {
154 | Log.d("TimePicker", "Dialog was cancelled");
155 | }
156 | });
157 | ```
158 |
159 | * `vibrate(boolean vibrate)`
160 | Set whether the dialogs should vibrate the device when a selection is made. This defaults to `true`.
161 |
162 | * `dismissOnPause(boolean dismissOnPause)`
163 | Set whether the picker dismisses itself when the parent Activity is paused or whether it recreates itself when the Activity is resumed.
164 |
165 | * `DatePickerDialog` `autoDismiss(boolean autoDismiss)`
166 | If set to `true` will dismiss the picker when the user selects a date. This defaults to `false`.
167 |
168 | * `TimepickerDialog` `enableSeconds(boolean enableSconds)` and `enableMinutes(boolean enableMinutes)`
169 | Allows you to enable or disable a seconds and minutes picker ont he `TimepickerDialog`. Enabling the seconds picker, implies enabling the minutes picker. Disabling the minute picker will disable the seconds picker. The last applied setting will be used. By default `enableSeconds = false` and `enableMinutes = true`.
170 |
171 | ## FAQ
172 |
173 | ### Why not use `SupportDialogFragment`?
174 | Not using the support library versions has been a well considered choice, based on the following considerations:
175 |
176 | * Less than 5% of the devices using the android market do not support native `Fragments`, a number which will decrease even further going forward.
177 | * Even if you use `SupportFragments` in your application, you can still use the normal `FragmentManager`
178 |
179 | This means that in the current setup everyone can use the library: people using the support library and people not using the support library.
180 |
181 | Finally changing to `SupportDialogFragment` now will break the API for all the people using this library.
182 |
183 | If you do really need `SupportDialogFragment`, you should fork the library. It involves changing all of 2 lines of code, so it should be easy enough to keep it up to date with the upstream.
184 |
185 | ### Why does the `DatePickerDialog` return the selected month -1?
186 | In the java `Calendar` class months use 0 based indexing: January is month 0, December is month 11. This convention is widely used in the java world, for example the native Android DatePicker.
187 |
188 | ### How do I use my custom logic to enable/disable dates?
189 | `DatePickerDialog` exposes some utility methods to enable / disable dates for common scenario's. If your needs are not covered by these, you can override the `isOutOfRange()` method by extending the `DatePickerDialog` class.
190 |
191 | ```java
192 | class MyDatePickerDialog extends DatePickerDialog {
193 | @override
194 | public boolean isOutOfRange(int year, int month, int day) {
195 | // disable days that are odd
196 | return day % 2 == 1;
197 | }
198 | }
199 | ```
200 |
201 | > You need to override `isOutOfRange()` with this signature, not the one with the Calendar signature.
202 |
203 | When you override `isOutOfRange()` the built-in methods for setting the enabled / disabled dates will no longer work. It will need to be completely handled by your implementation.
204 |
205 | ### Why are my callbacks lost when the device changes orientation?
206 | The simple solution is to dismiss the pickers when your activity is paused.
207 |
208 | ```java
209 | tpd.dismissOnPause(true);
210 | ```
211 |
212 | If you do wish to retain the pickers when an orientation change occurs, things become a bit more tricky.
213 |
214 | By default, when an orientation changes occurs android will destroy and recreate your entire `Activity`. Wherever possible this library will retain its state on an orientation change. The only notable exceptions are the different callbacks and listeners. These interfaces are often implemented on `Activities` or `Fragments`. Naively trying to retain them would cause memory leaks. Apart from explicitly requiring that the callback interfaces are implemented on an `Activity`, there is no safe way to properly retain the callbacks, that I'm aware off.
215 |
216 | This means that it is your responsibility to set the listeners in your `Activity`'s `onResume()` callback.
217 |
218 | ```java
219 | @Override
220 | public void onResume() {
221 | super.onResume();
222 |
223 | DatePickerDialog dpd = (DatePickerDialog) getFragmentManager().findFragmentByTag("Datepickerdialog");
224 | TimePickerDialog tpd = (TimePickerDialog) getFragmentManager().findFragmentByTag("TimepickerDialog");
225 |
226 | if(tpd != null) tpd.setOnTimeSetListener(this);
227 | if(dpd != null) dpd.setOnDateSetListener(this);
228 | }
229 | ```
230 |
231 |
232 | ## Potential Improvements
233 | * Landscape timepicker can use some improvement
234 | * Implement the new style of pickers
235 | * Code cleanup: there is a bit too much spit and ductape in the tweaks I've done.
236 | * Document all options on both pickers
237 |
238 |
239 | ## License
240 | Copyright (c) 2015 Wouter Dullaert
241 |
242 | Licensed under the Apache License, Version 2.0 (the "License");
243 | you may not use this file except in compliance with the License.
244 | You may obtain a copy of the License at
245 |
246 | http://www.apache.org/licenses/LICENSE-2.0
247 |
248 | Unless required by applicable law or agreed to in writing, software
249 | distributed under the License is distributed on an "AS IS" BASIS,
250 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
251 | See the License for the specific language governing permissions and
252 | limitations under the License.
253 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:2.2.2'
10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
11 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
23 | subprojects {
24 | tasks.withType(Javadoc).all { enabled = false }
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | VERSION_NAME=1.0.2
21 | VERSION_CODE=010002
22 | GROUP=com.layer-net
23 |
24 | ANDROID_BUILD_MIN_SDK_VERSION=14
25 | ANDROID_BUILD_TARGET_SDK_VERSION=24
26 | ANDROID_BUILD_SDK_VERSION=24
27 | ANDROID_BUILD_TOOLS_VERSION=24.0.1
28 |
29 | POM_DESCRIPTION=Material DateTime Picker Thai Edition
30 | POM_URL=https://github.com/layerlre/ThaiDateTimePicker
31 | POM_SCM_URL=https://github.com/layerlre/ThaiDateTimePicker
32 | POM_SCM_CONNECTION=https://github.com/layerlre/ThaiDateTimePicker.git
33 | POM_SCM_DEV_CONNECTION=https://github.com/layerlre/ThaiDateTimePicker.git
34 | POM_LICENCE_NAME=Apache v2 License
35 | POM_LICENCE_URL=https://github.com/layerlre/ThaiDateTimePicker/blob/master/LICENSE
36 | POM_LICENCE_DIST=repo
37 | POM_DEVELOPER_ID=layerlre
38 | POM_DEVELOPER_NAME=Sutachad Wichai
39 | POM_ISSUE_URL=https://github.com/layerlre/ThaiDateTimePicker/issues
40 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 01 14:19:14 CET 2016
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-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/resources/Date.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/resources/Date.png
--------------------------------------------------------------------------------
/resources/Time.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/resources/Time.png
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
5 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
6 |
7 | defaultConfig {
8 | applicationId 'com.wdullaer.materialdatetimepicker.sample'
9 | minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION)
10 | targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
11 | versionName project.VERSION_NAME
12 | versionCode Integer.parseInt(project.VERSION_CODE)
13 | }
14 |
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile 'com.android.support:appcompat-v7:24.0.0'
25 | compile project(':thai-datetimepicker')
26 | }
27 |
--------------------------------------------------------------------------------
/sample/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 /home/wdullaer/Downloads/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 |
--------------------------------------------------------------------------------
/sample/src/androidTest/java/com/layernet/datetimepickerexample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.layernet.datetimepickerexample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/layernet/datetimepickerexample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.layernet.datetimepickerexample;
2 |
3 | import android.content.DialogInterface;
4 | import android.graphics.Color;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.util.Log;
8 | import android.view.View;
9 | import android.widget.Button;
10 | import android.widget.CheckBox;
11 | import android.widget.TextView;
12 |
13 | import com.layernet.thaidatetimepicker.Utils;
14 | import com.layernet.thaidatetimepicker.date.DatePickerDialog;
15 | import com.layernet.thaidatetimepicker.time.RadialPickerLayout;
16 | import com.layernet.thaidatetimepicker.time.TimePickerDialog;
17 |
18 | import java.util.Calendar;
19 |
20 | public class MainActivity extends AppCompatActivity implements
21 | View.OnClickListener,
22 | TimePickerDialog.OnTimeSetListener,
23 | DatePickerDialog.OnDateSetListener
24 | {
25 | private TextView timeTextView;
26 | private TextView dateTextView;
27 | private CheckBox mode24Hours;
28 | private CheckBox modeDarkTime;
29 | private CheckBox modeDarkDate;
30 | private CheckBox modeCustomAccentTime;
31 | private CheckBox modeCustomAccentDate;
32 | private CheckBox vibrateTime;
33 | private CheckBox vibrateDate;
34 | private CheckBox dismissTime;
35 | private CheckBox dismissDate;
36 | private CheckBox titleTime;
37 | private CheckBox titleDate;
38 | private CheckBox showYearFirst;
39 | private CheckBox enableSeconds;
40 | private CheckBox enableMinutes;
41 | private CheckBox limitTimes;
42 | private CheckBox limitDates;
43 | private CheckBox disableDates;
44 | private CheckBox highlightDates;
45 |
46 | @Override
47 | protected void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 | setContentView(R.layout.activity_main);
50 |
51 | // Find our View instances
52 | timeTextView = (TextView)findViewById(R.id.time_textview);
53 | dateTextView = (TextView)findViewById(R.id.date_textview);
54 | Button timeButton = (Button)findViewById(R.id.time_button);
55 | Button dateButton = (Button)findViewById(R.id.date_button);
56 | mode24Hours = (CheckBox)findViewById(R.id.mode_24_hours);
57 | modeDarkTime = (CheckBox)findViewById(R.id.mode_dark_time);
58 | modeDarkDate = (CheckBox)findViewById(R.id.mode_dark_date);
59 | modeCustomAccentTime = (CheckBox) findViewById(R.id.mode_custom_accent_time);
60 | modeCustomAccentDate = (CheckBox) findViewById(R.id.mode_custom_accent_date);
61 | vibrateTime = (CheckBox) findViewById(R.id.vibrate_time);
62 | vibrateDate = (CheckBox) findViewById(R.id.vibrate_date);
63 | dismissTime = (CheckBox) findViewById(R.id.dismiss_time);
64 | dismissDate = (CheckBox) findViewById(R.id.dismiss_date);
65 | titleTime = (CheckBox) findViewById(R.id.title_time);
66 | titleDate = (CheckBox) findViewById(R.id.title_date);
67 | showYearFirst = (CheckBox) findViewById(R.id.show_year_first);
68 | enableSeconds = (CheckBox) findViewById(R.id.enable_seconds);
69 | enableMinutes = (CheckBox) findViewById(R.id.enable_minutes);
70 | limitTimes = (CheckBox) findViewById(R.id.limit_times);
71 | limitDates = (CheckBox) findViewById(R.id.limit_dates);
72 | disableDates = (CheckBox) findViewById(R.id.disable_dates);
73 | highlightDates = (CheckBox) findViewById(R.id.highlight_dates);
74 |
75 | // Check if picker mode is specified in Style.xml
76 | modeDarkTime.setChecked(Utils.isDarkTheme(this, modeDarkTime.isChecked()));
77 | modeDarkDate.setChecked(Utils.isDarkTheme(this, modeDarkDate.isChecked()));
78 |
79 | // Ensure a consistent state between enableSeconds and enableMinutes
80 | enableMinutes.setOnClickListener(this);
81 | enableSeconds.setOnClickListener(this);
82 |
83 | // Show a timepicker when the timeButton is clicked
84 | timeButton.setOnClickListener(new View.OnClickListener() {
85 | @Override
86 | public void onClick(View v) {
87 | Calendar now = Calendar.getInstance();
88 | TimePickerDialog tpd = TimePickerDialog.newInstance(
89 | MainActivity.this,
90 | now.get(Calendar.HOUR_OF_DAY),
91 | now.get(Calendar.MINUTE),
92 | mode24Hours.isChecked()
93 | );
94 | tpd.setThemeDark(modeDarkTime.isChecked());
95 | tpd.vibrate(vibrateTime.isChecked());
96 | tpd.dismissOnPause(dismissTime.isChecked());
97 | tpd.enableSeconds(enableSeconds.isChecked());
98 | tpd.enableMinutes(enableMinutes.isChecked());
99 | if (modeCustomAccentTime.isChecked()) {
100 | tpd.setAccentColor(Color.parseColor("#9C27B0"));
101 | }
102 | if (titleTime.isChecked()) {
103 | tpd.setTitle("TimePicker Title");
104 | }
105 | if (limitTimes.isChecked()) {
106 | tpd.setTimeInterval(2, 5, 10);
107 | }
108 | tpd.setOnCancelListener(new DialogInterface.OnCancelListener() {
109 | @Override
110 | public void onCancel(DialogInterface dialogInterface) {
111 | Log.d("TimePicker", "Dialog was cancelled");
112 | }
113 | });
114 | tpd.show(getFragmentManager(), "Timepickerdialog");
115 | }
116 | });
117 |
118 | // Show a datepicker when the dateButton is clicked
119 | dateButton.setOnClickListener(new View.OnClickListener() {
120 | @Override
121 | public void onClick(View v) {
122 | Calendar now = Calendar.getInstance();
123 | DatePickerDialog dpd = DatePickerDialog.newInstance(
124 | MainActivity.this,
125 | now.get(Calendar.YEAR),
126 | now.get(Calendar.MONTH),
127 | now.get(Calendar.DAY_OF_MONTH)
128 | );
129 | dpd.setThemeDark(modeDarkDate.isChecked());
130 | dpd.vibrate(vibrateDate.isChecked());
131 | dpd.dismissOnPause(dismissDate.isChecked());
132 | dpd.showYearPickerFirst(showYearFirst.isChecked());
133 | if (modeCustomAccentDate.isChecked()) {
134 | dpd.setAccentColor(Color.parseColor("#9C27B0"));
135 | }
136 | if (titleDate.isChecked()) {
137 | dpd.setTitle("DatePicker Title");
138 | }
139 | if (limitDates.isChecked()) {
140 | Calendar[] dates = new Calendar[13];
141 | for (int i = -6; i <= 6; i++) {
142 | Calendar date = Calendar.getInstance();
143 | date.add(Calendar.MONTH, i);
144 | dates[i+6] = date;
145 | }
146 | dpd.setSelectableDays(dates);
147 | }
148 | if (highlightDates.isChecked()) {
149 | Calendar[] dates = new Calendar[13];
150 | for (int i = -6; i <= 6; i++) {
151 | Calendar date = Calendar.getInstance();
152 | date.add(Calendar.WEEK_OF_YEAR, i);
153 | dates[i+6] = date;
154 | }
155 | dpd.setHighlightedDays(dates);
156 | }
157 | if (disableDates.isChecked()) {
158 | Calendar[] dates = new Calendar[3];
159 | for (int i = -1; i <= 1; i++) {
160 | Calendar date = Calendar.getInstance();
161 | date.add(Calendar.DAY_OF_MONTH, i);
162 | dates[i+1] = date;
163 | }
164 | dpd.setDisabledDays(dates);
165 | }
166 | dpd.show(getFragmentManager(), "Datepickerdialog");
167 | }
168 | });
169 | }
170 |
171 | @Override
172 | public void onClick(View view) {
173 | if (enableSeconds.isChecked() && view.getId() == R.id.enable_seconds) enableMinutes.setChecked(true);
174 | if (!enableMinutes.isChecked() && view.getId() == R.id.enable_minutes) enableSeconds.setChecked(false);
175 | }
176 |
177 | @Override
178 | public void onResume() {
179 | super.onResume();
180 | TimePickerDialog tpd = (TimePickerDialog) getFragmentManager().findFragmentByTag("Timepickerdialog");
181 | DatePickerDialog dpd = (DatePickerDialog) getFragmentManager().findFragmentByTag("Datepickerdialog");
182 |
183 | if(tpd != null) tpd.setOnTimeSetListener(this);
184 | if(dpd != null) dpd.setOnDateSetListener(this);
185 | }
186 |
187 | @Override
188 | public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute, int second) {
189 | String hourString = hourOfDay < 10 ? "0"+hourOfDay : ""+hourOfDay;
190 | String minuteString = minute < 10 ? "0"+minute : ""+minute;
191 | String secondString = second < 10 ? "0"+second : ""+second;
192 | String time = "You picked the following time: "+hourString+"h"+minuteString+"m"+secondString+"s";
193 | timeTextView.setText(time);
194 | }
195 |
196 | @Override
197 | public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
198 | String date = "You picked the following date: "+dayOfMonth+"/"+(++monthOfYear)+"/"+year;
199 | dateTextView.setText(date);
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
15 |
16 |
17 |
22 |
23 |
28 |
29 |
34 |
35 |
40 |
41 |
46 |
47 |
53 |
54 |
60 |
61 |
67 |
68 |
74 |
75 |
81 |
82 |
88 |
89 |
90 |
95 |
96 |
101 |
102 |
107 |
108 |
113 |
114 |
120 |
121 |
127 |
128 |
134 |
135 |
141 |
142 |
148 |
149 |
155 |
156 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layerlre/ThaiDateTimePicker/471ec91a091619ff9a99f7c279ba7180d2927be5/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Material DateTimePicker
5 | DateTimePicker Example
6 | The time you picked will be shown here.
7 | The date you picked will be shown here.
8 | Pick Time
9 | Pick Date
10 | Use 24 hour mode
11 | Use dark theme
12 | Use dark theme
13 | Use custom accent
14 | Vibrate on touch
15 | Vibrate on touch
16 | Dismiss on pause (eg: orientation change)
17 | Dismiss on pause (eg: orientation change)
18 | Show a title
19 | Show a title
20 | Show the year picker first
21 | Show seconds picker
22 | Show minutes picker
23 | Limit selectable times
24 | Limit selectable dates
25 | Disable specific dates
26 | Highlight certain dates
27 | Settings
28 |
29 |
30 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':thai-datetimepicker', ':sample'
2 |
--------------------------------------------------------------------------------
/supported_languages.txt:
--------------------------------------------------------------------------------
1 | {"th"}
2 |
--------------------------------------------------------------------------------
/thai-datetimepicker/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.jfrog.bintray'
3 | apply plugin: 'com.github.dcendents.android-maven'
4 | ext {
5 | bintrayRepo = 'maven'
6 | bintrayName = 'ThaiDateTimePicker'
7 |
8 | publishedGroupId = 'com.layer-net'
9 | libraryName = 'thai-datetimepicker'
10 | artifact = 'thai-datetimepicker'
11 |
12 | libraryDescription = 'Material DateTime Picker Thai Edition'
13 |
14 | siteUrl = 'https://github.com/layerlre/ThaiDateTimePicker'
15 | gitUrl = 'https://github.com/layerlre/ThaiDateTimePicker.git'
16 |
17 | libraryVersion = '1.0.3'
18 |
19 | developerId = 'layerlre'
20 | developerName = 'Suthachad Wichai'
21 | developerEmail = 'layerlre@gmail.com'
22 |
23 | licenseName = 'The Apache Software License, Version 2.0'
24 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
25 | allLicenses = ["Apache-2.0"]
26 | }
27 |
28 | android {
29 | compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
30 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
31 |
32 | defaultConfig {
33 | minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION)
34 | targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
35 | versionName project.VERSION_NAME
36 | versionCode Integer.parseInt(project.VERSION_CODE)
37 | }
38 |
39 | buildTypes {
40 | release {
41 | minifyEnabled false
42 | }
43 | }
44 | }
45 |
46 | dependencies {
47 | compile fileTree(dir: 'libs', include: ['*.jar'])
48 | compile 'com.android.support:support-v4:25.0.0'
49 | }
50 |
51 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
52 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
53 | //apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
--------------------------------------------------------------------------------
/thai-datetimepicker/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=ThaiDateTimePicker
2 | POM_ARTIFACT_ID=thai-datetimepicker
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/AccessibleLinearLayout.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 | import android.view.accessibility.AccessibilityEvent;
22 | import android.view.accessibility.AccessibilityNodeInfo;
23 | import android.widget.Button;
24 | import android.widget.LinearLayout;
25 |
26 | /**
27 | * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
28 | */
29 | public class AccessibleLinearLayout extends LinearLayout {
30 |
31 | public AccessibleLinearLayout(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | }
34 |
35 | @Override
36 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
37 | super.onInitializeAccessibilityEvent(event);
38 | event.setClassName(Button.class.getName());
39 | }
40 |
41 | @Override
42 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
43 | super.onInitializeAccessibilityNodeInfo(info);
44 | info.setClassName(Button.class.getName());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/AccessibleTextView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 | import android.view.accessibility.AccessibilityEvent;
22 | import android.view.accessibility.AccessibilityNodeInfo;
23 | import android.widget.Button;
24 | import android.widget.TextView;
25 |
26 | /**
27 | * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
28 | */
29 | public class AccessibleTextView extends TextView {
30 |
31 | public AccessibleTextView(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | }
34 |
35 | @Override
36 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
37 | super.onInitializeAccessibilityEvent(event);
38 | event.setClassName(Button.class.getName());
39 | }
40 |
41 | @Override
42 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
43 | super.onInitializeAccessibilityNodeInfo(info);
44 | info.setClassName(Button.class.getName());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/HapticFeedbackController.java:
--------------------------------------------------------------------------------
1 | package com.layernet.thaidatetimepicker;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 | import android.database.ContentObserver;
7 | import android.net.Uri;
8 | import android.os.SystemClock;
9 | import android.os.Vibrator;
10 | import android.provider.Settings;
11 |
12 | /**
13 | * A simple utility class to handle haptic feedback.
14 | */
15 | public class HapticFeedbackController {
16 | private static final int VIBRATE_DELAY_MS = 125;
17 | private static final int VIBRATE_LENGTH_MS = 50;
18 |
19 | private static boolean checkGlobalSetting(Context context) {
20 | return Settings.System.getInt(context.getContentResolver(),
21 | Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 1;
22 | }
23 |
24 | private final Context mContext;
25 | private final ContentObserver mContentObserver;
26 |
27 | private Vibrator mVibrator;
28 | private boolean mIsGloballyEnabled;
29 | private long mLastVibrate;
30 |
31 | public HapticFeedbackController(Context context) {
32 | mContext = context;
33 | mContentObserver = new ContentObserver(null) {
34 | @Override
35 | public void onChange(boolean selfChange) {
36 | mIsGloballyEnabled = checkGlobalSetting(mContext);
37 | }
38 | };
39 | }
40 |
41 | /**
42 | * Call to setup the controller.
43 | */
44 | public void start() {
45 | if (hasVibratePermission(mContext)) {
46 | mVibrator = (Vibrator) mContext.getSystemService(Service.VIBRATOR_SERVICE);
47 | }
48 |
49 | // Setup a listener for changes in haptic feedback settings
50 | mIsGloballyEnabled = checkGlobalSetting(mContext);
51 | Uri uri = Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED);
52 | mContext.getContentResolver().registerContentObserver(uri, false, mContentObserver);
53 | }
54 |
55 | /**
56 | * Method to verify that vibrate permission has been granted.
57 | *
58 | * Allows users of the library to disabled vibrate support if desired.
59 | * @return true if Vibrate permission has been granted
60 | */
61 | private boolean hasVibratePermission(Context context) {
62 | PackageManager pm = context.getPackageManager();
63 | int hasPerm = pm.checkPermission(android.Manifest.permission.VIBRATE, context.getPackageName());
64 | return hasPerm == PackageManager.PERMISSION_GRANTED;
65 | }
66 |
67 | /**
68 | * Call this when you don't need the controller anymore.
69 | */
70 | public void stop() {
71 | mVibrator = null;
72 | mContext.getContentResolver().unregisterContentObserver(mContentObserver);
73 | }
74 |
75 | /**
76 | * Try to vibrate. To prevent this becoming a single continuous vibration, nothing will
77 | * happen if we have vibrated very recently.
78 | */
79 | public void tryVibrate() {
80 | if (mVibrator != null && mIsGloballyEnabled) {
81 | long now = SystemClock.uptimeMillis();
82 | // We want to try to vibrate each individual tick discretely.
83 | if (now - mLastVibrate >= VIBRATE_DELAY_MS) {
84 | mVibrator.vibrate(VIBRATE_LENGTH_MS);
85 | mLastVibrate = now;
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/TypefaceHelper.java:
--------------------------------------------------------------------------------
1 | package com.layernet.thaidatetimepicker;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Typeface;
6 | import android.support.v4.util.SimpleArrayMap;
7 |
8 | /*
9 | Each call to Typeface.createFromAsset will load a new instance of the typeface into memory,
10 | and this memory is not consistently get garbage collected
11 | http://code.google.com/p/android/issues/detail?id=9904
12 | (It states released but even on Lollipop you can see the typefaces accumulate even after
13 | multiple GC passes)
14 | You can detect this by running:
15 | adb shell dumpsys meminfo com.your.packagenage
16 | You will see output like:
17 | Asset Allocations
18 | zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
19 | zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
20 | zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
21 | zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Regular.ttf: 123K
22 | zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
23 | */
24 | public class TypefaceHelper {
25 |
26 | private static final SimpleArrayMap cache = new SimpleArrayMap<>();
27 |
28 | public static Typeface get(Context c, String name) {
29 | synchronized (cache) {
30 | if (!cache.containsKey(name)) {
31 | Typeface t = Typeface.createFromAsset(
32 | c.getAssets(), String.format("fonts/%s.ttf", name));
33 | cache.put(name, t);
34 | return t;
35 | }
36 | return cache.get(name);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker;
18 |
19 | import android.animation.Keyframe;
20 | import android.animation.ObjectAnimator;
21 | import android.animation.PropertyValuesHolder;
22 | import android.annotation.SuppressLint;
23 | import android.content.Context;
24 | import android.content.res.Resources;
25 | import android.content.res.TypedArray;
26 | import android.graphics.Color;
27 | import android.os.Build;
28 | import android.support.annotation.AttrRes;
29 | import android.support.v4.content.ContextCompat;
30 | import android.util.TypedValue;
31 | import android.view.View;
32 |
33 | /**
34 | * Utility helper functions for time and date pickers.
35 | */
36 | public class Utils {
37 |
38 | //public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
39 | public static final int PULSE_ANIMATOR_DURATION = 544;
40 |
41 | // Alpha level for time picker selection.
42 | public static final int SELECTED_ALPHA = 255;
43 | public static final int SELECTED_ALPHA_THEME_DARK = 255;
44 | // Alpha level for fully opaque.
45 | public static final int FULL_ALPHA = 255;
46 |
47 | public static boolean isJellybeanOrLater() {
48 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
49 | }
50 |
51 | /**
52 | * Try to speak the specified text, for accessibility. Only available on JB or later.
53 | * @param text Text to announce.
54 | */
55 | @SuppressLint("NewApi")
56 | public static void tryAccessibilityAnnounce(View view, CharSequence text) {
57 | if (isJellybeanOrLater() && view != null && text != null) {
58 | view.announceForAccessibility(text);
59 | }
60 | }
61 |
62 | /**
63 | * Takes a number of weeks since the epoch and calculates the Julian day of
64 | * the Monday for that week.
65 | *
66 | * This assumes that the week containing the {@link Time#EPOCH_JULIAN_DAY}
67 | * is considered week 0. It returns the Julian day for the Monday
68 | * {@code week} weeks after the Monday of the week containing the epoch.
69 | *
70 | * @param week Number of weeks since the epoch
71 | * @return The julian day for the Monday of the given week since the epoch
72 | */
73 | /**
74 | public static int getJulianMondayFromWeeksSinceEpoch(int week) {
75 | return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
76 | }
77 | */
78 |
79 | /**
80 | * Returns the week since {@link Time#EPOCH_JULIAN_DAY} (Jan 1, 1970)
81 | * adjusted for first day of week.
82 | *
83 | * This takes a julian day and the week start day and calculates which
84 | * week since {@link Time#EPOCH_JULIAN_DAY} that day occurs in, starting
85 | * at 0. *Do not* use this to compute the ISO week number for the year.
86 | *
87 | * @param julianDay The julian day to calculate the week number for
88 | * @param firstDayOfWeek Which week day is the first day of the week,
89 | * see {@link Time#SUNDAY}
90 | * @return Weeks since the epoch
91 | */
92 | /**
93 | public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
94 | int diff = Time.THURSDAY - firstDayOfWeek;
95 | if (diff < 0) {
96 | diff += 7;
97 | }
98 | int refDay = Time.EPOCH_JULIAN_DAY - diff;
99 | return (julianDay - refDay) / 7;
100 | }
101 | */
102 |
103 | /**
104 | * Render an animator to pulsate a view in place.
105 | * @param labelToAnimate the view to pulsate.
106 | * @return The animator object. Use .start() to begin.
107 | */
108 | public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio,
109 | float increaseRatio) {
110 | Keyframe k0 = Keyframe.ofFloat(0f, 1f);
111 | Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio);
112 | Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
113 | Keyframe k3 = Keyframe.ofFloat(1f, 1f);
114 |
115 | PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3);
116 | PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe("scaleY", k0, k1, k2, k3);
117 | ObjectAnimator pulseAnimator =
118 | ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
119 | pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
120 |
121 | return pulseAnimator;
122 | }
123 |
124 | /**
125 | * Convert Dp to Pixel
126 | */
127 | @SuppressWarnings("unused")
128 | public static int dpToPx(float dp, Resources resources){
129 | float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
130 | return (int) px;
131 | }
132 |
133 | public static int darkenColor(int color) {
134 | float[] hsv = new float[3];
135 | Color.colorToHSV(color, hsv);
136 | hsv[2] = hsv[2] * 0.8f; // value component
137 | return Color.HSVToColor(hsv);
138 | }
139 |
140 | /**
141 | * Gets the colorAccent from the current context, if possible/available
142 | * @param context The context to use as reference for the color
143 | * @return the accent color of the current context
144 | */
145 | public static int getAccentColorFromThemeIfAvailable(Context context) {
146 | TypedValue typedValue = new TypedValue();
147 | // First, try the android:colorAccent
148 | if (Build.VERSION.SDK_INT >= 21) {
149 | context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
150 | return typedValue.data;
151 | }
152 | // Next, try colorAccent from support lib
153 | int colorAccentResId = context.getResources().getIdentifier("colorAccent", "attr", context.getPackageName());
154 | if (colorAccentResId != 0 && context.getTheme().resolveAttribute(colorAccentResId, typedValue, true)) {
155 | return typedValue.data;
156 | }
157 | // Return the value in mdtp_accent_color
158 | return ContextCompat.getColor(context, R.color.mdtp_accent_color);
159 | }
160 |
161 | /**
162 | * Gets dialog type (Light/Dark) from current theme
163 | * @param context The context to use as reference for the boolean
164 | * @param current Default value to return if cannot resolve the attribute
165 | * @return true if dark mode, false if light.
166 | */
167 | public static boolean isDarkTheme(Context context, boolean current) {
168 | return resolveBoolean(context, R.attr.mdtp_theme_dark, current);
169 | }
170 |
171 | /**
172 | * Gets the required boolean value from the current context, if possible/available
173 | * @param context The context to use as reference for the boolean
174 | * @param attr Attribute id to resolve
175 | * @param fallback Default value to return if no value is specified in theme
176 | * @return the boolean value from current theme
177 | */
178 | private static boolean resolveBoolean(Context context, @AttrRes int attr, boolean fallback) {
179 | TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
180 | try {
181 | return a.getBoolean(0, fallback);
182 | } finally {
183 | a.recycle();
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/AccessibleDateAnimator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.content.Context;
20 | import android.text.format.DateUtils;
21 | import android.util.AttributeSet;
22 | import android.view.accessibility.AccessibilityEvent;
23 | import android.widget.ViewAnimator;
24 |
25 | public class AccessibleDateAnimator extends ViewAnimator {
26 | private long mDateMillis;
27 |
28 | public AccessibleDateAnimator(Context context, AttributeSet attrs) {
29 | super(context, attrs);
30 | }
31 |
32 | public void setDateMillis(long dateMillis) {
33 | mDateMillis = dateMillis;
34 | }
35 |
36 | /**
37 | * Announce the currently-selected date when launched.
38 | */
39 | @Override
40 | public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
41 | if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
42 | // Clear the event's current text so that only the current date will be spoken.
43 | event.getText().clear();
44 | int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
45 | DateUtils.FORMAT_SHOW_WEEKDAY;
46 |
47 | String dateString = DateUtils.formatDateTime(getContext(), mDateMillis, flags);
48 | event.getText().add(dateString);
49 | return true;
50 | }
51 | return super.dispatchPopulateAccessibilityEvent(event);
52 | }
53 | }
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/DatePickerController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import java.util.Calendar;
20 |
21 | /**
22 | * Controller class to communicate among the various components of the date picker dialog.
23 | */
24 | public interface DatePickerController {
25 |
26 | void onYearSelected(int year);
27 |
28 | void onDayOfMonthSelected(int year, int month, int day);
29 |
30 | void registerOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener);
31 |
32 | void unregisterOnDateChangedListener(DatePickerDialog.OnDateChangedListener listener);
33 |
34 | MonthAdapter.CalendarDay getSelectedDay();
35 |
36 | boolean isThemeDark();
37 |
38 | int getAccentColor();
39 |
40 | Calendar[] getHighlightedDays();
41 |
42 | int getFirstDayOfWeek();
43 |
44 | int getMinYear();
45 |
46 | int getMaxYear();
47 |
48 | Calendar getStartDate();
49 |
50 | Calendar getEndDate();
51 |
52 | boolean isOutOfRange(int year, int month, int day);
53 |
54 | void tryVibrate();
55 | }
56 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/DayPickerView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.content.Context;
21 | import android.os.Build;
22 | import android.os.Bundle;
23 | import android.os.Handler;
24 | import android.support.annotation.NonNull;
25 | import android.util.AttributeSet;
26 | import android.util.Log;
27 | import android.view.View;
28 | import android.view.ViewConfiguration;
29 | import android.view.accessibility.AccessibilityEvent;
30 | import android.view.accessibility.AccessibilityNodeInfo;
31 | import android.widget.AbsListView;
32 | import android.widget.AbsListView.OnScrollListener;
33 | import android.widget.ListView;
34 |
35 | import com.layernet.thaidatetimepicker.Utils;
36 | import com.layernet.thaidatetimepicker.date.DatePickerDialog.OnDateChangedListener;
37 |
38 | import java.text.SimpleDateFormat;
39 | import java.util.Calendar;
40 | import java.util.Locale;
41 |
42 | /**
43 | * This displays a list of months in a calendar format with selectable days.
44 | */
45 | public abstract class DayPickerView extends ListView implements OnScrollListener,
46 | OnDateChangedListener {
47 |
48 | private static final String TAG = "MonthFragment";
49 |
50 | // Affects when the month selection will change while scrolling up
51 | protected static final int SCROLL_HYST_WEEKS = 2;
52 | // How long the GoTo fling animation should last
53 | protected static final int GOTO_SCROLL_DURATION = 250;
54 | // How long to wait after receiving an onScrollStateChanged notification
55 | // before acting on it
56 | protected static final int SCROLL_CHANGE_DELAY = 40;
57 | // The number of days to display in each week
58 | public static final int DAYS_PER_WEEK = 7;
59 | public static int LIST_TOP_OFFSET = -1; // so that the top line will be
60 | // under the separator
61 | // You can override these numbers to get a different appearance
62 | protected int mNumWeeks = 6;
63 | protected boolean mShowWeekNumber = false;
64 | protected int mDaysPerWeek = 7;
65 | private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
66 |
67 | // These affect the scroll speed and feel
68 | protected float mFriction = 1.0f;
69 |
70 | protected Context mContext;
71 | protected Handler mHandler;
72 |
73 | // highlighted time
74 | protected MonthAdapter.CalendarDay mSelectedDay = new MonthAdapter.CalendarDay();
75 | protected MonthAdapter mAdapter;
76 |
77 | protected MonthAdapter.CalendarDay mTempDay = new MonthAdapter.CalendarDay();
78 |
79 | // When the week starts; numbered like Time. (e.g. SUNDAY=0).
80 | protected int mFirstDayOfWeek;
81 | // The last name announced by accessibility
82 | protected CharSequence mPrevMonthName;
83 | // which month should be displayed/highlighted [0-11]
84 | protected int mCurrentMonthDisplayed;
85 | // used for tracking during a scroll
86 | protected long mPreviousScrollPosition;
87 | // used for tracking what state listview is in
88 | protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
89 | // used for tracking what state listview is in
90 | protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
91 |
92 | private DatePickerController mController;
93 | private boolean mPerformingScroll;
94 |
95 | public DayPickerView(Context context, AttributeSet attrs) {
96 | super(context, attrs);
97 | init(context);
98 | }
99 |
100 | public DayPickerView(Context context, DatePickerController controller) {
101 | super(context);
102 | init(context);
103 | setController(controller);
104 | }
105 |
106 | public void setController(DatePickerController controller) {
107 | mController = controller;
108 | mController.registerOnDateChangedListener(this);
109 | refreshAdapter();
110 | onDateChanged();
111 | }
112 |
113 | public void init(Context context) {
114 | mHandler = new Handler();
115 | setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
116 | setDrawSelectorOnTop(false);
117 |
118 | mContext = context;
119 | setUpListView();
120 | }
121 |
122 | public void onChange() {
123 | refreshAdapter();
124 | }
125 |
126 | /**
127 | * Creates a new adapter if necessary and sets up its parameters. Override
128 | * this method to provide a custom adapter.
129 | */
130 | protected void refreshAdapter() {
131 | if (mAdapter == null) {
132 | mAdapter = createMonthAdapter(getContext(), mController);
133 | } else {
134 | mAdapter.setSelectedDay(mSelectedDay);
135 | }
136 | // refresh the view with the new parameters
137 | setAdapter(mAdapter);
138 | }
139 |
140 | public abstract MonthAdapter createMonthAdapter(Context context,
141 | DatePickerController controller);
142 |
143 | /*
144 | * Sets all the required fields for the list view. Override this method to
145 | * set a different list view behavior.
146 | */
147 | protected void setUpListView() {
148 | // Transparent background on scroll
149 | setCacheColorHint(0);
150 | // No dividers
151 | setDivider(null);
152 | // Items are clickable
153 | setItemsCanFocus(true);
154 | // The thumb gets in the way, so disable it
155 | setFastScrollEnabled(false);
156 | setVerticalScrollBarEnabled(false);
157 | setOnScrollListener(this);
158 | setFadingEdgeLength(0);
159 | // Make the scrolling behavior nicer
160 | setFriction(ViewConfiguration.getScrollFriction() * mFriction);
161 | }
162 |
163 | /**
164 | * This moves to the specified time in the view. If the time is not already
165 | * in range it will move the list so that the first of the month containing
166 | * the time is at the top of the view. If the new time is already in view
167 | * the list will not be scrolled unless forceScroll is true. This time may
168 | * optionally be highlighted as selected as well.
169 | *
170 | * @param day The day to move to
171 | * @param animate Whether to scroll to the given time or just redraw at the
172 | * new location
173 | * @param setSelected Whether to set the given time as selected
174 | * @param forceScroll Whether to recenter even if the time is already
175 | * visible
176 | * @return Whether or not the view animated to the new location
177 | */
178 | public boolean goTo(MonthAdapter.CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {
179 |
180 | // Set the selected day
181 | if (setSelected) {
182 | mSelectedDay.set(day);
183 | }
184 |
185 | mTempDay.set(day);
186 | int minMonth = mController.getStartDate().get(Calendar.MONTH);
187 | final int position = (day.year - mController.getMinYear())
188 | * MonthAdapter.MONTHS_IN_YEAR + day.month - minMonth;
189 |
190 | View child;
191 | int i = 0;
192 | int top = 0;
193 | // Find a child that's completely in the view
194 | do {
195 | child = getChildAt(i++);
196 | if (child == null) {
197 | break;
198 | }
199 | top = child.getTop();
200 | if (Log.isLoggable(TAG, Log.DEBUG)) {
201 | Log.d(TAG, "child at " + (i - 1) + " has top " + top);
202 | }
203 | } while (top < 0);
204 |
205 | // Compute the first and last position visible
206 | int selectedPosition;
207 | if (child != null) {
208 | selectedPosition = getPositionForView(child);
209 | } else {
210 | selectedPosition = 0;
211 | }
212 |
213 | if (setSelected) {
214 | mAdapter.setSelectedDay(mSelectedDay);
215 | }
216 |
217 | if (Log.isLoggable(TAG, Log.DEBUG)) {
218 | Log.d(TAG, "GoTo position " + position);
219 | }
220 | // Check if the selected day is now outside of our visible range
221 | // and if so scroll to the month that contains it
222 | if (position != selectedPosition || forceScroll) {
223 | setMonthDisplayed(mTempDay);
224 | mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
225 | if (animate) {
226 | smoothScrollToPositionFromTop(
227 | position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
228 | return true;
229 | } else {
230 | postSetSelection(position);
231 | }
232 | } else if (setSelected) {
233 | setMonthDisplayed(mSelectedDay);
234 | }
235 | return false;
236 | }
237 |
238 | public void postSetSelection(final int position) {
239 | clearFocus();
240 | post(new Runnable() {
241 |
242 | @Override
243 | public void run() {
244 | setSelection(position);
245 | }
246 | });
247 | onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
248 | }
249 |
250 | /**
251 | * Updates the title and selected month if the view has moved to a new
252 | * month.
253 | */
254 | @Override
255 | public void onScroll(
256 | AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
257 | MonthView child = (MonthView) view.getChildAt(0);
258 | if (child == null) {
259 | return;
260 | }
261 |
262 | // Figure out where we are
263 | long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
264 | mPreviousScrollPosition = currScroll;
265 | mPreviousScrollState = mCurrentScrollState;
266 | }
267 |
268 | /**
269 | * Sets the month displayed at the top of this view based on time. Override
270 | * to add custom events when the title is changed.
271 | */
272 | protected void setMonthDisplayed(MonthAdapter.CalendarDay date) {
273 | mCurrentMonthDisplayed = date.month;
274 | invalidateViews();
275 | }
276 |
277 | @Override
278 | public void onScrollStateChanged(AbsListView view, int scrollState) {
279 | // use a post to prevent re-entering onScrollStateChanged before it
280 | // exits
281 | mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
282 | }
283 |
284 | protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
285 |
286 | protected class ScrollStateRunnable implements Runnable {
287 | private int mNewState;
288 |
289 | /**
290 | * Sets up the runnable with a short delay in case the scroll state
291 | * immediately changes again.
292 | *
293 | * @param view The list view that changed state
294 | * @param scrollState The new state it changed to
295 | */
296 | public void doScrollStateChange(AbsListView view, int scrollState) {
297 | mHandler.removeCallbacks(this);
298 | mNewState = scrollState;
299 | mHandler.postDelayed(this, SCROLL_CHANGE_DELAY);
300 | }
301 |
302 | @Override
303 | public void run() {
304 | mCurrentScrollState = mNewState;
305 | if (Log.isLoggable(TAG, Log.DEBUG)) {
306 | Log.d(TAG,
307 | "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
308 | }
309 | // Fix the position after a scroll or a fling ends
310 | if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
311 | && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE
312 | && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
313 | mPreviousScrollState = mNewState;
314 | int i = 0;
315 | View child = getChildAt(i);
316 | while (child != null && child.getBottom() <= 0) {
317 | child = getChildAt(++i);
318 | }
319 | if (child == null) {
320 | // The view is no longer visible, just return
321 | return;
322 | }
323 | int firstPosition = getFirstVisiblePosition();
324 | int lastPosition = getLastVisiblePosition();
325 | boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1;
326 | final int top = child.getTop();
327 | final int bottom = child.getBottom();
328 | final int midpoint = getHeight() / 2;
329 | if (scroll && top < LIST_TOP_OFFSET) {
330 | if (bottom > midpoint) {
331 | smoothScrollBy(top, GOTO_SCROLL_DURATION);
332 | } else {
333 | smoothScrollBy(bottom, GOTO_SCROLL_DURATION);
334 | }
335 | }
336 | } else {
337 | mPreviousScrollState = mNewState;
338 | }
339 | }
340 | }
341 |
342 | /**
343 | * Gets the position of the view that is most prominently displayed within the list view.
344 | */
345 | public int getMostVisiblePosition() {
346 | final int firstPosition = getFirstVisiblePosition();
347 | final int height = getHeight();
348 |
349 | int maxDisplayedHeight = 0;
350 | int mostVisibleIndex = 0;
351 | int i=0;
352 | int bottom = 0;
353 | while (bottom < height) {
354 | View child = getChildAt(i);
355 | if (child == null) {
356 | break;
357 | }
358 | bottom = child.getBottom();
359 | int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop());
360 | if (displayedHeight > maxDisplayedHeight) {
361 | mostVisibleIndex = i;
362 | maxDisplayedHeight = displayedHeight;
363 | }
364 | i++;
365 | }
366 | return firstPosition + mostVisibleIndex;
367 | }
368 |
369 | @Override
370 | public void onDateChanged() {
371 | goTo(mController.getSelectedDay(), false, true, true);
372 | }
373 |
374 | /**
375 | * Attempts to return the date that has accessibility focus.
376 | *
377 | * @return The date that has accessibility focus, or {@code null} if no date
378 | * has focus.
379 | */
380 | private MonthAdapter.CalendarDay findAccessibilityFocus() {
381 | final int childCount = getChildCount();
382 | for (int i = 0; i < childCount; i++) {
383 | final View child = getChildAt(i);
384 | if (child instanceof MonthView) {
385 | final MonthAdapter.CalendarDay focus = ((MonthView) child).getAccessibilityFocus();
386 | if (focus != null) {
387 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
388 | // Clear focus to avoid ListView bug in Jelly Bean MR1.
389 | ((MonthView) child).clearAccessibilityFocus();
390 | }
391 | return focus;
392 | }
393 | }
394 | }
395 |
396 | return null;
397 | }
398 |
399 | /**
400 | * Attempts to restore accessibility focus to a given date. No-op if
401 | * {@code day} is {@code null}.
402 | *
403 | * @param day The date that should receive accessibility focus
404 | * @return {@code true} if focus was restored
405 | */
406 | private boolean restoreAccessibilityFocus(MonthAdapter.CalendarDay day) {
407 | if (day == null) {
408 | return false;
409 | }
410 |
411 | final int childCount = getChildCount();
412 | for (int i = 0; i < childCount; i++) {
413 | final View child = getChildAt(i);
414 | if (child instanceof MonthView) {
415 | if (((MonthView) child).restoreAccessibilityFocus(day)) {
416 | return true;
417 | }
418 | }
419 | }
420 |
421 | return false;
422 | }
423 |
424 | @Override
425 | protected void layoutChildren() {
426 | final MonthAdapter.CalendarDay focusedDay = findAccessibilityFocus();
427 | super.layoutChildren();
428 | if (mPerformingScroll) {
429 | mPerformingScroll = false;
430 | } else {
431 | restoreAccessibilityFocus(focusedDay);
432 | }
433 | }
434 |
435 | @Override
436 | public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
437 | super.onInitializeAccessibilityEvent(event);
438 | event.setItemCount(-1);
439 | }
440 |
441 | private static String getMonthAndYearString(MonthAdapter.CalendarDay day) {
442 | Calendar cal = Calendar.getInstance();
443 | cal.set(day.year, day.month, day.day);
444 |
445 | String sbuf = "";
446 | sbuf += cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
447 | sbuf += " ";
448 | sbuf += YEAR_FORMAT.format(cal.getTime());
449 | return sbuf;
450 | }
451 |
452 | /**
453 | * Necessary for accessibility, to ensure we support "scrolling" forward and backward
454 | * in the month list.
455 | */
456 | @Override
457 | @SuppressWarnings("deprecation")
458 | public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
459 | super.onInitializeAccessibilityNodeInfo(info);
460 | if(Build.VERSION.SDK_INT >= 21) {
461 | info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
462 | info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
463 | }
464 | else {
465 | info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
466 | info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
467 | }
468 | }
469 |
470 | /**
471 | * When scroll forward/backward events are received, announce the newly scrolled-to month.
472 | */
473 | @SuppressLint("NewApi")
474 | @Override
475 | public boolean performAccessibilityAction(int action, Bundle arguments) {
476 | if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
477 | action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
478 | return super.performAccessibilityAction(action, arguments);
479 | }
480 |
481 | // Figure out what month is showing.
482 | int firstVisiblePosition = getFirstVisiblePosition();
483 | int minMonth = mController.getStartDate().get(Calendar.MONTH);
484 | int month = (firstVisiblePosition + minMonth) % MonthAdapter.MONTHS_IN_YEAR;
485 | int year = (firstVisiblePosition + minMonth) / MonthAdapter.MONTHS_IN_YEAR + mController.getMinYear();
486 | MonthAdapter.CalendarDay day = new MonthAdapter.CalendarDay(year, month, 1);
487 |
488 | // Scroll either forward or backward one month.
489 | if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
490 | day.month++;
491 | if (day.month == 12) {
492 | day.month = 0;
493 | day.year++;
494 | }
495 | } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
496 | View firstVisibleView = getChildAt(0);
497 | // If the view is fully visible, jump one month back. Otherwise, we'll just jump
498 | // to the first day of first visible month.
499 | if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
500 | // There's an off-by-one somewhere, so the top of the first visible item will
501 | // actually be -1 when it's at the exact top.
502 | day.month--;
503 | if (day.month == -1) {
504 | day.month = 11;
505 | day.year--;
506 | }
507 | }
508 | }
509 |
510 | // Go to that month.
511 | Utils.tryAccessibilityAnnounce(this, getMonthAndYearString(day));
512 | goTo(day, true, false, true);
513 | mPerformingScroll = true;
514 | return true;
515 | }
516 | }
517 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/MonthAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.content.Context;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.widget.AbsListView.LayoutParams;
24 | import android.widget.BaseAdapter;
25 |
26 | import com.layernet.thaidatetimepicker.date.MonthView.OnDayClickListener;
27 |
28 | import java.util.Calendar;
29 | import java.util.HashMap;
30 |
31 | /**
32 | * An adapter for a list of {@link MonthView} items.
33 | */
34 | public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
35 |
36 | private static final String TAG = "SimpleMonthAdapter";
37 |
38 | private final Context mContext;
39 | protected final DatePickerController mController;
40 |
41 | private CalendarDay mSelectedDay;
42 |
43 | protected static int WEEK_7_OVERHANG_HEIGHT = 7;
44 | protected static final int MONTHS_IN_YEAR = 12;
45 |
46 | /**
47 | * A convenience class to represent a specific date.
48 | */
49 | public static class CalendarDay {
50 | private Calendar calendar;
51 | int year;
52 | int month;
53 | int day;
54 |
55 | public CalendarDay() {
56 | setTime(System.currentTimeMillis());
57 | }
58 |
59 | public CalendarDay(long timeInMillis) {
60 | setTime(timeInMillis);
61 | }
62 |
63 | public CalendarDay(Calendar calendar) {
64 | year = calendar.get(Calendar.YEAR);
65 | month = calendar.get(Calendar.MONTH);
66 | day = calendar.get(Calendar.DAY_OF_MONTH);
67 | }
68 |
69 | public CalendarDay(int year, int month, int day) {
70 | setDay(year, month, day);
71 | }
72 |
73 | public void set(CalendarDay date) {
74 | year = date.year;
75 | month = date.month;
76 | day = date.day;
77 | }
78 |
79 | public void setDay(int year, int month, int day) {
80 | this.year = year;
81 | this.month = month;
82 | this.day = day;
83 | }
84 |
85 | private void setTime(long timeInMillis) {
86 | if (calendar == null) {
87 | calendar = Calendar.getInstance();
88 | }
89 | calendar.setTimeInMillis(timeInMillis);
90 | month = calendar.get(Calendar.MONTH);
91 | year = calendar.get(Calendar.YEAR);
92 | day = calendar.get(Calendar.DAY_OF_MONTH);
93 | }
94 |
95 | public int getYear() {
96 | return year;
97 | }
98 |
99 | public int getMonth() {
100 | return month;
101 | }
102 |
103 | public int getDay() {
104 | return day;
105 | }
106 | }
107 |
108 | public MonthAdapter(Context context,
109 | DatePickerController controller) {
110 | mContext = context;
111 | mController = controller;
112 | init();
113 | setSelectedDay(mController.getSelectedDay());
114 | }
115 |
116 | /**
117 | * Updates the selected day and related parameters.
118 | *
119 | * @param day The day to highlight
120 | */
121 | public void setSelectedDay(CalendarDay day) {
122 | mSelectedDay = day;
123 | notifyDataSetChanged();
124 | }
125 |
126 | @SuppressWarnings("unused")
127 | public CalendarDay getSelectedDay() {
128 | return mSelectedDay;
129 | }
130 |
131 | /**
132 | * Set up the gesture detector and selected time
133 | */
134 | protected void init() {
135 | mSelectedDay = new CalendarDay(System.currentTimeMillis());
136 | }
137 |
138 | @Override
139 | public int getCount() {
140 | Calendar endDate = mController.getEndDate();
141 | Calendar startDate = mController.getStartDate();
142 | int endMonth = endDate.get(Calendar.YEAR) * MONTHS_IN_YEAR + endDate.get(Calendar.MONTH);
143 | int startMonth = startDate.get(Calendar.YEAR) * MONTHS_IN_YEAR + startDate.get(Calendar.MONTH);
144 | return endMonth - startMonth + 1;
145 | //return ((mController.getMaxYear() - mController.getMinYear()) + 1) * MONTHS_IN_YEAR;
146 | }
147 |
148 | @Override
149 | public Object getItem(int position) {
150 | return null;
151 | }
152 |
153 | @Override
154 | public long getItemId(int position) {
155 | return position;
156 | }
157 |
158 | @Override
159 | public boolean hasStableIds() {
160 | return true;
161 | }
162 |
163 | @SuppressLint("NewApi")
164 | @SuppressWarnings("unchecked")
165 | @Override
166 | public View getView(int position, View convertView, ViewGroup parent) {
167 | MonthView v;
168 | HashMap drawingParams = null;
169 | if (convertView != null) {
170 | v = (MonthView) convertView;
171 | // We store the drawing parameters in the view so it can be recycled
172 | drawingParams = (HashMap) v.getTag();
173 | } else {
174 | v = createMonthView(mContext);
175 | // Set up the new view
176 | LayoutParams params = new LayoutParams(
177 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
178 | v.setLayoutParams(params);
179 | v.setClickable(true);
180 | v.setOnDayClickListener(this);
181 | }
182 | if (drawingParams == null) {
183 | drawingParams = new HashMap<>();
184 | }
185 | drawingParams.clear();
186 |
187 | final int month = (position + mController.getStartDate().get(Calendar.MONTH)) % MONTHS_IN_YEAR;
188 | final int year = (position + mController.getStartDate().get(Calendar.MONTH)) / MONTHS_IN_YEAR + mController.getMinYear();
189 |
190 | int selectedDay = -1;
191 | if (isSelectedDayInMonth(year, month)) {
192 | selectedDay = mSelectedDay.day;
193 | }
194 |
195 | // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
196 | // height/number of weeks before being displayed.
197 | v.reuse();
198 |
199 | drawingParams.put(MonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
200 | drawingParams.put(MonthView.VIEW_PARAMS_YEAR, year);
201 | drawingParams.put(MonthView.VIEW_PARAMS_MONTH, month);
202 | drawingParams.put(MonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
203 | v.setMonthParams(drawingParams);
204 | v.invalidate();
205 | return v;
206 | }
207 |
208 | public abstract MonthView createMonthView(Context context);
209 |
210 | private boolean isSelectedDayInMonth(int year, int month) {
211 | return mSelectedDay.year == year && mSelectedDay.month == month;
212 | }
213 |
214 |
215 | @Override
216 | public void onDayClick(MonthView view, CalendarDay day) {
217 | if (day != null) {
218 | onDayTapped(day);
219 | }
220 | }
221 |
222 | /**
223 | * Maintains the same hour/min/sec but moves the day to the tapped day.
224 | *
225 | * @param day The day that was tapped
226 | */
227 | protected void onDayTapped(CalendarDay day) {
228 | mController.tryVibrate();
229 | mController.onDayOfMonthSelected(day.year, day.month, day.day);
230 | setSelectedDay(day);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/MonthView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.content.Context;
20 | import android.content.res.Resources;
21 | import android.graphics.Canvas;
22 | import android.graphics.Paint;
23 | import android.graphics.Paint.Align;
24 | import android.graphics.Paint.Style;
25 | import android.graphics.Rect;
26 | import android.graphics.Typeface;
27 | import android.os.Build;
28 | import android.os.Bundle;
29 | import android.support.annotation.NonNull;
30 | import android.support.v4.content.ContextCompat;
31 | import android.support.v4.view.ViewCompat;
32 | import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
33 | import android.support.v4.widget.ExploreByTouchHelper;
34 | import android.text.format.DateFormat;
35 | import android.util.AttributeSet;
36 | import android.view.MotionEvent;
37 | import android.view.View;
38 | import android.view.accessibility.AccessibilityEvent;
39 | import android.view.accessibility.AccessibilityNodeInfo;
40 |
41 | import com.layernet.thaidatetimepicker.R;
42 | import com.layernet.thaidatetimepicker.TypefaceHelper;
43 | import com.layernet.thaidatetimepicker.date.MonthAdapter.CalendarDay;
44 |
45 | import java.security.InvalidParameterException;
46 | import java.text.SimpleDateFormat;
47 | import java.util.Calendar;
48 | import java.util.Formatter;
49 | import java.util.HashMap;
50 | import java.util.List;
51 | import java.util.Locale;
52 |
53 | /**
54 | * A calendar-like view displaying a specified month and the appropriate selectable day numbers
55 | * within the specified month.
56 | */
57 | public abstract class MonthView extends View {
58 | private static final String TAG = "MonthView";
59 |
60 | /**
61 | * These params can be passed into the view to control how it appears.
62 | * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
63 | * values are unlikely to fit most layouts correctly.
64 | */
65 | /**
66 | * This sets the height of this week in pixels
67 | */
68 | public static final String VIEW_PARAMS_HEIGHT = "height";
69 | /**
70 | * This specifies the position (or weeks since the epoch) of this week.
71 | */
72 | public static final String VIEW_PARAMS_MONTH = "month";
73 | /**
74 | * This specifies the position (or weeks since the epoch) of this week.
75 | */
76 | public static final String VIEW_PARAMS_YEAR = "year";
77 | /**
78 | * This sets one of the days in this view as selected {@link Calendar#SUNDAY}
79 | * through {@link Calendar#SATURDAY}.
80 | */
81 | public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
82 | /**
83 | * Which day the week should start on. {@link Calendar#SUNDAY} through
84 | * {@link Calendar#SATURDAY}.
85 | */
86 | public static final String VIEW_PARAMS_WEEK_START = "week_start";
87 | /**
88 | * How many days to display at a time. Days will be displayed starting with
89 | * {@link #mWeekStart}.
90 | */
91 | public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
92 | /**
93 | * Which month is currently in focus, as defined by {@link Calendar#MONTH}
94 | * [0-11].
95 | */
96 | public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
97 | /**
98 | * If this month should display week numbers. false if 0, true otherwise.
99 | */
100 | public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
101 |
102 | protected static int DEFAULT_HEIGHT = 32;
103 | protected static int MIN_HEIGHT = 10;
104 | protected static final int DEFAULT_SELECTED_DAY = -1;
105 | protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
106 | protected static final int DEFAULT_NUM_DAYS = 7;
107 | protected static final int DEFAULT_SHOW_WK_NUM = 0;
108 | protected static final int DEFAULT_FOCUS_MONTH = -1;
109 | protected static final int DEFAULT_NUM_ROWS = 6;
110 | protected static final int MAX_NUM_ROWS = 6;
111 | protected static final int BUDDHIST_OFFSET = 543;
112 |
113 | private static final int SELECTED_CIRCLE_ALPHA = 255;
114 |
115 | protected static int DAY_SEPARATOR_WIDTH = 1;
116 | protected static int MINI_DAY_NUMBER_TEXT_SIZE;
117 | protected static int MONTH_LABEL_TEXT_SIZE;
118 | protected static int MONTH_DAY_LABEL_TEXT_SIZE;
119 | protected static int MONTH_HEADER_SIZE;
120 | protected static int DAY_SELECTED_CIRCLE_SIZE;
121 |
122 | private static Locale locale = new Locale("th", "TH");
123 |
124 | // used for scaling to the device density
125 | protected static float mScale = 0;
126 |
127 | protected DatePickerController mController;
128 |
129 | // affects the padding on the sides of this view
130 | protected int mEdgePadding = 0;
131 |
132 | private String mDayOfWeekTypeface;
133 | private String mMonthTitleTypeface;
134 |
135 | protected Paint mMonthNumPaint;
136 | protected Paint mMonthTitlePaint;
137 | protected Paint mSelectedCirclePaint;
138 | protected Paint mMonthDayLabelPaint;
139 |
140 | private final Formatter mFormatter;
141 | private final StringBuilder mStringBuilder;
142 |
143 | // The Julian day of the first day displayed by this item
144 | protected int mFirstJulianDay = -1;
145 | // The month of the first day in this week
146 | protected int mFirstMonth = -1;
147 | // The month of the last day in this week
148 | protected int mLastMonth = -1;
149 |
150 | protected int mMonth;
151 |
152 | protected int mYear;
153 | // Quick reference to the width of this view, matches parent
154 | protected int mWidth;
155 | // The height this view should draw at in pixels, set by height param
156 | protected int mRowHeight = DEFAULT_HEIGHT;
157 | // If this view contains the today
158 | protected boolean mHasToday = false;
159 | // Which day is selected [0-6] or -1 if no day is selected
160 | protected int mSelectedDay = -1;
161 | // Which day is today [0-6] or -1 if no day is today
162 | protected int mToday = DEFAULT_SELECTED_DAY;
163 | // Which day of the week to start on [0-6]
164 | protected int mWeekStart = DEFAULT_WEEK_START;
165 | // How many days to display
166 | protected int mNumDays = DEFAULT_NUM_DAYS;
167 | // The number of days + a spot for week number if it is displayed
168 | protected int mNumCells = mNumDays;
169 | // The left edge of the selected day
170 | protected int mSelectedLeft = -1;
171 | // The right edge of the selected day
172 | protected int mSelectedRight = -1;
173 |
174 | private final Calendar mCalendar;
175 | protected final Calendar mDayLabelCalendar;
176 | private final MonthViewTouchHelper mTouchHelper;
177 |
178 | protected int mNumRows = DEFAULT_NUM_ROWS;
179 |
180 | // Optional listener for handling day click actions
181 | protected OnDayClickListener mOnDayClickListener;
182 |
183 | // Whether to prevent setting the accessibility delegate
184 | private boolean mLockAccessibilityDelegate;
185 |
186 | protected int mDayTextColor;
187 | protected int mSelectedDayTextColor;
188 | protected int mMonthDayTextColor;
189 | protected int mTodayNumberColor;
190 | protected int mHighlightedDayTextColor;
191 | protected int mDisabledDayTextColor;
192 | protected int mMonthTitleColor;
193 |
194 | public MonthView(Context context) {
195 | this(context, null, null);
196 | }
197 |
198 | public MonthView(Context context, AttributeSet attr, DatePickerController controller) {
199 | super(context, attr);
200 | mController = controller;
201 | Resources res = context.getResources();
202 |
203 | mDayLabelCalendar = Calendar.getInstance();
204 | mCalendar = Calendar.getInstance();
205 |
206 | mDayOfWeekTypeface = res.getString(R.string.mdtp_day_of_week_label_typeface);
207 | mMonthTitleTypeface = res.getString(R.string.mdtp_sans_serif);
208 |
209 | boolean darkTheme = mController != null && mController.isThemeDark();
210 | if (darkTheme) {
211 | mDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_normal_dark_theme);
212 | mMonthDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_month_day_dark_theme);
213 | mDisabledDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled_dark_theme);
214 | mHighlightedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_highlighted_dark_theme);
215 | } else {
216 | mDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_normal);
217 | mMonthDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_month_day);
218 | mDisabledDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled);
219 | mHighlightedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_highlighted);
220 | }
221 | mSelectedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
222 | mTodayNumberColor = mController.getAccentColor();
223 | mMonthTitleColor = ContextCompat.getColor(context, R.color.mdtp_white);
224 |
225 | mStringBuilder = new StringBuilder(50);
226 | mFormatter = new Formatter(mStringBuilder, locale);
227 |
228 | MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_day_number_size);
229 | MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_month_label_size);
230 | MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_month_day_label_text_size);
231 | MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.mdtp_month_list_item_header_height);
232 | DAY_SELECTED_CIRCLE_SIZE = res
233 | .getDimensionPixelSize(R.dimen.mdtp_day_number_select_circle_radius);
234 |
235 | mRowHeight = (res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height)
236 | - getMonthHeaderSize()) / MAX_NUM_ROWS;
237 |
238 | // Set up accessibility components.
239 | mTouchHelper = getMonthViewTouchHelper();
240 | ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
241 | ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
242 | mLockAccessibilityDelegate = true;
243 |
244 | // Sets up any standard paints that will be used
245 | initView();
246 | }
247 |
248 | public void setDatePickerController(DatePickerController controller) {
249 | mController = controller;
250 | }
251 |
252 | protected MonthViewTouchHelper getMonthViewTouchHelper() {
253 | return new MonthViewTouchHelper(this);
254 | }
255 |
256 | @Override
257 | public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
258 | // Workaround for a JB MR1 issue where accessibility delegates on
259 | // top-level ListView items are overwritten.
260 | if (!mLockAccessibilityDelegate) {
261 | super.setAccessibilityDelegate(delegate);
262 | }
263 | }
264 |
265 | public void setOnDayClickListener(OnDayClickListener listener) {
266 | mOnDayClickListener = listener;
267 | }
268 |
269 | @Override
270 | public boolean dispatchHoverEvent(@NonNull MotionEvent event) {
271 | // First right-of-refusal goes the touch exploration helper.
272 | if (mTouchHelper.dispatchHoverEvent(event)) {
273 | return true;
274 | }
275 | return super.dispatchHoverEvent(event);
276 | }
277 |
278 | @Override
279 | public boolean onTouchEvent(@NonNull MotionEvent event) {
280 | switch (event.getAction()) {
281 | case MotionEvent.ACTION_UP:
282 | final int day = getDayFromLocation(event.getX(), event.getY());
283 | if (day >= 0) {
284 | onDayClick(day);
285 | }
286 | break;
287 | }
288 | return true;
289 | }
290 |
291 | /**
292 | * Sets up the text and style properties for painting. Override this if you
293 | * want to use a different paint.
294 | */
295 | protected void initView() {
296 | mMonthTitlePaint = new Paint();
297 | mMonthTitlePaint.setFakeBoldText(true);
298 | mMonthTitlePaint.setAntiAlias(true);
299 | mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
300 | mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
301 | mMonthTitlePaint.setColor(mDayTextColor);
302 | mMonthTitlePaint.setTextAlign(Align.CENTER);
303 | mMonthTitlePaint.setStyle(Style.FILL);
304 |
305 | mSelectedCirclePaint = new Paint();
306 | mSelectedCirclePaint.setFakeBoldText(true);
307 | mSelectedCirclePaint.setAntiAlias(true);
308 | mSelectedCirclePaint.setColor(mTodayNumberColor);
309 | mSelectedCirclePaint.setTextAlign(Align.CENTER);
310 | mSelectedCirclePaint.setStyle(Style.FILL);
311 | mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
312 |
313 | mMonthDayLabelPaint = new Paint();
314 | mMonthDayLabelPaint.setAntiAlias(true);
315 | mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
316 | mMonthDayLabelPaint.setColor(mMonthDayTextColor);
317 | mMonthDayLabelPaint.setTypeface(TypefaceHelper.get(getContext(), "Roboto-Medium"));
318 | mMonthDayLabelPaint.setStyle(Style.FILL);
319 | mMonthDayLabelPaint.setTextAlign(Align.CENTER);
320 | mMonthDayLabelPaint.setFakeBoldText(true);
321 |
322 | mMonthNumPaint = new Paint();
323 | mMonthNumPaint.setAntiAlias(true);
324 | mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
325 | mMonthNumPaint.setStyle(Style.FILL);
326 | mMonthNumPaint.setTextAlign(Align.CENTER);
327 | mMonthNumPaint.setFakeBoldText(false);
328 | }
329 |
330 | @Override
331 | protected void onDraw(Canvas canvas) {
332 | drawMonthTitle(canvas);
333 | drawMonthDayLabels(canvas);
334 | drawMonthNums(canvas);
335 | }
336 |
337 | private int mDayOfWeekStart = 0;
338 |
339 | /**
340 | * Sets all the parameters for displaying this week. The only required
341 | * parameter is the week number. Other parameters have a default value and
342 | * will only update if a new value is included, except for focus month,
343 | * which will always default to no focus month if no value is passed in. See
344 | * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
345 | *
346 | * @param params A map of the new parameters, see
347 | * {@link #VIEW_PARAMS_HEIGHT}
348 | */
349 | public void setMonthParams(HashMap params) {
350 | if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
351 | throw new InvalidParameterException("You must specify month and year for this view");
352 | }
353 | setTag(params);
354 | // We keep the current value for any params not present
355 | if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
356 | mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
357 | if (mRowHeight < MIN_HEIGHT) {
358 | mRowHeight = MIN_HEIGHT;
359 | }
360 | }
361 | if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
362 | mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
363 | }
364 |
365 | // Allocate space for caching the day numbers and focus values
366 | mMonth = params.get(VIEW_PARAMS_MONTH);
367 | mYear = params.get(VIEW_PARAMS_YEAR);
368 |
369 | // Figure out what day today is
370 | //final Time today = new Time(Time.getCurrentTimezone());
371 | //today.setToNow();
372 | final Calendar today = Calendar.getInstance();
373 | mHasToday = false;
374 | mToday = -1;
375 |
376 | mCalendar.set(Calendar.MONTH, mMonth);
377 | mCalendar.set(Calendar.YEAR, mYear);
378 | mCalendar.set(Calendar.DAY_OF_MONTH, 1);
379 | mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
380 |
381 | if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
382 | mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
383 | } else {
384 | mWeekStart = mCalendar.getFirstDayOfWeek();
385 | }
386 |
387 | mNumCells = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
388 | for (int i = 0; i < mNumCells; i++) {
389 | final int day = i + 1;
390 | if (sameDay(day, today)) {
391 | mHasToday = true;
392 | mToday = day;
393 | }
394 | }
395 | mNumRows = calculateNumRows();
396 |
397 | // Invalidate cached accessibility information.
398 | mTouchHelper.invalidateRoot();
399 | }
400 |
401 | public void setSelectedDay(int day) {
402 | mSelectedDay = day;
403 | }
404 |
405 | public void reuse() {
406 | mNumRows = DEFAULT_NUM_ROWS;
407 | requestLayout();
408 | }
409 |
410 | private int calculateNumRows() {
411 | int offset = findDayOffset();
412 | int dividend = (offset + mNumCells) / mNumDays;
413 | int remainder = (offset + mNumCells) % mNumDays;
414 | return (dividend + (remainder > 0 ? 1 : 0));
415 | }
416 |
417 | private boolean sameDay(int day, Calendar today) {
418 | return mYear == today.get(Calendar.YEAR) &&
419 | mMonth == today.get(Calendar.MONTH) &&
420 | day == today.get(Calendar.DAY_OF_MONTH);
421 | }
422 |
423 | @Override
424 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
425 | setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
426 | + getMonthHeaderSize() + 5);
427 | }
428 |
429 | @Override
430 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
431 | mWidth = w;
432 |
433 | // Invalidate cached accessibility information.
434 | mTouchHelper.invalidateRoot();
435 | }
436 |
437 | public int getMonth() {
438 | return mMonth;
439 | }
440 |
441 | public int getYear() {
442 | return mYear;
443 | }
444 |
445 | /**
446 | * A wrapper to the MonthHeaderSize to allow override it in children
447 | */
448 | protected int getMonthHeaderSize() {
449 | return MONTH_HEADER_SIZE;
450 | }
451 |
452 | @NonNull
453 | private String getMonthAndYearString() {
454 | /** thai calender edit */
455 | String pattern = "MMMM";
456 |
457 | if (Build.VERSION.SDK_INT > 17) pattern = DateFormat.getBestDateTimePattern(locale, pattern);
458 |
459 | SimpleDateFormat formatter = new SimpleDateFormat(pattern, locale);
460 | formatter.applyLocalizedPattern(pattern);
461 | mStringBuilder.setLength(0);
462 | return formatter.format(mCalendar.getTime()) + " " + getContext().getString(R.string.mdtp_buddhist) + " " + (mCalendar.get(Calendar.YEAR) + BUDDHIST_OFFSET); // Thai calendar edit
463 | }
464 |
465 | protected void drawMonthTitle(Canvas canvas) {
466 | int x = (mWidth + 2 * mEdgePadding) / 2;
467 | int y = (getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE) / 2;
468 | canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
469 | }
470 |
471 | protected void drawMonthDayLabels(Canvas canvas) {
472 | int y = getMonthHeaderSize() - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
473 | int dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2);
474 |
475 | for (int i = 0; i < mNumDays; i++) {
476 | int x = (2 * i + 1) * dayWidthHalf + mEdgePadding;
477 |
478 | int calendarDay = (i + mWeekStart) % mNumDays;
479 | mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
480 | String weekString = getWeekDayLabel(mDayLabelCalendar);
481 | canvas.drawText(weekString, x, y, mMonthDayLabelPaint);
482 | }
483 | }
484 |
485 | /**
486 | * Draws the week and month day numbers for this week. Override this method
487 | * if you need different placement.
488 | *
489 | * @param canvas The canvas to draw on
490 | */
491 | protected void drawMonthNums(Canvas canvas) {
492 | int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
493 | + getMonthHeaderSize();
494 | final float dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2.0f);
495 | int j = findDayOffset();
496 | for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
497 | final int x = (int) ((2 * j + 1) * dayWidthHalf + mEdgePadding);
498 |
499 | int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
500 |
501 | final int startX = (int) (x - dayWidthHalf);
502 | final int stopX = (int) (x + dayWidthHalf);
503 | final int startY = (int) (y - yRelativeToDay);
504 | final int stopY = (int) (startY + mRowHeight);
505 |
506 | drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
507 |
508 | j++;
509 | if (j == mNumDays) {
510 | j = 0;
511 | y += mRowHeight;
512 | }
513 | }
514 | }
515 |
516 | /**
517 | * This method should draw the month day. Implemented by sub-classes to allow customization.
518 | *
519 | * @param canvas The canvas to draw on
520 | * @param year The year of this month day
521 | * @param month The month of this month day
522 | * @param day The day number of this month day
523 | * @param x The default x position to draw the day number
524 | * @param y The default y position to draw the day number
525 | * @param startX The left boundary of the day number rect
526 | * @param stopX The right boundary of the day number rect
527 | * @param startY The top boundary of the day number rect
528 | * @param stopY The bottom boundary of the day number rect
529 | */
530 | public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
531 | int x, int y, int startX, int stopX, int startY, int stopY);
532 |
533 | protected int findDayOffset() {
534 | return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
535 | - mWeekStart;
536 | }
537 |
538 |
539 | /**
540 | * Calculates the day that the given x position is in, accounting for week
541 | * number. Returns the day or -1 if the position wasn't in a day.
542 | *
543 | * @param x The x position of the touch event
544 | * @return The day number, or -1 if the position wasn't in a day
545 | */
546 | public int getDayFromLocation(float x, float y) {
547 | final int day = getInternalDayFromLocation(x, y);
548 | if (day < 1 || day > mNumCells) {
549 | return -1;
550 | }
551 | return day;
552 | }
553 |
554 | /**
555 | * Calculates the day that the given x position is in, accounting for week
556 | * number.
557 | *
558 | * @param x The x position of the touch event
559 | * @return The day number
560 | */
561 | protected int getInternalDayFromLocation(float x, float y) {
562 | int dayStart = mEdgePadding;
563 | if (x < dayStart || x > mWidth - mEdgePadding) {
564 | return -1;
565 | }
566 | // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
567 | int row = (int) (y - getMonthHeaderSize()) / mRowHeight;
568 | int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mEdgePadding));
569 |
570 | int day = column - findDayOffset() + 1;
571 | day += row * mNumDays;
572 | return day;
573 | }
574 |
575 | /**
576 | * Called when the user clicks on a day. Handles callbacks to the
577 | * {@link OnDayClickListener} if one is set.
578 | *
579 | * If the day is out of the range set by minDate and/or maxDate, this is a no-op.
580 | *
581 | * @param day The day that was clicked
582 | */
583 | private void onDayClick(int day) {
584 | // If the min / max date are set, only process the click if it's a valid selection.
585 | if (mController.isOutOfRange(mYear, mMonth, day)) {
586 | return;
587 | }
588 |
589 |
590 | if (mOnDayClickListener != null) {
591 | mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
592 | }
593 |
594 | // This is a no-op if accessibility is turned off.
595 | mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
596 | }
597 |
598 | /**
599 | * @param year
600 | * @param month
601 | * @param day
602 | * @return true if the given date should be highlighted
603 | */
604 | protected boolean isHighlighted(int year, int month, int day) {
605 | Calendar[] highlightedDays = mController.getHighlightedDays();
606 | if (highlightedDays == null) return false;
607 | for (Calendar c : highlightedDays) {
608 | if (year < c.get(Calendar.YEAR)) break;
609 | if (year > c.get(Calendar.YEAR)) continue;
610 | if (month < c.get(Calendar.MONTH)) break;
611 | if (month > c.get(Calendar.MONTH)) continue;
612 | if (day < c.get(Calendar.DAY_OF_MONTH)) break;
613 | if (day > c.get(Calendar.DAY_OF_MONTH)) continue;
614 | return true;
615 | }
616 | return false;
617 | }
618 |
619 | /**
620 | * Return a 1 or 2 letter String for use as a weekday label
621 | *
622 | * @param day The day for which to generate a label
623 | * @return The weekday label
624 | */
625 | private String getWeekDayLabel(Calendar day) {
626 |
627 | // Localised short version of the string is not available on API < 18
628 | if (Build.VERSION.SDK_INT < 18) {
629 | String dayName = new SimpleDateFormat("E", locale).format(day.getTime());
630 | String dayLabel = dayName.substring(0, 1);
631 |
632 | // Thai labels should select 1 or 2 letter
633 | if (mDayLabelCalendar.get(Calendar.DAY_OF_WEEK) != Calendar.THURSDAY || mDayLabelCalendar.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
634 | dayLabel = dayName.substring(0, 1);
635 | } else {
636 | dayName.substring(0, 2);
637 | }
638 |
639 | return dayLabel;
640 | }
641 | // Getting the short label is a one liner on API >= 18
642 | return new SimpleDateFormat("EEEEE", locale).format(day.getTime());
643 | }
644 |
645 | /**
646 | * @return The date that has accessibility focus, or {@code null} if no date
647 | * has focus
648 | */
649 | public CalendarDay getAccessibilityFocus() {
650 | final int day = mTouchHelper.getFocusedVirtualView();
651 | if (day >= 0) {
652 | return new CalendarDay(mYear, mMonth, day);
653 | }
654 | return null;
655 | }
656 |
657 | /**
658 | * Clears accessibility focus within the view. No-op if the view does not
659 | * contain accessibility focus.
660 | */
661 | public void clearAccessibilityFocus() {
662 | mTouchHelper.clearFocusedVirtualView();
663 | }
664 |
665 | /**
666 | * Attempts to restore accessibility focus to the specified date.
667 | *
668 | * @param day The date which should receive focus
669 | * @return {@code false} if the date is not valid for this month view, or
670 | * {@code true} if the date received focus
671 | */
672 | public boolean restoreAccessibilityFocus(CalendarDay day) {
673 | if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
674 | return false;
675 | }
676 | mTouchHelper.setFocusedVirtualView(day.day);
677 | return true;
678 | }
679 |
680 | /**
681 | * Provides a virtual view hierarchy for interfacing with an accessibility
682 | * service.
683 | */
684 | protected class MonthViewTouchHelper extends ExploreByTouchHelper {
685 | private static final String DATE_FORMAT = "dd MMMM yyyy";
686 |
687 | private final Rect mTempRect = new Rect();
688 | private final Calendar mTempCalendar = Calendar.getInstance();
689 |
690 | public MonthViewTouchHelper(View host) {
691 | super(host);
692 | }
693 |
694 | public void setFocusedVirtualView(int virtualViewId) {
695 | getAccessibilityNodeProvider(MonthView.this).performAction(
696 | virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
697 | }
698 |
699 | public void clearFocusedVirtualView() {
700 | final int focusedVirtualView = getFocusedVirtualView();
701 | if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
702 | getAccessibilityNodeProvider(MonthView.this).performAction(
703 | focusedVirtualView,
704 | AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
705 | null);
706 | }
707 | }
708 |
709 | @Override
710 | protected int getVirtualViewAt(float x, float y) {
711 | final int day = getDayFromLocation(x, y);
712 | if (day >= 0) {
713 | return day;
714 | }
715 | return ExploreByTouchHelper.INVALID_ID;
716 | }
717 |
718 | @Override
719 | protected void getVisibleVirtualViews(List virtualViewIds) {
720 | for (int day = 1; day <= mNumCells; day++) {
721 | virtualViewIds.add(day);
722 | }
723 | }
724 |
725 | @Override
726 | protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
727 | event.setContentDescription(getItemDescription(virtualViewId));
728 | }
729 |
730 | @Override
731 | protected void onPopulateNodeForVirtualView(int virtualViewId,
732 | AccessibilityNodeInfoCompat node) {
733 | getItemBounds(virtualViewId, mTempRect);
734 |
735 | node.setContentDescription(getItemDescription(virtualViewId));
736 | node.setBoundsInParent(mTempRect);
737 | node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
738 |
739 | if (virtualViewId == mSelectedDay) {
740 | node.setSelected(true);
741 | }
742 |
743 | }
744 |
745 | @Override
746 | protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
747 | Bundle arguments) {
748 | switch (action) {
749 | case AccessibilityNodeInfo.ACTION_CLICK:
750 | onDayClick(virtualViewId);
751 | return true;
752 | }
753 |
754 | return false;
755 | }
756 |
757 | /**
758 | * Calculates the bounding rectangle of a given time object.
759 | *
760 | * @param day The day to calculate bounds for
761 | * @param rect The rectangle in which to store the bounds
762 | */
763 | protected void getItemBounds(int day, Rect rect) {
764 | final int offsetX = mEdgePadding;
765 | final int offsetY = getMonthHeaderSize();
766 | final int cellHeight = mRowHeight;
767 | final int cellWidth = ((mWidth - (2 * mEdgePadding)) / mNumDays);
768 | final int index = ((day - 1) + findDayOffset());
769 | final int row = (index / mNumDays);
770 | final int column = (index % mNumDays);
771 | final int x = (offsetX + (column * cellWidth));
772 | final int y = (offsetY + (row * cellHeight));
773 |
774 | rect.set(x, y, (x + cellWidth), (y + cellHeight));
775 | }
776 |
777 | /**
778 | * Generates a description for a given time object. Since this
779 | * description will be spoken, the components are ordered by descending
780 | * specificity as DAY MONTH YEAR.
781 | *
782 | * @param day The day to generate a description for
783 | * @return A description of the time object
784 | */
785 | protected CharSequence getItemDescription(int day) {
786 | mTempCalendar.set(mYear, mMonth, day);
787 | final CharSequence date = DateFormat.format(DATE_FORMAT,
788 | mTempCalendar.getTimeInMillis());
789 |
790 | if (day == mSelectedDay) {
791 | return getContext().getString(R.string.mdtp_item_is_selected, date);
792 | }
793 |
794 | return date;
795 | }
796 | }
797 |
798 | /**
799 | * Handles callbacks when the user clicks on a time object.
800 | */
801 | public interface OnDayClickListener {
802 | void onDayClick(MonthView view, CalendarDay day);
803 | }
804 | }
805 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/SimpleDayPickerView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 |
22 | /**
23 | * A DayPickerView customized for {@link SimpleMonthAdapter}
24 | */
25 | public class SimpleDayPickerView extends DayPickerView {
26 |
27 | public SimpleDayPickerView(Context context, AttributeSet attrs) {
28 | super(context, attrs);
29 | }
30 |
31 | public SimpleDayPickerView(Context context, DatePickerController controller) {
32 | super(context, controller);
33 | }
34 |
35 | @Override
36 | public MonthAdapter createMonthAdapter(Context context, DatePickerController controller) {
37 | return new SimpleMonthAdapter(context, controller);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/SimpleMonthAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.content.Context;
20 |
21 | /**
22 | * An adapter for a list of {@link SimpleMonthView} items.
23 | */
24 | public class SimpleMonthAdapter extends MonthAdapter {
25 |
26 | public SimpleMonthAdapter(Context context, DatePickerController controller) {
27 | super(context, controller);
28 | }
29 |
30 | @Override
31 | public MonthView createMonthView(Context context) {
32 | final MonthView monthView = new SimpleMonthView(context, null, mController);
33 | return monthView;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/SimpleMonthView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.content.Context;
20 | import android.graphics.Canvas;
21 | import android.graphics.Typeface;
22 | import android.util.AttributeSet;
23 |
24 | public class SimpleMonthView extends MonthView {
25 |
26 | public SimpleMonthView(Context context, AttributeSet attr, DatePickerController controller) {
27 | super(context, attr, controller);
28 | }
29 |
30 | @Override
31 | public void drawMonthDay(Canvas canvas, int year, int month, int day,
32 | int x, int y, int startX, int stopX, int startY, int stopY) {
33 | if (mSelectedDay == day) {
34 | canvas.drawCircle(x , y - (MINI_DAY_NUMBER_TEXT_SIZE / 3), DAY_SELECTED_CIRCLE_SIZE,
35 | mSelectedCirclePaint);
36 | }
37 |
38 | if(isHighlighted(year, month, day)) {
39 | mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
40 | }
41 | else {
42 | mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
43 | }
44 |
45 | // If we have a mindate or maxdate, gray out the day number if it's outside the range.
46 | if (mController.isOutOfRange(year, month, day)) {
47 | mMonthNumPaint.setColor(mDisabledDayTextColor);
48 | }
49 | else if (mSelectedDay == day) {
50 | mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
51 | mMonthNumPaint.setColor(mSelectedDayTextColor);
52 | } else if (mHasToday && mToday == day) {
53 | mMonthNumPaint.setColor(mTodayNumberColor);
54 | } else {
55 | mMonthNumPaint.setColor(isHighlighted(year, month, day) ? mHighlightedDayTextColor : mDayTextColor);
56 | }
57 |
58 | canvas.drawText(String.format("%d", day), x, y, mMonthNumPaint);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/TextViewWithCircularIndicator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.content.Context;
20 | import android.content.res.ColorStateList;
21 | import android.graphics.Canvas;
22 | import android.graphics.Color;
23 | import android.graphics.Paint;
24 | import android.graphics.Paint.Align;
25 | import android.graphics.Paint.Style;
26 | import android.support.annotation.NonNull;
27 | import android.support.v4.content.ContextCompat;
28 | import android.util.AttributeSet;
29 | import android.widget.TextView;
30 |
31 | import com.layernet.thaidatetimepicker.R;
32 |
33 | /**
34 | * A text view which, when pressed or activated, displays a colored circle around the text.
35 | */
36 | public class TextViewWithCircularIndicator extends TextView {
37 |
38 | private static final int SELECTED_CIRCLE_ALPHA = 255;
39 |
40 | Paint mCirclePaint = new Paint();
41 |
42 | private int mCircleColor;
43 | private final String mItemIsSelectedText;
44 |
45 | private boolean mDrawCircle;
46 |
47 | public TextViewWithCircularIndicator(Context context, AttributeSet attrs) {
48 | super(context, attrs);
49 | mCircleColor = ContextCompat.getColor(context, R.color.mdtp_accent_color);
50 | mItemIsSelectedText = context.getResources().getString(R.string.mdtp_item_is_selected);
51 |
52 | init();
53 | }
54 |
55 | private void init() {
56 | mCirclePaint.setFakeBoldText(true);
57 | mCirclePaint.setAntiAlias(true);
58 | mCirclePaint.setColor(mCircleColor);
59 | mCirclePaint.setTextAlign(Align.CENTER);
60 | mCirclePaint.setStyle(Style.FILL);
61 | mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
62 | }
63 |
64 | public void setAccentColor(int color, boolean darkMode) {
65 | mCircleColor = color;
66 | mCirclePaint.setColor(mCircleColor);
67 | setTextColor(createTextColor(color, darkMode));
68 | }
69 |
70 | /**
71 | * Programmatically set the color state list (see mdtp_date_picker_year_selector)
72 | * @param accentColor pressed state text color
73 | * @param darkMode current theme mode
74 | * @return ColorStateList with pressed state
75 | */
76 | private ColorStateList createTextColor(int accentColor, boolean darkMode) {
77 | int[][] states = new int[][]{
78 | new int[]{android.R.attr.state_pressed}, // pressed
79 | new int[]{android.R.attr.state_selected}, // selected
80 | new int[]{}
81 | };
82 | int[] colors = new int[]{
83 | accentColor,
84 | Color.WHITE,
85 | darkMode ? Color.WHITE : Color.BLACK
86 | };
87 | return new ColorStateList(states, colors);
88 | }
89 |
90 | public void drawIndicator(boolean drawCircle) {
91 | mDrawCircle = drawCircle;
92 | }
93 |
94 | @Override
95 | public void onDraw(@NonNull Canvas canvas) {
96 | if (mDrawCircle) {
97 | final int width = getWidth();
98 | final int height = getHeight();
99 | int radius = Math.min(width, height) / 2;
100 | canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint);
101 | }
102 | setSelected(mDrawCircle);
103 | super.onDraw(canvas);
104 | }
105 |
106 | @Override
107 | public CharSequence getContentDescription() {
108 | CharSequence itemText = getText();
109 | if (mDrawCircle) {
110 | return String.format(mItemIsSelectedText, itemText);
111 | } else {
112 | return itemText;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/date/YearPickerView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.date;
18 |
19 | import android.content.Context;
20 | import android.content.res.Resources;
21 | import android.graphics.drawable.StateListDrawable;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.view.accessibility.AccessibilityEvent;
26 | import android.widget.AdapterView;
27 | import android.widget.AdapterView.OnItemClickListener;
28 | import android.widget.BaseAdapter;
29 | import android.widget.ListView;
30 | import android.widget.TextView;
31 |
32 | import com.layernet.thaidatetimepicker.R;
33 | import com.layernet.thaidatetimepicker.date.DatePickerDialog.OnDateChangedListener;
34 |
35 | /**
36 | * Displays a selectable list of years.
37 | */
38 | public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
39 | private static final String TAG = "YearPickerView";
40 | private static final int BUDDHIST_OFFSET = 543;
41 | private final DatePickerController mController;
42 | private YearAdapter mAdapter;
43 | private int mViewSize;
44 | private int mChildSize;
45 | private TextViewWithCircularIndicator mSelectedView;
46 |
47 | public YearPickerView(Context context, DatePickerController controller) {
48 | super(context);
49 | mController = controller;
50 | mController.registerOnDateChangedListener(this);
51 | ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
52 | LayoutParams.WRAP_CONTENT);
53 | setLayoutParams(frame);
54 | Resources res = context.getResources();
55 | mViewSize = res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height);
56 | mChildSize = res.getDimensionPixelOffset(R.dimen.mdtp_year_label_height);
57 | setVerticalFadingEdgeEnabled(true);
58 | setFadingEdgeLength(mChildSize / 3);
59 | init();
60 | setOnItemClickListener(this);
61 | setSelector(new StateListDrawable());
62 | setDividerHeight(0);
63 | onDateChanged();
64 | }
65 |
66 | private void init() {
67 | mAdapter = new YearAdapter(mController.getMinYear(), mController.getMaxYear());
68 | setAdapter(mAdapter);
69 | }
70 |
71 | @Override
72 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
73 | mController.tryVibrate();
74 | TextViewWithCircularIndicator clickedView = (TextViewWithCircularIndicator) view;
75 | if (clickedView != null) {
76 | if (clickedView != mSelectedView) {
77 | if (mSelectedView != null) {
78 | mSelectedView.drawIndicator(false);
79 | mSelectedView.requestLayout();
80 | }
81 | clickedView.drawIndicator(true);
82 | clickedView.requestLayout();
83 | mSelectedView = clickedView;
84 | }
85 | mController.onYearSelected(getYearFromTextView(clickedView));
86 | mAdapter.notifyDataSetChanged();
87 | }
88 | }
89 |
90 | private static int getYearFromTextView(TextView view) {
91 | return Integer.valueOf(view.getText().toString())-BUDDHIST_OFFSET; // Thai calendar edit
92 | }
93 |
94 | private final class YearAdapter extends BaseAdapter {
95 | private final int mMinYear;
96 | private final int mMaxYear;
97 |
98 | YearAdapter(int minYear, int maxYear) {
99 | if (minYear > maxYear) {
100 | throw new IllegalArgumentException("minYear > maxYear");
101 | }
102 | mMinYear = minYear;
103 | mMaxYear = maxYear;
104 | }
105 |
106 | @Override
107 | public int getCount() {
108 | return mMaxYear - mMinYear + 1;
109 | }
110 |
111 | @Override
112 | public Object getItem(int position) {
113 | return mMinYear + position;
114 | }
115 |
116 | @Override
117 | public long getItemId(int position) {
118 | return position;
119 | }
120 |
121 | @Override
122 | public View getView(int position, View convertView, ViewGroup parent) {
123 | TextViewWithCircularIndicator v;
124 | if (convertView != null) {
125 | v = (TextViewWithCircularIndicator) convertView;
126 | } else {
127 | v = (TextViewWithCircularIndicator) LayoutInflater.from(parent.getContext())
128 | .inflate(R.layout.mdtp_year_label_text_view, parent, false);
129 | v.setAccentColor(mController.getAccentColor(), mController.isThemeDark());
130 | }
131 | int year = mMinYear + position + BUDDHIST_OFFSET; // Thai calendar edit
132 | boolean selected = mController.getSelectedDay().year == year;
133 | v.setText(String.valueOf(year));
134 | v.drawIndicator(selected);
135 | v.requestLayout();
136 | if (selected) {
137 | mSelectedView = v;
138 | }
139 | return v;
140 | }
141 | }
142 |
143 | public void postSetSelectionCentered(final int position) {
144 | postSetSelectionFromTop(position, mViewSize / 2 - mChildSize / 2);
145 | }
146 |
147 | public void postSetSelectionFromTop(final int position, final int offset) {
148 | post(new Runnable() {
149 |
150 | @Override
151 | public void run() {
152 | setSelectionFromTop(position, offset);
153 | requestLayout();
154 | }
155 | });
156 | }
157 |
158 | public int getFirstPositionOffset() {
159 | final View firstChild = getChildAt(0);
160 | if (firstChild == null) {
161 | return 0;
162 | }
163 | return firstChild.getTop();
164 | }
165 |
166 | @Override
167 | public void onDateChanged() {
168 | mAdapter.notifyDataSetChanged();
169 | postSetSelectionCentered(mController.getSelectedDay().year - mController.getMinYear());
170 | }
171 |
172 | @Override
173 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
174 | super.onInitializeAccessibilityEvent(event);
175 | if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
176 | event.setFromIndex(0);
177 | event.setToIndex(0);
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/time/AmPmCirclesView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.time;
18 |
19 | import android.content.Context;
20 | import android.content.res.Resources;
21 | import android.graphics.Canvas;
22 | import android.graphics.Paint;
23 | import android.graphics.Typeface;
24 | import android.graphics.Paint.Align;
25 | import android.support.v4.content.ContextCompat;
26 | import android.util.Log;
27 | import android.view.View;
28 |
29 | import com.layernet.thaidatetimepicker.R;
30 | import com.layernet.thaidatetimepicker.Utils;
31 |
32 | import java.text.DateFormatSymbols;
33 |
34 | /**
35 | * Draw the two smaller AM and PM circles next to where the larger circle will be.
36 | */
37 | public class AmPmCirclesView extends View {
38 | private static final String TAG = "AmPmCirclesView";
39 |
40 | // Alpha level for selected circle.
41 | private static final int SELECTED_ALPHA = Utils.SELECTED_ALPHA;
42 | private static final int SELECTED_ALPHA_THEME_DARK = Utils.SELECTED_ALPHA_THEME_DARK;
43 |
44 | private final Paint mPaint = new Paint();
45 | private int mSelectedAlpha;
46 | private int mTouchedColor;
47 | private int mUnselectedColor;
48 | private int mAmPmTextColor;
49 | private int mAmPmSelectedTextColor;
50 | private int mAmPmDisabledTextColor;
51 | private int mSelectedColor;
52 | private float mCircleRadiusMultiplier;
53 | private float mAmPmCircleRadiusMultiplier;
54 | private String mAmText;
55 | private String mPmText;
56 | private boolean mAmDisabled;
57 | private boolean mPmDisabled;
58 | private boolean mIsInitialized;
59 |
60 | private static final int AM = TimePickerDialog.AM;
61 | private static final int PM = TimePickerDialog.PM;
62 |
63 | private boolean mDrawValuesReady;
64 | private int mAmPmCircleRadius;
65 | private int mAmXCenter;
66 | private int mPmXCenter;
67 | private int mAmPmYCenter;
68 | private int mAmOrPm;
69 | private int mAmOrPmPressed;
70 |
71 | public AmPmCirclesView(Context context) {
72 | super(context);
73 | mIsInitialized = false;
74 | }
75 |
76 | public void initialize(Context context, TimePickerController controller, int amOrPm) {
77 | if (mIsInitialized) {
78 | Log.e(TAG, "AmPmCirclesView may only be initialized once.");
79 | return;
80 | }
81 |
82 | Resources res = context.getResources();
83 |
84 | if (controller.isThemeDark()) {
85 | mUnselectedColor = ContextCompat.getColor(context, R.color.mdtp_circle_background_dark_theme);
86 | mAmPmTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
87 | mAmPmDisabledTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled_dark_theme);
88 | mSelectedAlpha = SELECTED_ALPHA_THEME_DARK;
89 | } else {
90 | mUnselectedColor = ContextCompat.getColor(context, R.color.mdtp_white);
91 | mAmPmTextColor = ContextCompat.getColor(context, R.color.mdtp_ampm_text_color);
92 | mAmPmDisabledTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled);
93 | mSelectedAlpha = SELECTED_ALPHA;
94 | }
95 |
96 | mSelectedColor = controller.getAccentColor();
97 | mTouchedColor = Utils.darkenColor(mSelectedColor);
98 | mAmPmSelectedTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
99 |
100 | String typefaceFamily = res.getString(R.string.mdtp_sans_serif);
101 | Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL);
102 | mPaint.setTypeface(tf);
103 | mPaint.setAntiAlias(true);
104 | mPaint.setTextAlign(Align.CENTER);
105 |
106 | mCircleRadiusMultiplier =
107 | Float.parseFloat(res.getString(R.string.mdtp_circle_radius_multiplier));
108 | mAmPmCircleRadiusMultiplier =
109 | Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
110 | String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
111 | mAmText = amPmTexts[0];
112 | mPmText = amPmTexts[1];
113 |
114 | mAmDisabled = controller.isAmDisabled();
115 | mPmDisabled = controller.isPmDisabled();
116 |
117 | setAmOrPm(amOrPm);
118 | mAmOrPmPressed = -1;
119 |
120 | mIsInitialized = true;
121 | }
122 |
123 | public void setAmOrPm(int amOrPm) {
124 | mAmOrPm = amOrPm;
125 | }
126 |
127 | public void setAmOrPmPressed(int amOrPmPressed) {
128 | mAmOrPmPressed = amOrPmPressed;
129 | }
130 |
131 | /**
132 | * Calculate whether the coordinates are touching the AM or PM circle.
133 | */
134 | public int getIsTouchingAmOrPm(float xCoord, float yCoord) {
135 | if (!mDrawValuesReady) {
136 | return -1;
137 | }
138 |
139 | int squaredYDistance = (int) ((yCoord - mAmPmYCenter)*(yCoord - mAmPmYCenter));
140 |
141 | int distanceToAmCenter =
142 | (int) Math.sqrt((xCoord - mAmXCenter)*(xCoord - mAmXCenter) + squaredYDistance);
143 | if (distanceToAmCenter <= mAmPmCircleRadius && !mAmDisabled) {
144 | return AM;
145 | }
146 |
147 | int distanceToPmCenter =
148 | (int) Math.sqrt((xCoord - mPmXCenter)*(xCoord - mPmXCenter) + squaredYDistance);
149 | if (distanceToPmCenter <= mAmPmCircleRadius && !mPmDisabled) {
150 | return PM;
151 | }
152 |
153 | // Neither was close enough.
154 | return -1;
155 | }
156 |
157 | @Override
158 | public void onDraw(Canvas canvas) {
159 | int viewWidth = getWidth();
160 | if (viewWidth == 0 || !mIsInitialized) {
161 | return;
162 | }
163 |
164 | if (!mDrawValuesReady) {
165 | int layoutXCenter = getWidth() / 2;
166 | int layoutYCenter = getHeight() / 2;
167 | int circleRadius =
168 | (int) (Math.min(layoutXCenter, layoutYCenter) * mCircleRadiusMultiplier);
169 | mAmPmCircleRadius = (int) (circleRadius * mAmPmCircleRadiusMultiplier);
170 | layoutYCenter += mAmPmCircleRadius*0.75;
171 | int textSize = mAmPmCircleRadius * 3 / 4;
172 | mPaint.setTextSize(textSize);
173 |
174 | // Line up the vertical center of the AM/PM circles with the bottom of the main circle.
175 | mAmPmYCenter = layoutYCenter - mAmPmCircleRadius / 2 + circleRadius;
176 | // Line up the horizontal edges of the AM/PM circles with the horizontal edges
177 | // of the main circle.
178 | mAmXCenter = layoutXCenter - circleRadius + mAmPmCircleRadius;
179 | mPmXCenter = layoutXCenter + circleRadius - mAmPmCircleRadius;
180 |
181 | mDrawValuesReady = true;
182 | }
183 |
184 | // We'll need to draw either a lighter blue (for selection), a darker blue (for touching)
185 | // or white (for not selected).
186 | int amColor = mUnselectedColor;
187 | int amAlpha = 255;
188 | int amTextColor = mAmPmTextColor;
189 | int pmColor = mUnselectedColor;
190 | int pmAlpha = 255;
191 | int pmTextColor = mAmPmTextColor;
192 |
193 | if (mAmOrPm == AM) {
194 | amColor = mSelectedColor;
195 | amAlpha = mSelectedAlpha;
196 | amTextColor = mAmPmSelectedTextColor;
197 | } else if (mAmOrPm == PM) {
198 | pmColor = mSelectedColor;
199 | pmAlpha = mSelectedAlpha;
200 | pmTextColor = mAmPmSelectedTextColor;
201 | }
202 | if (mAmOrPmPressed == AM) {
203 | amColor = mTouchedColor;
204 | amAlpha = mSelectedAlpha;
205 | } else if (mAmOrPmPressed == PM) {
206 | pmColor = mTouchedColor;
207 | pmAlpha = mSelectedAlpha;
208 | }
209 | if (mAmDisabled) {
210 | amColor = mUnselectedColor;
211 | amTextColor = mAmPmDisabledTextColor;
212 | }
213 | if (mPmDisabled) {
214 | pmColor = mUnselectedColor;
215 | pmTextColor = mAmPmDisabledTextColor;
216 | }
217 |
218 | // Draw the two circles.
219 | mPaint.setColor(amColor);
220 | mPaint.setAlpha(amAlpha);
221 | canvas.drawCircle(mAmXCenter, mAmPmYCenter, mAmPmCircleRadius, mPaint);
222 | mPaint.setColor(pmColor);
223 | mPaint.setAlpha(pmAlpha);
224 | canvas.drawCircle(mPmXCenter, mAmPmYCenter, mAmPmCircleRadius, mPaint);
225 |
226 | // Draw the AM/PM texts on top.
227 | mPaint.setColor(amTextColor);
228 | int textYCenter = mAmPmYCenter - (int) (mPaint.descent() + mPaint.ascent()) / 2;
229 | canvas.drawText(mAmText, mAmXCenter, textYCenter, mPaint);
230 | mPaint.setColor(pmTextColor);
231 | canvas.drawText(mPmText, mPmXCenter, textYCenter, mPaint);
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/time/CircleView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.time;
18 |
19 | import android.content.Context;
20 | import android.content.res.Resources;
21 | import android.graphics.Canvas;
22 | import android.graphics.Paint;
23 | import android.support.v4.content.ContextCompat;
24 | import android.util.Log;
25 | import android.view.View;
26 |
27 | import com.layernet.thaidatetimepicker.R;
28 |
29 | /**
30 | * Draws a simple white circle on which the numbers will be drawn.
31 | */
32 | public class CircleView extends View {
33 | private static final String TAG = "CircleView";
34 |
35 | private final Paint mPaint = new Paint();
36 | private boolean mIs24HourMode;
37 | private int mCircleColor;
38 | private int mDotColor;
39 | private float mCircleRadiusMultiplier;
40 | private float mAmPmCircleRadiusMultiplier;
41 | private boolean mIsInitialized;
42 |
43 | private boolean mDrawValuesReady;
44 | private int mXCenter;
45 | private int mYCenter;
46 | private int mCircleRadius;
47 |
48 | public CircleView(Context context) {
49 | super(context);
50 |
51 | mIsInitialized = false;
52 | }
53 |
54 | public void initialize(Context context, TimePickerController controller) {
55 | if (mIsInitialized) {
56 | Log.e(TAG, "CircleView may only be initialized once.");
57 | return;
58 | }
59 |
60 | Resources res = context.getResources();
61 |
62 | int colorRes = controller.isThemeDark() ? R.color.mdtp_circle_background_dark_theme : R.color.mdtp_circle_color;
63 | mCircleColor = ContextCompat.getColor(context, colorRes);
64 | mDotColor = controller.getAccentColor();
65 | mPaint.setAntiAlias(true);
66 |
67 | mIs24HourMode = controller.is24HourMode();
68 | if (mIs24HourMode) {
69 | mCircleRadiusMultiplier = Float.parseFloat(
70 | res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode));
71 | } else {
72 | mCircleRadiusMultiplier = Float.parseFloat(
73 | res.getString(R.string.mdtp_circle_radius_multiplier));
74 | mAmPmCircleRadiusMultiplier =
75 | Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
76 | }
77 |
78 | mIsInitialized = true;
79 | }
80 |
81 | @Override
82 | public void onDraw(Canvas canvas) {
83 | int viewWidth = getWidth();
84 | if (viewWidth == 0 || !mIsInitialized) {
85 | return;
86 | }
87 |
88 | if (!mDrawValuesReady) {
89 | mXCenter = getWidth() / 2;
90 | mYCenter = getHeight() / 2;
91 | mCircleRadius = (int) (Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier);
92 |
93 | if (!mIs24HourMode) {
94 | // We'll need to draw the AM/PM circles, so the main circle will need to have
95 | // a slightly higher center. To keep the entire view centered vertically, we'll
96 | // have to push it up by half the radius of the AM/PM circles.
97 | int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier);
98 | mYCenter -= amPmCircleRadius*0.75;
99 | }
100 |
101 | mDrawValuesReady = true;
102 | }
103 |
104 | // Draw the white circle.
105 | mPaint.setColor(mCircleColor);
106 | canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaint);
107 |
108 | // Draw a small black circle in the center.
109 | mPaint.setColor(mDotColor);
110 | canvas.drawCircle(mXCenter, mYCenter, 8, mPaint);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/time/RadialSelectorView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.time;
18 |
19 | import android.animation.Keyframe;
20 | import android.animation.ObjectAnimator;
21 | import android.animation.PropertyValuesHolder;
22 | import android.animation.ValueAnimator;
23 | import android.animation.ValueAnimator.AnimatorUpdateListener;
24 | import android.content.Context;
25 | import android.content.res.Resources;
26 | import android.graphics.Canvas;
27 | import android.graphics.Paint;
28 | import android.util.Log;
29 | import android.view.View;
30 |
31 | import com.layernet.thaidatetimepicker.R;
32 | import com.layernet.thaidatetimepicker.Utils;
33 |
34 | /**
35 | * View to show what number is selected. This will draw a blue circle over the number, with a blue
36 | * line coming from the center of the main circle to the edge of the blue selection.
37 | */
38 | public class RadialSelectorView extends View {
39 | private static final String TAG = "RadialSelectorView";
40 |
41 | // Alpha level for selected circle.
42 | private static final int SELECTED_ALPHA = Utils.SELECTED_ALPHA;
43 | private static final int SELECTED_ALPHA_THEME_DARK = Utils.SELECTED_ALPHA_THEME_DARK;
44 | // Alpha level for the line.
45 | private static final int FULL_ALPHA = Utils.FULL_ALPHA;
46 |
47 | private final Paint mPaint = new Paint();
48 |
49 | private boolean mIsInitialized;
50 | private boolean mDrawValuesReady;
51 |
52 | private float mCircleRadiusMultiplier;
53 | private float mAmPmCircleRadiusMultiplier;
54 | private float mInnerNumbersRadiusMultiplier;
55 | private float mOuterNumbersRadiusMultiplier;
56 | private float mNumbersRadiusMultiplier;
57 | private float mSelectionRadiusMultiplier;
58 | private float mAnimationRadiusMultiplier;
59 | private boolean mIs24HourMode;
60 | private boolean mHasInnerCircle;
61 | private int mSelectionAlpha;
62 |
63 | private int mXCenter;
64 | private int mYCenter;
65 | private int mCircleRadius;
66 | private float mTransitionMidRadiusMultiplier;
67 | private float mTransitionEndRadiusMultiplier;
68 | private int mLineLength;
69 | private int mSelectionRadius;
70 | private InvalidateUpdateListener mInvalidateUpdateListener;
71 |
72 | private int mSelectionDegrees;
73 | private double mSelectionRadians;
74 | private boolean mForceDrawDot;
75 |
76 | public RadialSelectorView(Context context) {
77 | super(context);
78 | mIsInitialized = false;
79 | }
80 |
81 | /**
82 | * Initialize this selector with the state of the picker.
83 | * @param context Current context.
84 | * @param controller Structure containing the accentColor and the 24-hour mode, which will tell us
85 | * whether the circle's center is moved up slightly to make room for the AM/PM circles.
86 | * @param hasInnerCircle Whether we have both an inner and an outer circle of numbers
87 | * that may be selected. Should be true for 24-hour mode in the hours circle.
88 | * @param disappearsOut Whether the numbers' animation will have them disappearing out
89 | * or disappearing in.
90 | * @param selectionDegrees The initial degrees to be selected.
91 | * @param isInnerCircle Whether the initial selection is in the inner or outer circle.
92 | * Will be ignored when hasInnerCircle is false.
93 | */
94 | public void initialize(Context context, TimePickerController controller, boolean hasInnerCircle,
95 | boolean disappearsOut, int selectionDegrees, boolean isInnerCircle) {
96 | if (mIsInitialized) {
97 | Log.e(TAG, "This RadialSelectorView may only be initialized once.");
98 | return;
99 | }
100 |
101 | Resources res = context.getResources();
102 |
103 | int accentColor = controller.getAccentColor();
104 | mPaint.setColor(accentColor);
105 | mPaint.setAntiAlias(true);
106 |
107 | mSelectionAlpha = controller.isThemeDark() ? SELECTED_ALPHA_THEME_DARK : SELECTED_ALPHA;
108 |
109 | // Calculate values for the circle radius size.
110 | mIs24HourMode = controller.is24HourMode();
111 | if (mIs24HourMode) {
112 | mCircleRadiusMultiplier = Float.parseFloat(
113 | res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode));
114 | } else {
115 | mCircleRadiusMultiplier = Float.parseFloat(
116 | res.getString(R.string.mdtp_circle_radius_multiplier));
117 | mAmPmCircleRadiusMultiplier =
118 | Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
119 | }
120 |
121 | // Calculate values for the radius size(s) of the numbers circle(s).
122 | mHasInnerCircle = hasInnerCircle;
123 | if (hasInnerCircle) {
124 | mInnerNumbersRadiusMultiplier =
125 | Float.parseFloat(res.getString(R.string.mdtp_numbers_radius_multiplier_inner));
126 | mOuterNumbersRadiusMultiplier =
127 | Float.parseFloat(res.getString(R.string.mdtp_numbers_radius_multiplier_outer));
128 | } else {
129 | mNumbersRadiusMultiplier =
130 | Float.parseFloat(res.getString(R.string.mdtp_numbers_radius_multiplier_normal));
131 | }
132 | mSelectionRadiusMultiplier =
133 | Float.parseFloat(res.getString(R.string.mdtp_selection_radius_multiplier));
134 |
135 | // Calculate values for the transition mid-way states.
136 | mAnimationRadiusMultiplier = 1;
137 | mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
138 | mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
139 | mInvalidateUpdateListener = new InvalidateUpdateListener();
140 |
141 | setSelection(selectionDegrees, isInnerCircle, false);
142 | mIsInitialized = true;
143 | }
144 |
145 | /**
146 | * Set the selection.
147 | * @param selectionDegrees The degrees to be selected.
148 | * @param isInnerCircle Whether the selection should be in the inner circle or outer. Will be
149 | * ignored if hasInnerCircle was initialized to false.
150 | * @param forceDrawDot Whether to force the dot in the center of the selection circle to be
151 | * drawn. If false, the dot will be drawn only when the degrees is not a multiple of 30, i.e.
152 | * the selection is not on a visible number.
153 | */
154 | public void setSelection(int selectionDegrees, boolean isInnerCircle, boolean forceDrawDot) {
155 | mSelectionDegrees = selectionDegrees;
156 | mSelectionRadians = selectionDegrees * Math.PI / 180;
157 | mForceDrawDot = forceDrawDot;
158 |
159 | if (mHasInnerCircle) {
160 | if (isInnerCircle) {
161 | mNumbersRadiusMultiplier = mInnerNumbersRadiusMultiplier;
162 | } else {
163 | mNumbersRadiusMultiplier = mOuterNumbersRadiusMultiplier;
164 | }
165 | }
166 | }
167 |
168 | /**
169 | * Allows for smoother animations.
170 | */
171 | @Override
172 | public boolean hasOverlappingRendering() {
173 | return false;
174 | }
175 |
176 | /**
177 | * Set the multiplier for the radius. Will be used during animations to move in/out.
178 | */
179 | public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
180 | mAnimationRadiusMultiplier = animationRadiusMultiplier;
181 | }
182 |
183 | public int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
184 | final Boolean[] isInnerCircle) {
185 | if (!mDrawValuesReady) {
186 | return -1;
187 | }
188 |
189 | double hypotenuse = Math.sqrt(
190 | (pointY - mYCenter)*(pointY - mYCenter) +
191 | (pointX - mXCenter)*(pointX - mXCenter));
192 | // Check if we're outside the range
193 | if (mHasInnerCircle) {
194 | if (forceLegal) {
195 | // If we're told to force the coordinates to be legal, we'll set the isInnerCircle
196 | // boolean based based off whichever number the coordinates are closer to.
197 | int innerNumberRadius = (int) (mCircleRadius * mInnerNumbersRadiusMultiplier);
198 | int distanceToInnerNumber = (int) Math.abs(hypotenuse - innerNumberRadius);
199 | int outerNumberRadius = (int) (mCircleRadius * mOuterNumbersRadiusMultiplier);
200 | int distanceToOuterNumber = (int) Math.abs(hypotenuse - outerNumberRadius);
201 |
202 | isInnerCircle[0] = (distanceToInnerNumber <= distanceToOuterNumber);
203 | } else {
204 | // Otherwise, if we're close enough to either number (with the space between the
205 | // two allotted equally), set the isInnerCircle boolean as the closer one.
206 | // appropriately, but otherwise return -1.
207 | int minAllowedHypotenuseForInnerNumber =
208 | (int) (mCircleRadius * mInnerNumbersRadiusMultiplier) - mSelectionRadius;
209 | int maxAllowedHypotenuseForOuterNumber =
210 | (int) (mCircleRadius * mOuterNumbersRadiusMultiplier) + mSelectionRadius;
211 | int halfwayHypotenusePoint = (int) (mCircleRadius *
212 | ((mOuterNumbersRadiusMultiplier + mInnerNumbersRadiusMultiplier) / 2));
213 |
214 | if (hypotenuse >= minAllowedHypotenuseForInnerNumber &&
215 | hypotenuse <= halfwayHypotenusePoint) {
216 | isInnerCircle[0] = true;
217 | } else if (hypotenuse <= maxAllowedHypotenuseForOuterNumber &&
218 | hypotenuse >= halfwayHypotenusePoint) {
219 | isInnerCircle[0] = false;
220 | } else {
221 | return -1;
222 | }
223 | }
224 | } else {
225 | // If there's just one circle, we'll need to return -1 if:
226 | // we're not told to force the coordinates to be legal, and
227 | // the coordinates' distance to the number is within the allowed distance.
228 | if (!forceLegal) {
229 | int distanceToNumber = (int) Math.abs(hypotenuse - mLineLength);
230 | // The max allowed distance will be defined as the distance from the center of the
231 | // number to the edge of the circle.
232 | int maxAllowedDistance = (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier));
233 | if (distanceToNumber > maxAllowedDistance) {
234 | return -1;
235 | }
236 | }
237 | }
238 |
239 |
240 | float opposite = Math.abs(pointY - mYCenter);
241 | double radians = Math.asin(opposite / hypotenuse);
242 | int degrees = (int) (radians * 180 / Math.PI);
243 |
244 | // Now we have to translate to the correct quadrant.
245 | boolean rightSide = (pointX > mXCenter);
246 | boolean topSide = (pointY < mYCenter);
247 | if (rightSide && topSide) {
248 | degrees = 90 - degrees;
249 | } else if (rightSide && !topSide) {
250 | degrees = 90 + degrees;
251 | } else if (!rightSide && !topSide) {
252 | degrees = 270 - degrees;
253 | } else if (!rightSide && topSide) {
254 | degrees = 270 + degrees;
255 | }
256 | return degrees;
257 | }
258 |
259 | @Override
260 | public void onDraw(Canvas canvas) {
261 | int viewWidth = getWidth();
262 | if (viewWidth == 0 || !mIsInitialized) {
263 | return;
264 | }
265 |
266 | if (!mDrawValuesReady) {
267 | mXCenter = getWidth() / 2;
268 | mYCenter = getHeight() / 2;
269 | mCircleRadius = (int) (Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier);
270 |
271 | if (!mIs24HourMode) {
272 | // We'll need to draw the AM/PM circles, so the main circle will need to have
273 | // a slightly higher center. To keep the entire view centered vertically, we'll
274 | // have to push it up by half the radius of the AM/PM circles.
275 | int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier);
276 | mYCenter -= amPmCircleRadius *0.75;
277 | }
278 |
279 | mSelectionRadius = (int) (mCircleRadius * mSelectionRadiusMultiplier);
280 |
281 | mDrawValuesReady = true;
282 | }
283 |
284 | // Calculate the current radius at which to place the selection circle.
285 | mLineLength = (int) (mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier);
286 | int pointX = mXCenter + (int) (mLineLength * Math.sin(mSelectionRadians));
287 | int pointY = mYCenter - (int) (mLineLength * Math.cos(mSelectionRadians));
288 |
289 | // Draw the selection circle.
290 | mPaint.setAlpha(mSelectionAlpha);
291 | canvas.drawCircle(pointX, pointY, mSelectionRadius, mPaint);
292 |
293 | if (mForceDrawDot | mSelectionDegrees % 30 != 0) {
294 | // We're not on a direct tick (or we've been told to draw the dot anyway).
295 | mPaint.setAlpha(FULL_ALPHA);
296 | canvas.drawCircle(pointX, pointY, (mSelectionRadius * 2 / 7), mPaint);
297 | } else {
298 | // We're not drawing the dot, so shorten the line to only go as far as the edge of the
299 | // selection circle.
300 | int lineLength = mLineLength;
301 | lineLength -= mSelectionRadius;
302 | pointX = mXCenter + (int) (lineLength * Math.sin(mSelectionRadians));
303 | pointY = mYCenter - (int) (lineLength * Math.cos(mSelectionRadians));
304 | }
305 |
306 | // Draw the line from the center of the circle.
307 | mPaint.setAlpha(255);
308 | mPaint.setStrokeWidth(3);
309 | canvas.drawLine(mXCenter, mYCenter, pointX, pointY, mPaint);
310 | }
311 |
312 | public ObjectAnimator getDisappearAnimator() {
313 | if (!mIsInitialized || !mDrawValuesReady) {
314 | Log.e(TAG, "RadialSelectorView was not ready for animation.");
315 | return null;
316 | }
317 |
318 | Keyframe kf0, kf1, kf2;
319 | float midwayPoint = 0.2f;
320 | int duration = 500;
321 |
322 | kf0 = Keyframe.ofFloat(0f, 1);
323 | kf1 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
324 | kf2 = Keyframe.ofFloat(1f, mTransitionEndRadiusMultiplier);
325 | PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
326 | "animationRadiusMultiplier", kf0, kf1, kf2);
327 |
328 | kf0 = Keyframe.ofFloat(0f, 1f);
329 | kf1 = Keyframe.ofFloat(1f, 0f);
330 | PropertyValuesHolder fadeOut = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1);
331 |
332 | ObjectAnimator disappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
333 | this, radiusDisappear, fadeOut).setDuration(duration);
334 | disappearAnimator.addUpdateListener(mInvalidateUpdateListener);
335 |
336 | return disappearAnimator;
337 | }
338 |
339 | public ObjectAnimator getReappearAnimator() {
340 | if (!mIsInitialized || !mDrawValuesReady) {
341 | Log.e(TAG, "RadialSelectorView was not ready for animation.");
342 | return null;
343 | }
344 |
345 | Keyframe kf0, kf1, kf2, kf3;
346 | float midwayPoint = 0.2f;
347 | int duration = 500;
348 |
349 | // The time points are half of what they would normally be, because this animation is
350 | // staggered against the disappear so they happen seamlessly. The reappear starts
351 | // halfway into the disappear.
352 | float delayMultiplier = 0.25f;
353 | float transitionDurationMultiplier = 1f;
354 | float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
355 | int totalDuration = (int) (duration * totalDurationMultiplier);
356 | float delayPoint = (delayMultiplier * duration) / totalDuration;
357 | midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
358 |
359 | kf0 = Keyframe.ofFloat(0f, mTransitionEndRadiusMultiplier);
360 | kf1 = Keyframe.ofFloat(delayPoint, mTransitionEndRadiusMultiplier);
361 | kf2 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
362 | kf3 = Keyframe.ofFloat(1f, 1);
363 | PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
364 | "animationRadiusMultiplier", kf0, kf1, kf2, kf3);
365 |
366 | kf0 = Keyframe.ofFloat(0f, 0f);
367 | kf1 = Keyframe.ofFloat(delayPoint, 0f);
368 | kf2 = Keyframe.ofFloat(1f, 1f);
369 | PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
370 |
371 | ObjectAnimator reappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
372 | this, radiusReappear, fadeIn).setDuration(totalDuration);
373 | reappearAnimator.addUpdateListener(mInvalidateUpdateListener);
374 | return reappearAnimator;
375 | }
376 |
377 | /**
378 | * We'll need to invalidate during the animation.
379 | */
380 | private class InvalidateUpdateListener implements AnimatorUpdateListener {
381 | @Override
382 | public void onAnimationUpdate(ValueAnimator animation) {
383 | RadialSelectorView.this.invalidate();
384 | }
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/thai-datetimepicker/src/main/java/com/layernet/thaidatetimepicker/time/RadialTextsView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 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 |
17 | package com.layernet.thaidatetimepicker.time;
18 |
19 | import android.animation.Keyframe;
20 | import android.animation.ObjectAnimator;
21 | import android.animation.PropertyValuesHolder;
22 | import android.animation.ValueAnimator;
23 | import android.animation.ValueAnimator.AnimatorUpdateListener;
24 | import android.content.Context;
25 | import android.content.res.Resources;
26 | import android.graphics.Canvas;
27 | import android.graphics.Paint;
28 | import android.graphics.Typeface;
29 | import android.graphics.Paint.Align;
30 | import android.support.v4.content.ContextCompat;
31 | import android.util.Log;
32 | import android.view.View;
33 |
34 | import com.layernet.thaidatetimepicker.R;
35 |
36 | /**
37 | * A view to show a series of numbers in a circular pattern.
38 | */
39 | public class RadialTextsView extends View {
40 | private final static String TAG = "RadialTextsView";
41 |
42 | private final Paint mPaint = new Paint();
43 | private final Paint mSelectedPaint = new Paint();
44 | private final Paint mInactivePaint = new Paint();
45 |
46 | private boolean mDrawValuesReady;
47 | private boolean mIsInitialized;
48 |
49 | private int selection = -1;
50 |
51 | private SelectionValidator mValidator;
52 |
53 | private Typeface mTypefaceLight;
54 | private Typeface mTypefaceRegular;
55 | private String[] mTexts;
56 | private String[] mInnerTexts;
57 | private boolean mIs24HourMode;
58 | private boolean mHasInnerCircle;
59 | private float mCircleRadiusMultiplier;
60 | private float mAmPmCircleRadiusMultiplier;
61 | private float mNumbersRadiusMultiplier;
62 | private float mInnerNumbersRadiusMultiplier;
63 | private float mTextSizeMultiplier;
64 | private float mInnerTextSizeMultiplier;
65 |
66 | private int mXCenter;
67 | private int mYCenter;
68 | private float mCircleRadius;
69 | private boolean mTextGridValuesDirty;
70 | private float mTextSize;
71 | private float mInnerTextSize;
72 | private float[] mTextGridHeights;
73 | private float[] mTextGridWidths;
74 | private float[] mInnerTextGridHeights;
75 | private float[] mInnerTextGridWidths;
76 |
77 | private float mAnimationRadiusMultiplier;
78 | private float mTransitionMidRadiusMultiplier;
79 | private float mTransitionEndRadiusMultiplier;
80 | ObjectAnimator mDisappearAnimator;
81 | ObjectAnimator mReappearAnimator;
82 | private InvalidateUpdateListener mInvalidateUpdateListener;
83 |
84 | public RadialTextsView(Context context) {
85 | super(context);
86 | mIsInitialized = false;
87 | }
88 |
89 | public void initialize(Context context, String[] texts, String[] innerTexts,
90 | TimePickerController controller, SelectionValidator validator, boolean disappearsOut) {
91 | if (mIsInitialized) {
92 | Log.e(TAG, "This RadialTextsView may only be initialized once.");
93 | return;
94 | }
95 | Resources res = context.getResources();
96 |
97 | // Set up the paint.
98 | int textColorRes = controller.isThemeDark() ? R.color.mdtp_white : R.color.mdtp_numbers_text_color;
99 | mPaint.setColor(ContextCompat.getColor(context, textColorRes));
100 | String typefaceFamily = res.getString(R.string.mdtp_radial_numbers_typeface);
101 | mTypefaceLight = Typeface.create(typefaceFamily, Typeface.NORMAL);
102 | String typefaceFamilyRegular = res.getString(R.string.mdtp_sans_serif);
103 | mTypefaceRegular = Typeface.create(typefaceFamilyRegular, Typeface.NORMAL);
104 | mPaint.setAntiAlias(true);
105 | mPaint.setTextAlign(Align.CENTER);
106 |
107 | // Set up the selected paint
108 | int selectedTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
109 | mSelectedPaint.setColor(selectedTextColor);
110 | mSelectedPaint.setAntiAlias(true);
111 | mSelectedPaint.setTextAlign(Align.CENTER);
112 |
113 | // Set up the inactive paint
114 | int inactiveColorRes = controller.isThemeDark() ? R.color.mdtp_date_picker_text_disabled_dark_theme
115 | : R.color.mdtp_date_picker_text_disabled;
116 | mInactivePaint.setColor(ContextCompat.getColor(context, inactiveColorRes));
117 | mInactivePaint.setAntiAlias(true);
118 | mInactivePaint.setTextAlign(Align.CENTER);
119 |
120 | mTexts = texts;
121 | mInnerTexts = innerTexts;
122 | mIs24HourMode = controller.is24HourMode();
123 | mHasInnerCircle = (innerTexts != null);
124 |
125 | // Calculate the radius for the main circle.
126 | if (mIs24HourMode) {
127 | mCircleRadiusMultiplier = Float.parseFloat(
128 | res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode));
129 | } else {
130 | mCircleRadiusMultiplier = Float.parseFloat(
131 | res.getString(R.string.mdtp_circle_radius_multiplier));
132 | mAmPmCircleRadiusMultiplier =
133 | Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
134 | }
135 |
136 | // Initialize the widths and heights of the grid, and calculate the values for the numbers.
137 | mTextGridHeights = new float[7];
138 | mTextGridWidths = new float[7];
139 | if (mHasInnerCircle) {
140 | mNumbersRadiusMultiplier = Float.parseFloat(
141 | res.getString(R.string.mdtp_numbers_radius_multiplier_outer));
142 | mTextSizeMultiplier = Float.parseFloat(
143 | res.getString(R.string.mdtp_text_size_multiplier_outer));
144 | mInnerNumbersRadiusMultiplier = Float.parseFloat(
145 | res.getString(R.string.mdtp_numbers_radius_multiplier_inner));
146 | mInnerTextSizeMultiplier = Float.parseFloat(
147 | res.getString(R.string.mdtp_text_size_multiplier_inner));
148 |
149 | mInnerTextGridHeights = new float[7];
150 | mInnerTextGridWidths = new float[7];
151 | } else {
152 | mNumbersRadiusMultiplier = Float.parseFloat(
153 | res.getString(R.string.mdtp_numbers_radius_multiplier_normal));
154 | mTextSizeMultiplier = Float.parseFloat(
155 | res.getString(R.string.mdtp_text_size_multiplier_normal));
156 | }
157 |
158 | mAnimationRadiusMultiplier = 1;
159 | mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
160 | mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
161 | mInvalidateUpdateListener = new InvalidateUpdateListener();
162 |
163 | mValidator = validator;
164 |
165 | mTextGridValuesDirty = true;
166 | mIsInitialized = true;
167 | }
168 |
169 | /**
170 | * Set the value of the selected text. Depending on the theme this will be rendered differently
171 | * @param selection The text which is currently selected
172 | */
173 | protected void setSelection(int selection) {
174 | this.selection = selection;
175 | }
176 |
177 | /**
178 | * Allows for smoother animation.
179 | */
180 | @Override
181 | public boolean hasOverlappingRendering() {
182 | return false;
183 | }
184 |
185 | /**
186 | * Used by the animation to move the numbers in and out.
187 | */
188 | @SuppressWarnings("unused")
189 | public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
190 | mAnimationRadiusMultiplier = animationRadiusMultiplier;
191 | mTextGridValuesDirty = true;
192 | }
193 |
194 | @Override
195 | public void onDraw(Canvas canvas) {
196 | int viewWidth = getWidth();
197 | if (viewWidth == 0 || !mIsInitialized) {
198 | return;
199 | }
200 |
201 | if (!mDrawValuesReady) {
202 | mXCenter = getWidth() / 2;
203 | mYCenter = getHeight() / 2;
204 | mCircleRadius = Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier;
205 | if (!mIs24HourMode) {
206 | // We'll need to draw the AM/PM circles, so the main circle will need to have
207 | // a slightly higher center. To keep the entire view centered vertically, we'll
208 | // have to push it up by half the radius of the AM/PM circles.
209 | float amPmCircleRadius = mCircleRadius * mAmPmCircleRadiusMultiplier;
210 | mYCenter -= amPmCircleRadius *0.75;
211 | }
212 |
213 | mTextSize = mCircleRadius * mTextSizeMultiplier;
214 | if (mHasInnerCircle) {
215 | mInnerTextSize = mCircleRadius * mInnerTextSizeMultiplier;
216 | }
217 |
218 | // Because the text positions will be static, pre-render the animations.
219 | renderAnimations();
220 |
221 | mTextGridValuesDirty = true;
222 | mDrawValuesReady = true;
223 | }
224 |
225 | // Calculate the text positions, but only if they've changed since the last onDraw.
226 | if (mTextGridValuesDirty) {
227 | float numbersRadius =
228 | mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier;
229 |
230 | // Calculate the positions for the 12 numbers in the main circle.
231 | calculateGridSizes(numbersRadius, mXCenter, mYCenter,
232 | mTextSize, mTextGridHeights, mTextGridWidths);
233 | if (mHasInnerCircle) {
234 | // If we have an inner circle, calculate those positions too.
235 | float innerNumbersRadius =
236 | mCircleRadius * mInnerNumbersRadiusMultiplier * mAnimationRadiusMultiplier;
237 | calculateGridSizes(innerNumbersRadius, mXCenter, mYCenter,
238 | mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
239 | }
240 | mTextGridValuesDirty = false;
241 | }
242 |
243 | // Draw the texts in the pre-calculated positions.
244 | drawTexts(canvas, mTextSize, mTypefaceLight, mTexts, mTextGridWidths, mTextGridHeights);
245 | if (mHasInnerCircle) {
246 | drawTexts(canvas, mInnerTextSize, mTypefaceRegular, mInnerTexts,
247 | mInnerTextGridWidths, mInnerTextGridHeights);
248 | }
249 | }
250 |
251 | /**
252 | * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
253 | * drawn at based on the specified circle radius. Place the values in the textGridHeights and
254 | * textGridWidths parameters.
255 | */
256 | private void calculateGridSizes(float numbersRadius, float xCenter, float yCenter,
257 | float textSize, float[] textGridHeights, float[] textGridWidths) {
258 | /*
259 | * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
260 | */
261 | float offset1 = numbersRadius;
262 | // cos(30) = a / r => r * cos(30) = a => r * √3/2 = a
263 | float offset2 = numbersRadius * ((float) Math.sqrt(3)) / 2f;
264 | // sin(30) = o / r => r * sin(30) = o => r / 2 = a
265 | float offset3 = numbersRadius / 2f;
266 | mPaint.setTextSize(textSize);
267 | mSelectedPaint.setTextSize(textSize);
268 | mInactivePaint.setTextSize(textSize);
269 | // We'll need yTextBase to be slightly lower to account for the text's baseline.
270 | yCenter -= (mPaint.descent() + mPaint.ascent()) / 2;
271 |
272 | textGridHeights[0] = yCenter - offset1;
273 | textGridWidths[0] = xCenter - offset1;
274 | textGridHeights[1] = yCenter - offset2;
275 | textGridWidths[1] = xCenter - offset2;
276 | textGridHeights[2] = yCenter - offset3;
277 | textGridWidths[2] = xCenter - offset3;
278 | textGridHeights[3] = yCenter;
279 | textGridWidths[3] = xCenter;
280 | textGridHeights[4] = yCenter + offset3;
281 | textGridWidths[4] = xCenter + offset3;
282 | textGridHeights[5] = yCenter + offset2;
283 | textGridWidths[5] = xCenter + offset2;
284 | textGridHeights[6] = yCenter + offset1;
285 | textGridWidths[6] = xCenter + offset1;
286 | }
287 |
288 | private Paint[] assignTextColors(String[] texts) {
289 | Paint[] paints = new Paint[texts.length];
290 | for(int i=0;i {
17 | private int hour;
18 | private int minute;
19 | private int second;
20 |
21 | public enum TYPE {
22 | HOUR,
23 | MINUTE,
24 | SECOND
25 | }
26 |
27 | public Timepoint(Timepoint time) {
28 | this(time.hour, time.minute, time.second);
29 | }
30 |
31 | public Timepoint(@IntRange(from=0, to=23) int hour,
32 | @IntRange(from=0, to=59) int minute,
33 | @IntRange(from=0, to=59) int second) {
34 | this.hour = hour % 24;
35 | this.minute = minute % 60;
36 | this.second = second % 60;
37 | }
38 |
39 | public Timepoint(@IntRange(from=0, to=23) int hour,
40 | @IntRange(from=0, to=59) int minute) {
41 | this(hour, minute, 0);
42 | }
43 |
44 | public Timepoint(@IntRange(from=0, to=23) int hour) {
45 | this(hour, 0);
46 | }
47 |
48 | public Timepoint(Parcel in) {
49 | hour = in.readInt();
50 | minute = in.readInt();
51 | second = in.readInt();
52 | }
53 |
54 | @IntRange(from=0, to=23)
55 | public int getHour() {
56 | return hour;
57 | }
58 |
59 | @IntRange(from=0, to=59)
60 | public int getMinute() {
61 | return minute;
62 | }
63 |
64 | @IntRange(from=0, to=59)
65 | public int getSecond() {
66 | return second;
67 | }
68 |
69 | public boolean isAM() {
70 | return hour < 12;
71 | }
72 |
73 | public boolean isPM() {
74 | return hour >= 12 && hour < 24;
75 | }
76 |
77 | public void setAM() {
78 | if(hour >= 12) hour = hour % 12;
79 | }
80 |
81 | public void setPM() {
82 | if(hour < 12) hour = (hour + 12) % 24;
83 | }
84 |
85 | @Override
86 | public boolean equals(Object o) {
87 | try {
88 | Timepoint other = (Timepoint) o;
89 |
90 | return other.getHour() == hour &&
91 | other.getMinute() == minute &&
92 | other.getSecond() == second;
93 | }
94 | catch(ClassCastException e) {
95 | return false;
96 | }
97 | }
98 |
99 | @Override
100 | public int compareTo(@NonNull Timepoint t) {
101 | return (this.hour - t.hour)*3600 + (this.minute - t.minute)*60 + (this.second - t.second);
102 | }
103 |
104 | @Override
105 | public void writeToParcel(Parcel out, int flags) {
106 | out.writeInt(hour);
107 | out.writeInt(minute);
108 | out.writeInt(second);
109 | }
110 |
111 | @Override
112 | public int describeContents() {
113 | return 0;
114 | }
115 |
116 | public static final Parcelable.Creator CREATOR
117 | = new Parcelable.Creator() {
118 | public Timepoint createFromParcel(Parcel in) {
119 | return new Timepoint(in);
120 | }
121 |
122 | public Timepoint[] newArray(int size) {
123 | return new Timepoint[size];
124 | }
125 | };
126 | }
127 |
--------------------------------------------------------------------------------