├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── build.gradle
├── example
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── solar
│ │ └── blaz
│ │ └── date
│ │ └── week
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── solar
│ │ └── blaz
│ │ └── date
│ │ └── week
│ │ └── MainActivity.java
│ └── res
│ ├── color
│ └── date_picker_text_color.xml
│ ├── drawable
│ ├── date_picker_day_bg.xml
│ └── date_picker_indicator.xml
│ ├── 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
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── example_screenshot.gif
└── example_screenshot.png
├── library
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── solar
│ │ └── blaz
│ │ └── date
│ │ └── week
│ │ └── WeekDatePicker.java
│ └── res
│ └── values
│ └── attrs.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | #!! ERROR: andr is undefined. Use list command to see defined gitignore types !!#
4 |
5 | ### Android ###
6 | # Built application files
7 | *.apk
8 | *.ap_
9 |
10 | # Files for the Dalvik VM
11 | *.dex
12 |
13 | # Java class files
14 | *.class
15 |
16 | # Generated files
17 | bin/
18 | gen/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 |
34 | ### Crashlytics ###
35 | #Crashlytics
36 | crashlytics-build.properties
37 | com_crashlytics_export_strings.xml
38 |
39 |
40 | ### Intellij ###
41 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
42 |
43 | *.iml
44 |
45 | ## Directory-based project format:
46 | .idea/
47 | # if you remove the above rule, at least ignore the following:
48 |
49 | # User-specific stuff:
50 | # .idea/workspace.xml
51 | # .idea/tasks.xml
52 | # .idea/misc.xml
53 | # .idea/dictionaries
54 |
55 | # Sensitive or high-churn files:
56 | # .idea/dataSources.ids
57 | # .idea/dataSources.xml
58 | # .idea/sqlDataSources.xml
59 | # .idea/dynamic.xml
60 | # .idea/uiDesigner.xml
61 |
62 | # Gradle:
63 | # .idea/gradle.xml
64 | # .idea/libraries
65 |
66 | ## File-based project format:
67 | *.ipr
68 | *.iws
69 |
70 | ## Plugin-specific files:
71 |
72 | # IntelliJ
73 | out/
74 |
75 | # mpeltonen/sbt-idea plugin
76 | .idea_modules/
77 |
78 | # JIRA plugin
79 | atlassian-ide-plugin.xml
80 |
81 | # Crashlytics plugin (for Android Studio and IntelliJ)
82 | com_crashlytics_export_strings.xml
83 |
84 |
85 | ### OSX ###
86 | .DS_Store
87 | .AppleDouble
88 | .LSOverride
89 |
90 | # Icon must end with two \r
91 | Icon
92 |
93 |
94 | # Thumbnails
95 | ._*
96 |
97 | # Files that might appear on external disk
98 | .Spotlight-V100
99 | .Trashes
100 |
101 | # Directories potentially created on remote AFP share
102 | .AppleDB
103 | .AppleDesktop
104 | Network Trash Folder
105 | Temporary Items
106 | .apdisk
107 |
108 |
109 | ### Windows ###
110 | # Windows image file caches
111 | Thumbs.db
112 | ehthumbs.db
113 |
114 | # Folder config file
115 | Desktop.ini
116 |
117 | # Recycle Bin used on file shares
118 | $RECYCLE.BIN/
119 |
120 | # Windows Installer files
121 | *.cab
122 | *.msi
123 | *.msm
124 | *.msp
125 |
126 | # Windows shortcuts
127 | *.lnk
128 |
129 |
130 | ### Linux ###
131 | *~
132 |
133 | # KDE directory preferences
134 | .directory
135 |
136 |
137 | ### Gradle ###
138 | .gradle
139 | build/
140 | gradle/*
141 | !gradle/wrapper/*
142 |
143 | # Ignore Gradle GUI config
144 | gradle-app.setting
145 |
146 | # Android gradle captures
147 | captures/
148 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## Version 1.2 *(11.6.2016)*
4 |
5 | * Adding date limitation functionality
6 |
7 | ## Version 1.1 *(21.3.2016)*
8 |
9 | * Prefixing attributes.
10 |
11 | ## Version 1.0.0 *(17.2.2016)*
12 |
13 | * Initial release
14 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | # Contributors to WeekDatePicker project
2 |
3 | ## Creator & Maintainer
4 |
5 | * Blaž Šolar <[blaz.solar](http://blaz.solar)>
6 |
7 | ## Contributors
8 |
9 | In chronological order:
10 |
11 | * [Your name or handle] <[email or website]>
12 | * [Brief summary of your changes]
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Blaž Šolar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WeekDatePicker
2 |
3 | WeekDatePicker is a lightweight implementation of week date picker for Android. It is based on a View instead of ViewGroup. That gives it benefit of being memory efficient, fast and adds ability of infinite scrolling.
4 |
5 | Date implementation is based on Java 8 Date Time backport [threeten](http://www.threeten.org/)
6 |
7 | ## Example
8 | 
9 |
10 | Source code with examples is included in repository.
11 |
12 | ## Dependencies
13 | ### Gradle
14 | ```
15 | compile "solar.blaz:week-date-picker:1.2"
16 | ```
17 |
18 | ## Usage
19 | ```xml
20 |
25 |
26 |
39 |
40 |
41 |
42 | ```
43 |
--------------------------------------------------------------------------------
/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 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.2'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/example/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 'Google Inc.:Google APIs:23'
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "solar.blaz.date.week.example"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile 'com.android.support:appcompat-v7:23.4.0'
24 | compile project(':week-date-picker')
25 |
26 | testCompile 'junit:junit:4.12'
27 | }
28 |
--------------------------------------------------------------------------------
/example/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/blazsolar/Documents/workspace/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 |
--------------------------------------------------------------------------------
/example/src/androidTest/java/solar/blaz/date/week/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package solar.blaz.date.week;
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 | }
14 |
--------------------------------------------------------------------------------
/example/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/src/main/java/solar/blaz/date/week/MainActivity.java:
--------------------------------------------------------------------------------
1 | package solar.blaz.date.week;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | import org.threeten.bp.LocalDate;
7 |
8 | import solar.blaz.date.week.example.R;
9 |
10 | /**
11 | * Created by Blaz Solar on 23/01/16.
12 | */
13 | public class MainActivity extends Activity {
14 |
15 | private WeekDatePicker datePicker;
16 |
17 | @Override protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_main);
20 |
21 | datePicker = (WeekDatePicker) findViewById(R.id.date_picker);
22 |
23 | datePicker.setDateIndicator(LocalDate.now().plusDays(1), true);
24 | datePicker.setLimits(LocalDate.now().minusWeeks(1), null);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/src/main/res/color/date_picker_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/date_picker_day_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/date_picker_indicator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/example/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/example/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/example/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/example/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/example/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/example/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Week Date Picker
3 |
4 |
--------------------------------------------------------------------------------
/example/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jun 11 14:02:46 CEST 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.13-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # 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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | 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 |
--------------------------------------------------------------------------------
/images/example_screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/images/example_screenshot.gif
--------------------------------------------------------------------------------
/images/example_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blazsolar/WeekDatePicker/3595a5570c840282bc0c01e28cd4d80d323098fa/images/example_screenshot.png
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 |
6 | dependencies {
7 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
8 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
9 | }
10 | }
11 |
12 | apply plugin: 'com.android.library'
13 | apply plugin: 'com.github.dcendents.android-maven'
14 | apply plugin: 'com.jfrog.bintray'
15 |
16 | group = 'solar.blaz'
17 | version = '1.2'
18 |
19 | android {
20 | compileSdkVersion 'Google Inc.:Google APIs:23'
21 | buildToolsVersion "23.0.3"
22 |
23 | defaultConfig {
24 | minSdkVersion 15
25 | targetSdkVersion 23
26 | versionCode 3
27 | versionName project.version
28 | }
29 |
30 | buildTypes {
31 | release {
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 | }
36 | }
37 |
38 | dependencies {
39 | compile 'com.android.support:appcompat-v7:23.4.0'
40 | compile 'org.threeten:threetenbp:1.3.1'
41 | }
42 |
43 | bintray {
44 | user = bintrayUser
45 | key = bintrayKey
46 |
47 | publish = true
48 |
49 | configurations = ['archives'] //When uploading configuration files
50 | pkg {
51 | repo = 'maven'
52 | name = 'week-date-picker'
53 | desc = 'Lightweight week date picker implmentation for Android.'
54 | websiteUrl = "https://github.com/blazsolar/WeekDatePicker"
55 | issueTrackerUrl = 'https://github.com/blazsolar/WeekDatePicker/issues'
56 | vcsUrl = "https://github.com/blazsolar/WeekDatePicker"
57 | licenses = ['MIT']
58 | labels = ['aar', 'android', 'date']
59 | publicDownloadNumbers = true
60 |
61 | githubRepo = 'blazsolar/WeekDatePicker'
62 | githubReleaseNotesFile = 'README.md'
63 | }
64 | }
65 |
66 | install {
67 | repositories.mavenInstaller {
68 | pom {
69 | project {
70 | packaging 'aar'
71 | name 'Lightweight week date picker implmentation for Android.'
72 | url "https://github.com/blazsolar/WeekDatePicker"
73 | licenses {
74 | license {
75 | name 'The MIT License (MIT)'
76 | url 'http://rem.mit-license.org'
77 | }
78 | }
79 | developers {
80 | developer {
81 | id "blazsolar"
82 | name "Blaz Solar"
83 | }
84 | }
85 | scm {
86 | url "https://github.com/blazsolar/WeekDatePicker"
87 | connection "scm:git@github.com:blazsolar/WeekDatePicker.git"
88 | developerConnection "scm:git@github.com:blazsolar/WeekDatePicker.git"
89 | }
90 | }
91 | }
92 | }
93 | }
94 |
95 | task sourcesJar(type: Jar) {
96 | from android.sourceSets.main.java.srcDirs
97 | classifier = 'sources'
98 | }
99 |
100 | task javadoc(type: Javadoc) {
101 | source = android.sourceSets.main.java.srcDirs
102 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
103 | }
104 |
105 | task javadocJar(type: Jar, dependsOn: javadoc) {
106 | classifier = 'javadoc'
107 | from javadoc.destinationDir
108 | }
109 | artifacts {
110 | archives javadocJar
111 | archives sourcesJar
112 | }
113 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/library/src/main/java/solar/blaz/date/week/WeekDatePicker.java:
--------------------------------------------------------------------------------
1 | package solar.blaz.date.week;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.res.ColorStateList;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.graphics.Paint.FontMetricsInt;
11 | import android.graphics.Paint.Style;
12 | import android.graphics.Rect;
13 | import android.graphics.drawable.Drawable;
14 | import android.os.Build;
15 | import android.os.Parcel;
16 | import android.os.Parcelable;
17 | import android.support.annotation.NonNull;
18 | import android.support.annotation.Nullable;
19 | import android.support.v4.text.TextDirectionHeuristicCompat;
20 | import android.support.v4.text.TextDirectionHeuristicsCompat;
21 | import android.text.BoringLayout;
22 | import android.text.Layout;
23 | import android.text.TextPaint;
24 | import android.text.TextUtils;
25 | import android.util.AttributeSet;
26 | import android.util.SparseBooleanArray;
27 | import android.view.KeyEvent;
28 | import android.view.MotionEvent;
29 | import android.view.VelocityTracker;
30 | import android.view.View;
31 | import android.view.ViewConfiguration;
32 | import android.view.animation.DecelerateInterpolator;
33 | import android.widget.OverScroller;
34 |
35 | import org.threeten.bp.DayOfWeek;
36 | import org.threeten.bp.LocalDate;
37 | import org.threeten.bp.format.TextStyle;
38 | import org.threeten.bp.temporal.ChronoUnit;
39 | import org.threeten.bp.temporal.TemporalAdjusters;
40 |
41 | import java.util.ArrayList;
42 | import java.util.List;
43 | import java.util.Locale;
44 |
45 | /**
46 | * Created by Blaž Šolar on 24/01/14.
47 | */
48 | public class WeekDatePicker extends View {
49 |
50 | public static final String TAG = "DatePicker";
51 |
52 | /**
53 | * The coefficient by which to adjust (divide) the max fling velocity.
54 | */
55 | private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 4;
56 |
57 | /**
58 | * The the duration for adjusting the selector wheel.
59 | */
60 | private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
61 |
62 | /**
63 | * Determines speed during touch scrolling.
64 | */
65 | private VelocityTracker velocityTracker;
66 |
67 | /**
68 | * @see ViewConfiguration#getScaledMinimumFlingVelocity()
69 | */
70 | private int minimumFlingVelocity;
71 |
72 | /**
73 | * @see ViewConfiguration#getScaledMaximumFlingVelocity()
74 | */
75 | private int maximumFlingVelocity;
76 |
77 | private int touchSlop;
78 |
79 | private final LocalDate today;
80 | private LocalDate firstDay; // first day of week for current date
81 | private final DayOfWeek firstDayOfWeek;
82 | private final BoringLayout[] layouts = new BoringLayout[3 * 7]; // we are drawing 3 weeks at a time on screen
83 | private final BoringLayout[] dayLabelLayouts = new BoringLayout[7];
84 |
85 | @Nullable private final CharSequence[] labelNames;
86 |
87 | private final TextPaint dayTextPaint;
88 | private final TextPaint dayLabelTextPain;
89 | private final Paint selectedDayColor;
90 |
91 | private BoringLayout.Metrics dayMetrics;
92 | private BoringLayout.Metrics dayLabelMetrics;
93 |
94 | private ColorStateList dayTextColor;
95 | private ColorStateList dayLabelTextColor;
96 |
97 | private TextUtils.TruncateAt ellipsize;
98 |
99 | @Nullable private Drawable dayDrawable;
100 | @Nullable private Drawable indicatorDrawable;
101 |
102 | private int weekWidth;
103 | private int dayWidth;
104 |
105 | private float lastDownEventX;
106 |
107 | private OverScroller flingScrollerX;
108 | private OverScroller adjustScrollerX;
109 |
110 | private int previousScrollerX;
111 |
112 | private boolean scrollingX;
113 | private int scrollPositionStart;
114 |
115 | private OnWeekChanged onWeekChanged;
116 | private OnDateSelected onDateSelected;
117 |
118 | private final SparseBooleanArray dayIndicators = new SparseBooleanArray();
119 |
120 | private int selectedWeek;
121 | private int selectedDay;
122 | private int pressedDay = Integer.MIN_VALUE;
123 |
124 | private int dayDelta;
125 |
126 | private float dividerSize = 0;
127 | private float labelPadding = 0;
128 |
129 | @Nullable private Rect backgroundRect;
130 | @Nullable private Rect indicatorRect;
131 |
132 | private TextDirectionHeuristicCompat textDir;
133 |
134 | @Nullable private LocalDate fromDate;
135 | @Nullable private LocalDate toDate;
136 |
137 | public WeekDatePicker(Context context) {
138 | this(context, null);
139 | }
140 |
141 | public WeekDatePicker(Context context, AttributeSet attrs) {
142 | this(context, attrs, R.attr.weekDatePickerStyle);
143 | }
144 |
145 | public WeekDatePicker(Context context, AttributeSet attrs, int defStyle) {
146 | super(context, attrs, defStyle);
147 |
148 | // create the selector wheel paint
149 | TextPaint paint = new TextPaint();
150 | paint.setAntiAlias(true);
151 | dayTextPaint = paint;
152 |
153 | dayLabelTextPain = new TextPaint();
154 | dayLabelTextPain.setAntiAlias(true);
155 |
156 | selectedDayColor = new Paint();
157 | selectedDayColor.setColor(Color.RED);
158 | selectedDayColor.setStyle(Style.FILL);
159 |
160 | TypedArray a = context.getTheme().obtainStyledAttributes(
161 | attrs,
162 | R.styleable.WeekDatePicker,
163 | defStyle, 0
164 | );
165 |
166 | int ellipsize = 3; // END default value
167 |
168 | try {
169 | dayTextColor = a.getColorStateList(R.styleable.WeekDatePicker_android_textColor);
170 | if (dayTextColor == null) {
171 | dayTextColor = ColorStateList.valueOf(Color.BLACK);
172 | }
173 |
174 | dayLabelTextColor = a.getColorStateList(R.styleable.WeekDatePicker_wdp_labelTextColor);
175 | if (dayLabelTextColor == null) {
176 | dayLabelTextColor = ColorStateList.valueOf(Color.BLACK);
177 | }
178 |
179 | ellipsize = a.getInt(R.styleable.WeekDatePicker_android_ellipsize, ellipsize);
180 | dividerSize = a.getDimension(R.styleable.WeekDatePicker_wdp_dividerSize, dividerSize);
181 |
182 | float textSize = a.getDimension(R.styleable.WeekDatePicker_android_textSize, -1);
183 | if(textSize > -1) {
184 | setTextSize(textSize);
185 | }
186 |
187 | float labelTextSize = a.getDimension(R.styleable.WeekDatePicker_wdp_labelTextSize, -1);
188 | if (labelTextSize > -1) {
189 | setLabelTextSize(labelTextSize);
190 | }
191 |
192 | labelPadding = a.getDimension(R.styleable.WeekDatePicker_wdp_labelPadding, labelPadding);
193 |
194 | labelNames = a.getTextArray(R.styleable.WeekDatePicker_wdp_labelNames);
195 |
196 | dayDrawable = a.getDrawable(R.styleable.WeekDatePicker_wdp_dayBackground);
197 | indicatorDrawable = a.getDrawable(R.styleable.WeekDatePicker_wdp_indicatorDrawable);
198 |
199 | int dayOfWeek = a.getInt(R.styleable.WeekDatePicker_wdp_firstDayOfWeek, DayOfWeek.SUNDAY.getValue());
200 | firstDayOfWeek = DayOfWeek.of(dayOfWeek);
201 |
202 | } finally {
203 | a.recycle();
204 | }
205 |
206 | switch (ellipsize) {
207 | case 1:
208 | setEllipsize(TextUtils.TruncateAt.START);
209 | break;
210 | case 2:
211 | setEllipsize(TextUtils.TruncateAt.MIDDLE);
212 | break;
213 | case 3:
214 | setEllipsize(TextUtils.TruncateAt.END);
215 | break;
216 | case 4:
217 | setEllipsize(TextUtils.TruncateAt.MARQUEE);
218 | break;
219 | }
220 |
221 | buildFontMetrics();
222 | buildLabelFontMetrics();
223 |
224 | // setWillNotDraw(false);
225 |
226 | flingScrollerX = new OverScroller(context);
227 | adjustScrollerX = new OverScroller(context, new DecelerateInterpolator(2.5f));
228 |
229 | // initialize constants
230 | ViewConfiguration configuration = ViewConfiguration.get(context);
231 | touchSlop = configuration.getScaledTouchSlop();
232 | minimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
233 | maximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
234 | / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
235 |
236 | previousScrollerX = Integer.MIN_VALUE;
237 |
238 | calculateItemSize(getWidth(), getHeight());
239 |
240 | // mTouchHelper = new PickerTouchHelper(this);
241 | // ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
242 |
243 | today = LocalDate.now();
244 | firstDay = getFirstDay(0);
245 | selectedDay = firstDay.until(today).getDays();
246 |
247 | }
248 |
249 | @Override
250 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
251 |
252 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
253 | int width = MeasureSpec.getSize(widthMeasureSpec);
254 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
255 |
256 | for (int i = 0; i < 2; i++) {
257 |
258 | if (i != 0 && width == getMeasuredWidth()) {
259 | break;
260 | }
261 |
262 | int height;
263 | if (heightMode == MeasureSpec.EXACTLY) {
264 | height = heightSize;
265 | } else {
266 | int labelTextHeight = Math.abs(dayLabelMetrics.ascent) + Math.abs(dayLabelMetrics.descent);
267 | labelTextHeight += getPaddingTop() + getPaddingBottom();
268 |
269 | int measuredWidth = getMeasuredWidth();
270 | if (measuredWidth == 0) {
271 | measuredWidth = width;
272 | }
273 |
274 | int totalHeight = (int) (labelTextHeight + measuredWidth / 7 / 3 * 2 + labelPadding);
275 |
276 | if (heightMode == MeasureSpec.AT_MOST) {
277 | height = Math.min(heightSize, totalHeight);
278 | } else {
279 | height = totalHeight;
280 | }
281 | }
282 |
283 | setMeasuredDimension(width, height);
284 | }
285 | }
286 |
287 | @Override
288 | protected void onDraw(Canvas canvas) {
289 | super.onDraw(canvas);
290 |
291 | float itemWithPadding = weekWidth + dividerSize;
292 |
293 | int saveCount = canvas.getSaveCount();
294 | canvas.save();
295 |
296 | int weekOffset = getSelectedWeek() - 1;
297 | float position = itemWithPadding * weekOffset;
298 |
299 | canvas.translate(position, getPaddingTop());
300 |
301 | for (int i = 0; i < 3; i++) {
302 | drawWeek(canvas, i * 7, weekOffset + i);
303 | canvas.translate(itemWithPadding, 0);
304 | }
305 |
306 | canvas.restoreToCount(saveCount);
307 |
308 | }
309 |
310 | private void drawWeek(Canvas canvas, int layoutIndex, int weekOffset) {
311 |
312 | int saveCount = canvas.save();
313 |
314 | int labelHeight = dayLabelLayouts[0].getHeight();
315 | float circleRadius = dayWidth / 3;
316 | int centerY = layouts[0].getHeight() / 2;
317 | float dateLineOffset = circleRadius - centerY;
318 |
319 | for (int i = 0; i < 7; i++) {
320 |
321 | int itemIndex = weekOffset * 7 + i;
322 | BoringLayout layout = layouts[layoutIndex + i];
323 | BoringLayout labelLayout = dayLabelLayouts[i];
324 |
325 | dayLabelTextPain.setColor(getTextColor(dayLabelTextColor, itemIndex));
326 | labelLayout.draw(canvas);
327 |
328 | dayTextPaint.setColor(getTextColor(dayTextColor, itemIndex));
329 |
330 | int count = canvas.save();
331 | canvas.translate(0, labelHeight + dateLineOffset + labelPadding);
332 |
333 | if (dayDrawable != null) {
334 | dayDrawable.setBounds(backgroundRect);
335 | dayDrawable.setState(getItemDrawableState(itemIndex));
336 | dayDrawable.draw(canvas);
337 | }
338 |
339 | if (indicatorDrawable != null && dayIndicators.get(itemIndex - dayDelta, false)) {
340 | indicatorDrawable.setBounds(indicatorRect);
341 | indicatorDrawable.setState(getItemDrawableState(itemIndex));
342 | indicatorDrawable.draw(canvas);
343 | }
344 |
345 | layout.draw(canvas);
346 |
347 | canvas.restoreToCount(count);
348 |
349 | canvas.translate(dayWidth, 0);
350 | }
351 |
352 | canvas.restoreToCount(saveCount);
353 |
354 | }
355 |
356 | private LocalDate getRelativeFirstDay(int weekOffset) {
357 | weekOffset = getSelectedWeek() + weekOffset;
358 | return getFirstDay(weekOffset);
359 | }
360 |
361 | private LocalDate getFirstDay(int weekOffset) {
362 | return getFirstDay().plusWeeks(weekOffset).with(TemporalAdjusters.previousOrSame(firstDayOfWeek));
363 | }
364 |
365 | @NonNull private LocalDate getFirstDay() {
366 | if (fromDate != null) {
367 | return fromDate;
368 | } else {
369 | return today;
370 | }
371 | }
372 |
373 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
374 | @Override
375 | public void onRtlPropertiesChanged(int layoutDirection) {
376 | super.onRtlPropertiesChanged(layoutDirection);
377 |
378 | textDir = getTextDirectionHeuristic();
379 | }
380 |
381 | public void setLimits(@Nullable LocalDate from, @Nullable LocalDate to) {
382 |
383 | readjustIndexes(from);
384 |
385 | fromDate = from;
386 | toDate = to;
387 |
388 | firstDay = getFirstDay(0);
389 |
390 | invalidate();
391 | }
392 |
393 | private void readjustIndexes(@Nullable LocalDate newDate) {
394 |
395 | LocalDate from = newDate == null ? today : newDate;
396 | LocalDate to = fromDate == null ? today : fromDate;
397 |
398 | int delta = from.until(to).getDays();
399 | selectedDay += delta;
400 | dayDelta = delta;
401 |
402 | }
403 |
404 | private TextDirectionHeuristicCompat getTextDirectionHeuristic() {
405 |
406 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
407 |
408 | return TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR;
409 |
410 | } else {
411 |
412 | // Always need to resolve layout direction first
413 | final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
414 |
415 | switch (getTextDirection()) {
416 | default:
417 | case TEXT_DIRECTION_FIRST_STRONG:
418 | return (defaultIsRtl ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL :
419 | TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR);
420 | case TEXT_DIRECTION_ANY_RTL:
421 | return TextDirectionHeuristicsCompat.ANYRTL_LTR;
422 | case TEXT_DIRECTION_LTR:
423 | return TextDirectionHeuristicsCompat.LTR;
424 | case TEXT_DIRECTION_RTL:
425 | return TextDirectionHeuristicsCompat.RTL;
426 | case TEXT_DIRECTION_LOCALE:
427 | return TextDirectionHeuristicsCompat.LOCALE;
428 | }
429 | }
430 | }
431 |
432 | private void remakeLayout() {
433 |
434 | if (getWidth() > 0) {
435 |
436 | LocalDate day = getRelativeFirstDay(-1);
437 |
438 | for (int i = 0; i < layouts.length; i++) {
439 |
440 | String dayText = String.valueOf(day.getDayOfMonth());
441 | if (layouts[i] == null) {
442 | layouts[i] = BoringLayout.make(dayText, dayTextPaint, dayWidth,
443 | Layout.Alignment.ALIGN_CENTER, 1f, 1f, dayMetrics, false, ellipsize,
444 | dayWidth);
445 | } else {
446 | layouts[i].replaceOrMake(dayText, dayTextPaint, dayWidth,
447 | Layout.Alignment.ALIGN_CENTER, 1f, 1f, dayMetrics, false, ellipsize,
448 | dayWidth);
449 | }
450 |
451 | day = day.plusDays(1);
452 | }
453 |
454 | DayOfWeek dayOfWeek = firstDayOfWeek; // first index is 1
455 | for (int i = 0; i < dayLabelLayouts.length; i++) {
456 |
457 | CharSequence name;
458 | if (labelNames == null) {
459 | name = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault());
460 | } else {
461 | int index = dayOfWeek.getValue() - 1;
462 | name = labelNames[index];
463 | }
464 |
465 |
466 | if (dayLabelLayouts[i] == null) {
467 | dayLabelLayouts[i] = BoringLayout.make(name, dayLabelTextPain, dayWidth,
468 | Layout.Alignment.ALIGN_CENTER, 1f, 1f, dayLabelMetrics, false, ellipsize,
469 | dayWidth);
470 | } else {
471 | dayLabelLayouts[i].replaceOrMake(name, dayLabelTextPain, dayWidth,
472 | Layout.Alignment.ALIGN_CENTER, 1f, 1f, dayLabelMetrics, false, ellipsize,
473 | dayWidth);
474 | }
475 |
476 | dayOfWeek = dayOfWeek.plus(1);
477 |
478 | }
479 |
480 | }
481 |
482 | }
483 |
484 | /**
485 | * Calculates text color for specified item based on its position and state.
486 | *
487 | * @param item Index of item to get text color for
488 | * @return Item text color
489 | */
490 | private int getTextColor(ColorStateList color, int item) {
491 |
492 | List states = new ArrayList<>();
493 |
494 | if (isItemEnabled(item)) {
495 | states.add(android.R.attr.state_enabled);
496 | }
497 |
498 | if (isItemPressed(item)) {
499 | states.add(android.R.attr.state_pressed);
500 | }
501 |
502 | if (isItemSelected(item)) {
503 | states.add(android.R.attr.state_selected);
504 | }
505 |
506 | int[] finalState = new int[states.size()];
507 | if (states.size() > 0) {
508 | for (int i = 0; i < states.size(); i++) {
509 | finalState[i] = states.get(i);
510 | }
511 | }
512 |
513 | return color.getColorForState(finalState, color.getDefaultColor());
514 |
515 | }
516 |
517 | private int[] getItemDrawableState(int item) {
518 |
519 | List state = new ArrayList<>();
520 | if (isItemEnabled(item)) {
521 | state.add(android.R.attr.state_enabled);
522 | }
523 |
524 | if (isItemSelected(item)) {
525 | state.add(android.R.attr.state_selected);
526 | }
527 |
528 | if (isItemPressed(item)) {
529 | state.add(android.R.attr.state_pressed);
530 | }
531 |
532 | int[] intState = new int[state.size()];
533 | for (int i = 0; i < state.size(); i++) {
534 | intState[i] = state.get(i);
535 | }
536 | return intState;
537 |
538 | }
539 |
540 | private boolean isItemPressed(int item) {
541 | return item == pressedDay;
542 | }
543 |
544 | private boolean isItemSelected(int item) {
545 | return item == selectedDay;
546 | }
547 |
548 | private boolean isItemEnabled(int item) {
549 | LocalDate date = getDate(item);
550 |
551 | return (fromDate == null || fromDate.isEqual(date) || fromDate.isBefore(date))
552 | && (toDate == null || date.isEqual(toDate) || toDate.isAfter(date));
553 | }
554 |
555 | private LocalDate getDate(int day) {
556 | return firstDay.plusDays(day);
557 | }
558 |
559 | @Override
560 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
561 | super.onSizeChanged(w, h, oldw, oldh);
562 |
563 | calculateItemSize(w, h);
564 | calculateBackgroundRect();
565 | calculateIndicatorRect();
566 | }
567 |
568 | @Override
569 | public boolean onTouchEvent(MotionEvent event) {
570 | if(!isEnabled()) {
571 | return false;
572 | }
573 |
574 | if (velocityTracker == null) {
575 | velocityTracker = VelocityTracker.obtain();
576 | }
577 | velocityTracker.addMovement(event);
578 |
579 | int action = event.getActionMasked();
580 | switch (action) {
581 | case MotionEvent.ACTION_MOVE:
582 |
583 | float currentMoveX = event.getX();
584 |
585 | int deltaMoveX = (int) (lastDownEventX - currentMoveX);
586 |
587 | if(scrollingX || Math.abs(deltaMoveX) > touchSlop) {
588 |
589 | if(!scrollingX) {
590 | deltaMoveX = 0;
591 | pressedDay = Integer.MIN_VALUE;
592 | scrollingX = true;
593 | getParent().requestDisallowInterceptTouchEvent(true);
594 | scrollPositionStart = getScrollX();
595 | }
596 |
597 | scrollBy(deltaMoveX, 0);
598 |
599 | lastDownEventX = currentMoveX;
600 | invalidate();
601 |
602 | }
603 |
604 | break;
605 | case MotionEvent.ACTION_DOWN:
606 |
607 | if(!adjustScrollerX.isFinished()) {
608 | adjustScrollerX.forceFinished(true);
609 | } else if(!flingScrollerX.isFinished()) {
610 | flingScrollerX.forceFinished(true);
611 | } else {
612 | scrollingX = false;
613 | }
614 |
615 | lastDownEventX = event.getX();
616 |
617 | if(!scrollingX) {
618 | pressedDay = getDayPositionFromTouch(event.getX());
619 | }
620 | invalidate();
621 |
622 | break;
623 | case MotionEvent.ACTION_UP:
624 |
625 | VelocityTracker velocityTracker = this.velocityTracker;
626 | velocityTracker.computeCurrentVelocity(500, maximumFlingVelocity);
627 | int initialVelocityX = (int) velocityTracker.getXVelocity();
628 |
629 | if(scrollingX && Math.abs(initialVelocityX) > minimumFlingVelocity) {
630 | flingX(initialVelocityX);
631 | } else {
632 | float positionX = event.getX();
633 | if(!scrollingX) {
634 | int itemPos = getDayPositionFromTouch(positionX);
635 | if (isItemEnabled(itemPos)) {
636 | selectDay(itemPos);
637 | }
638 | } else if(scrollingX) {
639 | finishScrolling();
640 | }
641 | }
642 |
643 | this.velocityTracker.recycle();
644 | this.velocityTracker = null;
645 |
646 | case MotionEvent.ACTION_CANCEL:
647 | pressedDay = Integer.MIN_VALUE;
648 | invalidate();
649 | break;
650 | }
651 |
652 | return true;
653 | }
654 |
655 | public void selectDay(@NonNull LocalDate date) {
656 | int day = getDayForDate(date);
657 | selectDay(day);
658 | }
659 |
660 | @Override public void scrollTo(int x, int y) {
661 | if (fromDate != null && x < 0) {
662 | x = 0;
663 | }
664 |
665 | if (toDate != null) {
666 | float totalWidth = (weekWidth + dividerSize) * ChronoUnit.WEEKS.between(getFirstDay(), toDate);
667 |
668 | if (x > totalWidth) {
669 | x = (int) totalWidth;
670 | }
671 | }
672 |
673 | super.scrollTo(x, y);
674 | }
675 |
676 | private void selectDay(final int day) {
677 |
678 | if (selectedDay != day) {
679 |
680 | selectedDay = day;
681 |
682 | // post to the UI Thread to avoid potential interference with the OpenGL Thread
683 | if (onDateSelected != null) {
684 | final LocalDate date = getDate(day);
685 | post(new Runnable() {
686 | @Override
687 | public void run() {
688 | onDateSelected.onDateSelected(date);
689 | }
690 | });
691 | }
692 |
693 | invalidate();
694 |
695 | }
696 |
697 | int week = selectedDay / 7;
698 | if (selectedDay < 0 && selectedDay % 7 != 0) {
699 | week -= 1;
700 | }
701 |
702 | if (week != selectedWeek) {
703 | adjustToNearestWeekX(week);
704 | }
705 |
706 | }
707 |
708 | @Override
709 | public boolean onKeyDown(int keyCode, KeyEvent event) {
710 |
711 | if (!isEnabled()) {
712 | return super.onKeyDown(keyCode, event);
713 | }
714 |
715 | switch (keyCode) {
716 | case KeyEvent.KEYCODE_DPAD_CENTER:
717 | case KeyEvent.KEYCODE_ENTER:
718 | // selectDay();
719 | return true;
720 | case KeyEvent.KEYCODE_DPAD_LEFT:
721 | smoothScrollBy(-1);
722 | return true;
723 | case KeyEvent.KEYCODE_DPAD_RIGHT:
724 | smoothScrollBy(1);
725 | return true;
726 | default:
727 | return super.onKeyDown(keyCode, event);
728 | }
729 |
730 | }
731 |
732 | // @Override
733 | // protected boolean dispatchHoverEvent(MotionEvent event) {
734 | //
735 | // if (mTouchHelper.dispatchHoverEvent(event)) {
736 | // return true;
737 | // }
738 | //
739 | // return super.dispatchHoverEvent(event);
740 | // }
741 |
742 | @Override
743 | public void computeScroll() {
744 | computeScrollX();
745 | }
746 |
747 | @Override
748 | public void getFocusedRect(Rect r) {
749 | super.getFocusedRect(r); // TODO this should only be current item
750 | }
751 |
752 | public void setOnWeekChangedListener(OnWeekChanged onWeekChanged) {
753 | this.onWeekChanged = onWeekChanged;
754 | }
755 |
756 | public void setOnDateSelectedListener(OnDateSelected onDateSelected) {
757 | this.onDateSelected = onDateSelected;
758 | }
759 |
760 | public int getSelectedWeek() {
761 | int x = getScrollX();
762 | return getWeekPositionFromCoordinates(x);
763 | }
764 |
765 | public void scrollToWeek(int index) {
766 | selectedWeek = index;
767 | scrollToItem(index);
768 | }
769 |
770 | @Override
771 | protected void onRestoreInstanceState(Parcelable state) {
772 |
773 | if (!(state instanceof SavedState)) {
774 | super.onRestoreInstanceState(state);
775 | return;
776 | }
777 |
778 | SavedState ss = (SavedState) state;
779 | super.onRestoreInstanceState(ss.getSuperState());
780 |
781 | scrollToWeek(ss.mSelItem);
782 |
783 |
784 | }
785 |
786 | @Override
787 | protected Parcelable onSaveInstanceState() {
788 | Parcelable superState = super.onSaveInstanceState();
789 |
790 | SavedState savedState = new SavedState(superState);
791 | savedState.mSelItem = selectedWeek;
792 |
793 | return savedState;
794 |
795 | }
796 |
797 | public TextUtils.TruncateAt getEllipsize() {
798 | return ellipsize;
799 | }
800 |
801 | public void setEllipsize(TextUtils.TruncateAt ellipsize) {
802 | if (this.ellipsize != ellipsize) {
803 | this.ellipsize = ellipsize;
804 |
805 | remakeLayout();
806 | invalidate();
807 | }
808 | }
809 |
810 | public void setDateIndicator(@NonNull LocalDate date, boolean enabled) {
811 | int itemIndex = getDayForDate(date);
812 | if (enabled) {
813 | dayIndicators.put(itemIndex, true);
814 | } else {
815 | dayIndicators.delete(itemIndex);
816 | }
817 | }
818 |
819 | private int getDayForDate(@NonNull LocalDate date) {
820 | return firstDay.until(date).getDays();
821 | }
822 |
823 | @Override
824 | protected void drawableStateChanged() {
825 | super.drawableStateChanged(); //TODO
826 | }
827 |
828 | @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) {
829 | super.onScrollChanged(l, t, oldl, oldt);
830 |
831 | if (getWeekPositionFromCoordinates(l) != getWeekPositionFromCoordinates(oldl)) {
832 | remakeLayout();
833 | }
834 | }
835 |
836 | private int getDayPositionFromTouch(float x) {
837 | int weekPositionFromTouch = getWeekPositionFromTouch(x);
838 | int position = weekPositionFromTouch * 7 + getRelativeDayPositionFromTouch(x);
839 | if (weekPositionFromTouch < 0) {
840 | position += 6;
841 | }
842 | return position;
843 | }
844 |
845 | private int getRelativeDayPositionFromTouch(float x) {
846 | return getRelativeDayPositionFromCoordinates((int) (getScrollX() + x));
847 | }
848 |
849 | private int getWeekPositionFromTouch(float x) {
850 | return getWeekPositionFromCoordinates((int) (getScrollX() - (weekWidth + dividerSize) * .5f + x));
851 | }
852 |
853 | /**
854 | * Returns day of the week for passed coordinates.
855 | */
856 | private int getRelativeDayPositionFromCoordinates(int x) {
857 | float relativePosition = x % (weekWidth + dividerSize);
858 | return (int) (relativePosition / dayWidth);
859 | }
860 |
861 | private void computeScrollX() {
862 | OverScroller scroller = flingScrollerX;
863 | if(scroller.isFinished()) {
864 | scroller = adjustScrollerX;
865 | if(scroller.isFinished()) {
866 | return;
867 | }
868 | }
869 |
870 | if(scroller.computeScrollOffset()) {
871 |
872 | int currentScrollerX = scroller.getCurrX();
873 | if(previousScrollerX == Integer.MIN_VALUE) {
874 | previousScrollerX = scroller.getStartX();
875 | }
876 |
877 | scrollBy(currentScrollerX - previousScrollerX, 0);
878 | previousScrollerX = currentScrollerX;
879 |
880 | if(scroller.isFinished()) {
881 | onScrollerFinishedX(scroller);
882 | }
883 |
884 | postInvalidate();
885 | }
886 | }
887 |
888 | private void flingX(int velocityX) {
889 |
890 | int signum= Integer.signum(velocityX);
891 |
892 | int currentWeekPosition = getWeekPositionFromCoordinates(scrollPositionStart);
893 | float weekWidth = this.weekWidth + dividerSize;
894 |
895 | int finalPosition;
896 | switch (signum) {
897 | case -1:
898 | finalPosition = (int) weekWidth * (currentWeekPosition + 1);
899 | break;
900 | default:
901 | case 0:
902 | finalPosition = (int) weekWidth * (currentWeekPosition);
903 | break;
904 | case 1:
905 | finalPosition = (int) weekWidth * (currentWeekPosition - 1);
906 | break;
907 | }
908 |
909 | int dx = finalPosition - getScrollX();
910 |
911 | previousScrollerX = Integer.MIN_VALUE;
912 | flingScrollerX.startScroll(getScrollX(), getScrollY(), dx, 0);
913 |
914 | invalidate();
915 | }
916 |
917 | private void adjustToNearestWeekX() {
918 |
919 | int x = getScrollX();
920 | int week = Math.round(x / (weekWidth + dividerSize * 1f));
921 | adjustToNearestWeekX(week);
922 |
923 | }
924 |
925 | private void adjustToNearestWeekX(int week) {
926 |
927 | int x = getScrollX();
928 |
929 | if (selectedWeek != week) {
930 | selectedWeek = week;
931 | notifyWeekChange();
932 | }
933 |
934 | int weekPosition = (weekWidth + (int) dividerSize) * week;
935 |
936 | int deltaX = weekPosition - x;
937 |
938 | previousScrollerX = Integer.MIN_VALUE;
939 | adjustScrollerX.startScroll(x, 0, deltaX, 0, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
940 | invalidate();
941 | }
942 |
943 | private void calculateItemSize(int w, int h) {
944 |
945 | int items = 1;
946 | int totalPadding = ((int) dividerSize * (items - 1));
947 | weekWidth = (w - totalPadding) / items;
948 | dayWidth = weekWidth / 7;
949 |
950 | scrollToItem(selectedWeek);
951 |
952 | buildFontMetrics();
953 | buildLabelFontMetrics();
954 | remakeLayout();
955 |
956 | }
957 |
958 | private void calculateBackgroundRect() {
959 |
960 | if (dayDrawable != null) {
961 | float circleRadius = dayWidth / 3;
962 | int centerX = layouts[0].getWidth() / 2;
963 | int centerY = layouts[0].getHeight() / 2;
964 |
965 | backgroundRect = new Rect((int) (centerX - circleRadius), (int) (centerY - circleRadius),
966 | (int) (centerX + circleRadius), (int) (centerY + circleRadius));
967 | } else {
968 | backgroundRect = null;
969 | }
970 |
971 | }
972 |
973 | private void calculateIndicatorRect() {
974 |
975 | if (indicatorDrawable != null) {
976 |
977 | float circleRadius = dayWidth / 3;
978 | int centerX = layouts[0].getWidth() / 2;
979 | int centerY = layouts[0].getHeight() / 2;
980 |
981 | int indicatorDotWidth = indicatorDrawable.getIntrinsicWidth();
982 | int indicatorDotHeight = indicatorDrawable.getIntrinsicHeight();
983 | indicatorRect = new Rect(centerX - indicatorDotWidth / 2,
984 | (int) (centerY + circleRadius - indicatorDotHeight),
985 | centerX + indicatorDotWidth / 2, (int) (centerY + circleRadius));
986 |
987 | } else {
988 | indicatorRect = null;
989 | }
990 |
991 | }
992 |
993 | private void onScrollerFinishedX(OverScroller scroller) {
994 | if(scroller == flingScrollerX) {
995 | finishScrolling();
996 | }
997 | }
998 |
999 | private void finishScrolling() {
1000 |
1001 | adjustToNearestWeekX();
1002 | scrollingX = false;
1003 | }
1004 |
1005 | private int getPositionOnScreen(float x) {
1006 | return (int) (x / (weekWidth + dividerSize));
1007 | }
1008 |
1009 | private void smoothScrollBy(int i) {
1010 | int deltaMoveX = (weekWidth + (int) dividerSize) * i;
1011 |
1012 | previousScrollerX = Integer.MIN_VALUE;
1013 | flingScrollerX.startScroll(getScrollX(), 0, deltaMoveX, 0);
1014 | invalidate();
1015 | }
1016 |
1017 | /**
1018 | * Sets text size for items
1019 | * @param size New item text size in px.
1020 | */
1021 | private void setTextSize(float size) {
1022 | if(size != dayTextPaint.getTextSize()) {
1023 | dayTextPaint.setTextSize(size);
1024 |
1025 | buildFontMetrics();
1026 | requestLayout();
1027 | invalidate();
1028 | }
1029 | }
1030 |
1031 | private void setLabelTextSize(float size) {
1032 | if (size != dayLabelTextPain.getTextSize()) {
1033 | dayLabelTextPain.setTextSize(size);
1034 |
1035 | buildLabelFontMetrics();
1036 | remakeLayout();
1037 | invalidate();
1038 | }
1039 | }
1040 |
1041 | /**
1042 | * Calculates item from x coordinate position.
1043 | * @param x Scroll position to calculate.
1044 | * @return Selected item from scrolling position in {param x}
1045 | */
1046 | private int getWeekPositionFromCoordinates(int x) {
1047 | return Math.round(x / (weekWidth + dividerSize));
1048 | }
1049 |
1050 | /**
1051 | * Scrolls to specified item.
1052 | * @param index Index of an item to scroll to
1053 | */
1054 | private void scrollToItem(int index) {
1055 | scrollTo((weekWidth + (int) dividerSize) * index, 0);
1056 | }
1057 |
1058 | private void notifyWeekChange() {
1059 |
1060 | // post to the UI Thread to avoid potential interference with the OpenGL Thread
1061 | if (onWeekChanged != null) {
1062 | post(new Runnable() {
1063 | @Override
1064 | public void run() {
1065 | LocalDate firstDay = getFirstDay(getWeekPositionFromCoordinates(getScrollX()));
1066 | onWeekChanged.onItemSelected(firstDay);
1067 | }
1068 | });
1069 | }
1070 |
1071 | }
1072 |
1073 | private void buildFontMetrics() {
1074 | FontMetricsInt fontMetricsInt = dayTextPaint.getFontMetricsInt();
1075 | dayMetrics = WeekDatePicker.toBoringFontMetrics(fontMetricsInt, dayMetrics);
1076 | dayMetrics.width = weekWidth;
1077 | }
1078 |
1079 | private void buildLabelFontMetrics() {
1080 | FontMetricsInt fontMetricsInt = dayLabelTextPain.getFontMetricsInt();
1081 | dayLabelMetrics = WeekDatePicker.toBoringFontMetrics(fontMetricsInt, dayLabelMetrics);
1082 | dayLabelMetrics.width = weekWidth;
1083 | }
1084 |
1085 | public interface OnWeekChanged {
1086 |
1087 | void onItemSelected(LocalDate firstDay);
1088 |
1089 | }
1090 |
1091 | public interface OnDateSelected {
1092 |
1093 | void onDateSelected(LocalDate date);
1094 |
1095 | }
1096 |
1097 | private static BoringLayout.Metrics toBoringFontMetrics(FontMetricsInt metrics,
1098 | @Nullable BoringLayout.Metrics fontMetrics) {
1099 |
1100 | if (fontMetrics == null) {
1101 | fontMetrics = new BoringLayout.Metrics();
1102 | }
1103 |
1104 | fontMetrics.ascent = metrics.ascent;
1105 | fontMetrics.bottom = metrics.bottom;
1106 | fontMetrics.descent = metrics.descent;
1107 | fontMetrics.leading = metrics.leading;
1108 | fontMetrics.top = metrics.top;
1109 | return fontMetrics;
1110 | }
1111 |
1112 | public static class SavedState extends BaseSavedState {
1113 |
1114 | private int mSelItem;
1115 |
1116 | public SavedState(Parcelable superState) {
1117 | super(superState);
1118 | }
1119 |
1120 | private SavedState(Parcel in) {
1121 | super(in);
1122 | mSelItem = in.readInt();
1123 | }
1124 |
1125 | @Override
1126 | public void writeToParcel(Parcel dest, int flags) {
1127 | super.writeToParcel(dest, flags);
1128 |
1129 | dest.writeInt(mSelItem);
1130 | }
1131 |
1132 | @Override
1133 | public String toString() {
1134 | return "HorizontalPicker.SavedState{"
1135 | + Integer.toHexString(System.identityHashCode(this))
1136 | + " selItem=" + mSelItem
1137 | + "}";
1138 | }
1139 |
1140 | @SuppressWarnings("hiding")
1141 | public static final Creator CREATOR
1142 | = new Creator() {
1143 | public SavedState createFromParcel(Parcel in) {
1144 | return new SavedState(in);
1145 | }
1146 |
1147 | public SavedState[] newArray(int size) {
1148 | return new SavedState[size];
1149 | }
1150 | };
1151 | }
1152 |
1153 | }
1154 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library'
2 | include ':example'
3 |
4 | project(':library').name = 'week-date-picker'
5 |
--------------------------------------------------------------------------------