├── sample
├── .gitignore
├── src
│ └── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values
│ │ │ ├── styles.xml
│ │ │ ├── colors.xml
│ │ │ └── strings.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ └── menu
│ │ │ └── main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── alamkanak
│ │ └── weekview
│ │ └── sample
│ │ └── MainActivity.java
├── build.gradle
└── proguard-rules.pro
├── library
├── .gitignore
├── project.properties
├── gradle.properties
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ └── values
│ │ │ └── attrs.xml
│ │ └── java
│ │ └── com
│ │ └── alamkanak
│ │ └── weekview
│ │ ├── WeekViewEvent.java
│ │ └── WeekView.java
├── build.gradle
└── proguard-rules.pro
├── settings.gradle
├── images
└── screen-shot.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── .gitignore
├── gradlew.bat
├── gradlew
├── README.md
└── LICENSE
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/project.properties:
--------------------------------------------------------------------------------
1 | android.library=true
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library', ':sample'
2 |
--------------------------------------------------------------------------------
/images/screen-shot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ribot/Android-Week-View/HEAD/images/screen-shot.png
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Android Week View
2 | POM_ARTIFACT_ID=android-week-view
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ribot/Android-Week-View/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/sample/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ribot/Android-Week-View/HEAD/sample/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ribot/Android-Week-View/HEAD/sample/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ribot/Android-Week-View/HEAD/sample/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ribot/Android-Week-View/HEAD/sample/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ribot/Android-Week-View/HEAD/sample/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 19 19:16:06 BDT 2014
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.1-all.zip
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #59dbe0
4 | #f57f68
5 | #87d288
6 | #f8b552
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Android Week View
4 | Go to today
5 | Day view
6 | 3 day view
7 | Week view
8 |
9 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | repositories {
4 | mavenCentral()
5 | }
6 |
7 | android {
8 | compileSdkVersion 20
9 | buildToolsVersion "20.0.0"
10 |
11 | defaultConfig {
12 | minSdkVersion 9
13 | targetSdkVersion 20
14 | }
15 | }
16 |
17 | dependencies {
18 | compile 'com.android.support:appcompat-v7:20.0.0'
19 | }
20 |
21 | apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | VERSION_NAME=1.1.6
2 | GROUP=com.github.alamkanak
3 |
4 | POM_DESCRIPTION=Dissect layout traversals on Android.
5 | POM_URL=https://github.com/alamkanak/Android-Week-View
6 | POM_SCM_URL=https://github.com/alamkanak/Android-Week-View
7 | POM_SCM_CONNECTION=scm:git@github.com/alamkanak/Android-Week-View.git
8 | POM_SCM_DEV_CONNECTION=scm:git@github.com:alamkanak/Android-Week-View.git
9 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
10 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
11 | POM_LICENCE_DIST=repo
12 | POM_DEVELOPER_ID=alamkanak
13 | POM_DEVELOPER_NAME=Raquib-ul-Alam (Kanak)
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 20
5 | buildToolsVersion "20.0.0"
6 |
7 | defaultConfig {
8 | applicationId "com.alamkanak.weekview"
9 | minSdkVersion 9
10 | targetSdkVersion 20
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | runProguard false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile project(':library')
25 | compile 'com.android.support:appcompat-v7:19.1.0'
26 | }
27 |
--------------------------------------------------------------------------------
/library/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 D:/Program Files (x86)/Android/android-studio/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/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 D:\Program Files (x86)\Android\android-studio\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/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/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 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | ### Android ###
4 | # Built application files
5 | *.apk
6 | *.ap_
7 |
8 | # Files for the Dalvik VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # Generated files
15 | bin/
16 | gen/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 |
32 | ### Intellij ###
33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
34 |
35 | /*.iml
36 |
37 | ## Directory-based project format:
38 | .idea/
39 | # if you remove the above rule, at least ignore the follwing:
40 |
41 | # User-specific stuff:
42 | # .idea/workspace.xml
43 | # .idea/tasks.xml
44 | # .idea/dictionaries
45 |
46 | # Sensitive or high-churn files:
47 | # .idea/dataSources.ids
48 | # .idea/dataSources.xml
49 | # .idea/sqlDataSources.xml
50 | # .idea/dynamic.xml
51 | # .idea/uiDesigner.xml
52 |
53 | # Gradle:
54 | # .idea/gradle.xml
55 | # .idea/libraries
56 |
57 | # Mongo Explorer plugin:
58 | # .idea/mongoSettings.xml
59 |
60 | ## File-based project format:
61 | *.ipr
62 | *.iws
63 |
64 | ## Plugin-specific files:
65 |
66 | # IntelliJ
67 | out/
68 |
69 | # mpeltonen/sbt-idea plugin
70 | .idea_modules/
71 |
72 | # JIRA plugin
73 | atlassian-ide-plugin.xml
74 |
75 | # Crashlytics plugin (for Android Studio and IntelliJ)
76 | com_crashlytics_export_strings.xml
77 |
78 |
79 | ### OSX ###
80 | .DS_Store
81 | .AppleDouble
82 | .LSOverride
83 |
84 | # Icon must end with two \r
85 | Icon
86 |
87 |
88 | # Thumbnails
89 | ._*
90 |
91 | # Files that might appear on external disk
92 | .Spotlight-V100
93 | .Trashes
94 |
95 | # Directories potentially created on remote AFP share
96 | .AppleDB
97 | .AppleDesktop
98 | Network Trash Folder
99 | Temporary Items
100 | .apdisk
101 |
102 |
103 | ### Windows ###
104 | # Windows image file caches
105 | Thumbs.db
106 | ehthumbs.db
107 |
108 | # Folder config file
109 | Desktop.ini
110 |
111 | # Recycle Bin used on file shares
112 | $RECYCLE.BIN/
113 |
114 | # Windows Installer files
115 | *.cab
116 | *.msi
117 | *.msm
118 | *.msp
119 |
120 | # Windows shortcuts
121 | *.lnk
122 |
123 |
124 | ### Gradle ###
125 | .gradle
126 | build/
127 |
128 | # Ignore Gradle GUI config
129 | gradle-app.setting
130 | *.iml
131 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java:
--------------------------------------------------------------------------------
1 | package com.alamkanak.weekview;
2 |
3 | import java.util.Calendar;
4 |
5 | /**
6 | * Created by Raquib-ul-Alam Kanak on 7/21/2014.
7 | * Website: http://april-shower.com
8 | */
9 | public class WeekViewEvent {
10 | private long mId;
11 | private Calendar mStartTime;
12 | private Calendar mEndTime;
13 | private String mName;
14 | private int mColor;
15 |
16 | public WeekViewEvent(){
17 |
18 | }
19 |
20 | /**
21 | * Initializes the event for week view.
22 | * @param name Name of the event.
23 | * @param startYear Year when the event starts.
24 | * @param startMonth Month when the event starts.
25 | * @param startDay Day when the event starts.
26 | * @param startHour Hour (in 24-hour format) when the event starts.
27 | * @param startMinute Minute when the event starts.
28 | * @param endYear Year when the event ends.
29 | * @param endMonth Month when the event ends.
30 | * @param endDay Day when the event ends.
31 | * @param endHour Hour (in 24-hour format) when the event ends.
32 | * @param endMinute Minute when the event ends.
33 | */
34 | public WeekViewEvent(long id, String name, int startYear, int startMonth, int startDay, int startHour, int startMinute, int endYear, int endMonth, int endDay, int endHour, int endMinute) {
35 | this.mId = id;
36 |
37 | this.mStartTime = Calendar.getInstance();
38 | this.mStartTime.set(Calendar.YEAR, startYear);
39 | this.mStartTime.set(Calendar.MONTH, startMonth-1);
40 | this.mStartTime.set(Calendar.DAY_OF_MONTH, startDay);
41 | this.mStartTime.set(Calendar.HOUR_OF_DAY, startHour);
42 | this.mStartTime.set(Calendar.MINUTE, startMinute);
43 |
44 | this.mEndTime = Calendar.getInstance();
45 | this.mEndTime.set(Calendar.YEAR, endYear);
46 | this.mEndTime.set(Calendar.MONTH, endMonth-1);
47 | this.mEndTime.set(Calendar.DAY_OF_MONTH, endDay);
48 | this.mEndTime.set(Calendar.HOUR_OF_DAY, endHour);
49 | this.mEndTime.set(Calendar.MINUTE, endMinute);
50 |
51 | this.mName = name;
52 | }
53 |
54 | /**
55 | * Initializes the event for week view.
56 | * @param name Name of the event.
57 | * @param startTime The time when the event starts.
58 | * @param endTime The time when the event ends.
59 | */
60 | public WeekViewEvent(long id, String name, Calendar startTime, Calendar endTime) {
61 | this.mId = id;
62 | this.mName = name;
63 | this.mStartTime = startTime;
64 | this.mEndTime = endTime;
65 | }
66 |
67 |
68 | public Calendar getStartTime() {
69 | return mStartTime;
70 | }
71 |
72 | public void setStartTime(Calendar startTime) {
73 | this.mStartTime = startTime;
74 | }
75 |
76 | public Calendar getEndTime() {
77 | return mEndTime;
78 | }
79 |
80 | public void setEndTime(Calendar endTime) {
81 | this.mEndTime = endTime;
82 | }
83 |
84 | public String getName() {
85 | return mName;
86 | }
87 |
88 | public void setName(String name) {
89 | this.mName = name;
90 | }
91 |
92 | public int getColor() {
93 | return mColor;
94 | }
95 |
96 | public void setColor(int color) {
97 | this.mColor = color;
98 | }
99 |
100 | public long getId() {
101 | return mId;
102 | }
103 |
104 | public void setId(long id) {
105 | this.mId = id;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Android Week View
2 | =================
3 |
4 | **Android Week View** is an android library to display calendars (week view or day view) within the app. It supports custom styling.
5 |
6 | 
7 |
8 | Features
9 | ------------
10 |
11 | * Week view calendar
12 | * Day view calendar
13 | * Custom styling
14 | * Horizontal and vertical scrolling
15 | * Infinite horizontal scrolling
16 | * Live preview of custom styling in xml preview window
17 |
18 | Who uses it
19 | ---------------
20 |
21 | * [Series Addict](https://play.google.com/store/apps/details?id=com.alamkanak.seriesaddict)
22 | * Using the library? Just [tweet me](https://twitter.com/alamkanak) or [send me an email](mailto:alam.kanak@gmail.com).
23 |
24 | Usage
25 | ---------
26 |
27 | 1. Import the library into your project.
28 | * Grab via maven
29 |
30 | ```xml
31 |
32 | com.github.alamkanak
33 | android-week-view
34 | 1.1.6
35 | aar
36 |
37 | ```
38 | * Grab via gradle
39 |
40 | ```groovy
41 | compile 'com.github.alamkanak:android-week-view:1.1.6'
42 | ```
43 | 2. Add WeekView in your xml layout.
44 |
45 | ```xml
46 |
62 | ```
63 | 3. Write the following code in your java file.
64 |
65 | ```java
66 | // Get a reference for the week view in the layout.
67 | mWeekView = (WeekView) findViewById(R.id.weekView);
68 |
69 | // Set an action when any event is clicked.
70 | mWeekView.setOnEventClickListener(mEventClickListener);
71 |
72 | // The week view has infinite scrolling horizontally. We have to provide the events of a
73 | // month every time the month changes on the week view.
74 | mWeekView.setMonthChangeListener(mMonthChangeListener);
75 |
76 | // Set long press listener for events.
77 | mWeekView.setEventLongPressListener(mEventLongPressListener);
78 | ```
79 | 4. Implement `WeekView.MonthChangeListener`, `WeekView.EventClickListener`, `WeekView.EventLongPressListener` according to your need.
80 |
81 | 5. Provide the events for the `WeekView` in `WeekView.MonthChangeListener.onMonthChange()` callback. Please remember that the calendar preloads events of three consecutive months to enable lag-free scrolling.
82 |
83 | ```java
84 | WeekView.MonthChangeListener mMonthChangeListener = new WeekView.MonthChangeListener() {
85 | @Override
86 | public List onMonthChange(int newYear, int newMonth) {
87 | // Populate the week view with some events.
88 | List events = getEvents(newYear, newMonth);
89 | return events;
90 | }
91 | };
92 | ```
93 |
94 | Customization
95 | -------------------
96 |
97 | You can customize the look of the `WeekView` in xml. Use the following attributes in xml. All these attributes also have getters and setters to enable you to change the style dynamically.
98 |
99 | - `columnGap`
100 | - `dayBackgroundColor`
101 | - `dayNameLength`
102 | - `eventMarginVertical`
103 | - `eventPadding`
104 | - `eventTextColor`
105 | - `eventTextSize`
106 | - `firstDayOfWeek`
107 | - `headerColumnBackground`
108 | - `headerColumnPadding`
109 | - `headerColumnTextColor`
110 | - `headerRowBackgroundColor`
111 | - `headerRowPadding`
112 | - `hourHeight`
113 | - `hourSeparatorColor`
114 | - `hourSeparatorHeight`
115 | - `noOfVisibleDays`
116 | - `overlappingEventGap`
117 | - `textSize`
118 | - `todayBackgroundColor`
119 | - `todayHeaderTextColor`
120 |
121 | Sample
122 | ----------
123 |
124 | There is also a [sample app](https://github.com/alamkanak/Android-Week-View/tree/master/sample) to get you started.
125 |
126 | To do
127 | -------
128 |
129 | * Add event touch feedback selector
130 | * Show events that expand multiple days properly
131 |
132 | Changelog
133 | ---------
134 |
135 | **Version 1.1.6**
136 |
137 | * Added support for events that expands to multiple days
138 |
139 | **Version 1.1.5**
140 |
141 | * A bug related to overlapping events fixed
142 | * You can now programmatically get first and last visible day in the week view
143 |
144 | **Version 1.1.4**
145 |
146 | * Small bug fixed
147 |
148 | **Version 1.1.3**
149 |
150 | * Margins support added for overlapping events
151 |
152 | **Version 1.1.2**
153 |
154 | * Small bugs fixed
155 | * Hour separator inconsistency fixed
156 |
157 | **Version 1.1.1**
158 |
159 | * Overlapping event bug fixed
160 |
161 | **Version 1.1.0**
162 |
163 | * Added support for overlapping events
164 |
165 | License
166 | ----------
167 |
168 | Copyright 2014 Raquib-ul-Alam
169 |
170 | Licensed under the Apache License, Version 2.0 (the "License");
171 | you may not use this file except in compliance with the License.
172 | You may obtain a copy of the License at
173 |
174 | http://www.apache.org/licenses/LICENSE-2.0
175 |
176 | Unless required by applicable law or agreed to in writing, software
177 | distributed under the License is distributed on an "AS IS" BASIS,
178 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
179 | See the License for the specific language governing permissions and
180 | limitations under the License.
181 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.alamkanak.weekview.sample;
2 |
3 | import android.app.Activity;
4 | import android.graphics.RectF;
5 | import android.os.Bundle;
6 | import android.util.TypedValue;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import android.widget.Toast;
10 |
11 | import com.alamkanak.weekview.WeekView;
12 | import com.alamkanak.weekview.WeekViewEvent;
13 |
14 | import java.util.ArrayList;
15 | import java.util.Calendar;
16 | import java.util.List;
17 |
18 |
19 | /**
20 | * Created by Raquib-ul-Alam Kanak on 7/21/2014.
21 | * Website: http://april-shower.com
22 | */
23 | public class MainActivity extends Activity implements WeekView.MonthChangeListener,
24 | WeekView.EventClickListener, WeekView.EventLongPressListener {
25 |
26 | private static final int TYPE_DAY_VIEW = 1;
27 | private static final int TYPE_THREE_DAY_VIEW = 2;
28 | private static final int TYPE_WEEK_VIEW = 3;
29 | private int mWeekViewType = TYPE_THREE_DAY_VIEW;
30 | private WeekView mWeekView;
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 |
37 | // Get a reference for the week view in the layout.
38 | mWeekView = (WeekView) findViewById(R.id.weekView);
39 |
40 | // Show a toast message about the touched event.
41 | mWeekView.setOnEventClickListener(this);
42 |
43 | // The week view has infinite scrolling horizontally. We have to provide the events of a
44 | // month every time the month changes on the week view.
45 | mWeekView.setMonthChangeListener(this);
46 |
47 | // Set long press listener for events.
48 | mWeekView.setEventLongPressListener(this);
49 | }
50 |
51 |
52 | @Override
53 | public boolean onCreateOptionsMenu(Menu menu) {
54 | getMenuInflater().inflate(R.menu.main, menu);
55 | return true;
56 | }
57 |
58 | @Override
59 | public boolean onOptionsItemSelected(MenuItem item) {
60 | int id = item.getItemId();
61 | switch (id){
62 | case R.id.action_today:
63 | mWeekView.goToToday();
64 | return true;
65 | case R.id.action_day_view:
66 | if (mWeekViewType != TYPE_DAY_VIEW) {
67 | item.setChecked(!item.isChecked());
68 | mWeekViewType = TYPE_DAY_VIEW;
69 | mWeekView.setNumberOfVisibleDays(1);
70 |
71 | // Lets change some dimensions to best fit the view.
72 | mWeekView.setColumnGap((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()));
73 | mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
74 | mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
75 | }
76 | return true;
77 | case R.id.action_three_day_view:
78 | if (mWeekViewType != TYPE_THREE_DAY_VIEW) {
79 | item.setChecked(!item.isChecked());
80 | mWeekViewType = TYPE_THREE_DAY_VIEW;
81 | mWeekView.setNumberOfVisibleDays(3);
82 |
83 | // Lets change some dimensions to best fit the view.
84 | mWeekView.setColumnGap((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()));
85 | mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
86 | mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
87 | }
88 | return true;
89 | case R.id.action_week_view:
90 | if (mWeekViewType != TYPE_WEEK_VIEW) {
91 | item.setChecked(!item.isChecked());
92 | mWeekViewType = TYPE_WEEK_VIEW;
93 | mWeekView.setNumberOfVisibleDays(7);
94 |
95 | // Lets change some dimensions to best fit the view.
96 | mWeekView.setColumnGap((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()));
97 | mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics()));
98 | mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics()));
99 | }
100 | return true;
101 | }
102 |
103 | return super.onOptionsItemSelected(item);
104 | }
105 |
106 | @Override
107 | public List onMonthChange(int newYear, int newMonth) {
108 |
109 | // Populate the week view with some events.
110 | List events = new ArrayList();
111 |
112 | Calendar startTime = Calendar.getInstance();
113 | startTime.set(Calendar.HOUR_OF_DAY, 3);
114 | startTime.set(Calendar.MINUTE, 0);
115 | startTime.set(Calendar.MONTH, newMonth-1);
116 | startTime.set(Calendar.YEAR, newYear);
117 | Calendar endTime = (Calendar) startTime.clone();
118 | endTime.add(Calendar.HOUR, 1);
119 | endTime.set(Calendar.MONTH, newMonth-1);
120 | WeekViewEvent event = new WeekViewEvent(1, getEventTitle(startTime), startTime, endTime);
121 | event.setColor(getResources().getColor(R.color.event_color_01));
122 | events.add(event);
123 |
124 | startTime = Calendar.getInstance();
125 | startTime.set(Calendar.HOUR_OF_DAY, 3);
126 | startTime.set(Calendar.MINUTE, 30);
127 | startTime.set(Calendar.MONTH, newMonth-1);
128 | startTime.set(Calendar.YEAR, newYear);
129 | endTime = (Calendar) startTime.clone();
130 | endTime.set(Calendar.HOUR_OF_DAY, 4);
131 | endTime.set(Calendar.MINUTE, 30);
132 | endTime.set(Calendar.MONTH, newMonth-1);
133 | event = new WeekViewEvent(10, getEventTitle(startTime), startTime, endTime);
134 | event.setColor(getResources().getColor(R.color.event_color_02));
135 | events.add(event);
136 |
137 | startTime = Calendar.getInstance();
138 | startTime.set(Calendar.HOUR_OF_DAY, 4);
139 | startTime.set(Calendar.MINUTE, 20);
140 | startTime.set(Calendar.MONTH, newMonth-1);
141 | startTime.set(Calendar.YEAR, newYear);
142 | endTime = (Calendar) startTime.clone();
143 | endTime.set(Calendar.HOUR_OF_DAY, 5);
144 | endTime.set(Calendar.MINUTE, 0);
145 | event = new WeekViewEvent(10, getEventTitle(startTime), startTime, endTime);
146 | event.setColor(getResources().getColor(R.color.event_color_03));
147 | events.add(event);
148 |
149 | startTime = Calendar.getInstance();
150 | startTime.set(Calendar.HOUR_OF_DAY, 5);
151 | startTime.set(Calendar.MINUTE, 30);
152 | startTime.set(Calendar.MONTH, newMonth-1);
153 | startTime.set(Calendar.YEAR, newYear);
154 | endTime = (Calendar) startTime.clone();
155 | endTime.add(Calendar.HOUR_OF_DAY, 2);
156 | endTime.set(Calendar.MONTH, newMonth-1);
157 | event = new WeekViewEvent(2, getEventTitle(startTime), startTime, endTime);
158 | event.setColor(getResources().getColor(R.color.event_color_02));
159 | events.add(event);
160 |
161 | startTime = Calendar.getInstance();
162 | startTime.set(Calendar.HOUR_OF_DAY, 5);
163 | startTime.set(Calendar.MINUTE, 0);
164 | startTime.set(Calendar.MONTH, newMonth-1);
165 | startTime.set(Calendar.YEAR, newYear);
166 | startTime.add(Calendar.DATE, 1);
167 | endTime = (Calendar) startTime.clone();
168 | endTime.add(Calendar.HOUR_OF_DAY, 3);
169 | endTime.set(Calendar.MONTH, newMonth - 1);
170 | event = new WeekViewEvent(3, getEventTitle(startTime), startTime, endTime);
171 | event.setColor(getResources().getColor(R.color.event_color_03));
172 | events.add(event);
173 |
174 | startTime = Calendar.getInstance();
175 | startTime.set(Calendar.DAY_OF_MONTH, 15);
176 | startTime.set(Calendar.HOUR_OF_DAY, 3);
177 | startTime.set(Calendar.MINUTE, 0);
178 | startTime.set(Calendar.MONTH, newMonth-1);
179 | startTime.set(Calendar.YEAR, newYear);
180 | endTime = (Calendar) startTime.clone();
181 | endTime.add(Calendar.HOUR_OF_DAY, 3);
182 | event = new WeekViewEvent(4, getEventTitle(startTime), startTime, endTime);
183 | event.setColor(getResources().getColor(R.color.event_color_04));
184 | events.add(event);
185 |
186 | startTime = Calendar.getInstance();
187 | startTime.set(Calendar.DAY_OF_MONTH, 1);
188 | startTime.set(Calendar.HOUR_OF_DAY, 3);
189 | startTime.set(Calendar.MINUTE, 0);
190 | startTime.set(Calendar.MONTH, newMonth-1);
191 | startTime.set(Calendar.YEAR, newYear);
192 | endTime = (Calendar) startTime.clone();
193 | endTime.add(Calendar.HOUR_OF_DAY, 3);
194 | event = new WeekViewEvent(5, getEventTitle(startTime), startTime, endTime);
195 | event.setColor(getResources().getColor(R.color.event_color_01));
196 | events.add(event);
197 |
198 | startTime = Calendar.getInstance();
199 | startTime.set(Calendar.DAY_OF_MONTH, startTime.getActualMaximum(Calendar.DAY_OF_MONTH));
200 | startTime.set(Calendar.HOUR_OF_DAY, 15);
201 | startTime.set(Calendar.MINUTE, 0);
202 | startTime.set(Calendar.MONTH, newMonth-1);
203 | startTime.set(Calendar.YEAR, newYear);
204 | endTime = (Calendar) startTime.clone();
205 | endTime.add(Calendar.HOUR_OF_DAY, 3);
206 | event = new WeekViewEvent(5, getEventTitle(startTime), startTime, endTime);
207 | event.setColor(getResources().getColor(R.color.event_color_02));
208 | events.add(event);
209 |
210 | return events;
211 | }
212 |
213 |
214 |
215 | private String getEventTitle(Calendar time) {
216 | return String.format("Event of %02d:%02d %s/%d", time.get(Calendar.HOUR_OF_DAY), time.get(Calendar.MINUTE), time.get(Calendar.MONTH)+1, time.get(Calendar.DAY_OF_MONTH));
217 | }
218 |
219 | @Override
220 | public void onEventClick(WeekViewEvent event, RectF eventRect) {
221 | Toast.makeText(MainActivity.this, "Clicked " + event.getName(), Toast.LENGTH_SHORT).show();
222 | }
223 |
224 | @Override
225 | public void onEventLongPress(WeekViewEvent event, RectF eventRect) {
226 | Toast.makeText(MainActivity.this, "Long pressed event: " + event.getName(), Toast.LENGTH_SHORT).show();
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/library/src/main/java/com/alamkanak/weekview/WeekView.java:
--------------------------------------------------------------------------------
1 | package com.alamkanak.weekview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.PointF;
9 | import android.graphics.Rect;
10 | import android.graphics.RectF;
11 | import android.graphics.Typeface;
12 | import android.support.v4.view.GestureDetectorCompat;
13 | import android.support.v4.view.ViewCompat;
14 | import android.text.Layout;
15 | import android.text.StaticLayout;
16 | import android.text.TextPaint;
17 | import android.text.TextUtils;
18 | import android.util.AttributeSet;
19 | import android.util.TypedValue;
20 | import android.view.GestureDetector;
21 | import android.view.HapticFeedbackConstants;
22 | import android.view.MotionEvent;
23 | import android.view.SoundEffectConstants;
24 | import android.view.View;
25 | import android.widget.OverScroller;
26 | import android.widget.Scroller;
27 |
28 | import java.util.ArrayList;
29 | import java.util.Calendar;
30 | import java.util.Collections;
31 | import java.util.Comparator;
32 | import java.util.List;
33 |
34 | /**
35 | * Created by Raquib-ul-Alam Kanak on 7/21/2014.
36 | * Website: http://april-shower.com
37 | */
38 | public class WeekView extends View {
39 |
40 | public static final int LENGTH_SHORT = 1;
41 | public static final int LENGTH_LONG = 2;
42 | private final Context mContext;
43 | private Calendar mToday;
44 | private Calendar mStartDate;
45 | private Paint mTimeTextPaint;
46 | private float mTimeTextWidth;
47 | private float mTimeTextHeight;
48 | private Paint mHeaderTextPaint;
49 | private float mHeaderTextHeight;
50 | private GestureDetectorCompat mGestureDetector;
51 | private OverScroller mScroller;
52 | private PointF mCurrentOrigin = new PointF(0f, 0f);
53 | private Direction mCurrentScrollDirection = Direction.NONE;
54 | private Paint mHeaderBackgroundPaint;
55 | private float mWidthPerDay;
56 | private Paint mDayBackgroundPaint;
57 | private Paint mHourSeparatorPaint;
58 | private float mHeaderMarginBottom;
59 | private Paint mTodayBackgroundPaint;
60 | private Paint mTodayHeaderTextPaint;
61 | private Paint mEventBackgroundPaint;
62 | private float mHeaderColumnWidth;
63 | private List mEventRects;
64 | private TextPaint mEventTextPaint;
65 | private Paint mHeaderColumnBackgroundPaint;
66 | private Scroller mStickyScroller;
67 | private int mFetchedMonths[] = new int[3];
68 | private boolean mRefreshEvents = false;
69 | private float mDistanceY = 0;
70 | private float mDistanceX = 0;
71 | private Direction mCurrentFlingDirection = Direction.NONE;
72 |
73 | // Attributes and their default values.
74 | private int mHourHeight = 50;
75 | private int mColumnGap = 10;
76 | private int mFirstDayOfWeek = Calendar.MONDAY;
77 | private int mTextSize = 12;
78 | private int mHeaderColumnPadding = 10;
79 | private int mHeaderColumnTextColor = Color.BLACK;
80 | private int mNumberOfVisibleDays = 3;
81 | private int mHeaderRowPadding = 10;
82 | private int mHeaderRowBackgroundColor = Color.WHITE;
83 | private int mDayBackgroundColor = Color.rgb(245, 245, 245);
84 | private int mHourSeparatorColor = Color.rgb(230, 230, 230);
85 | private int mTodayBackgroundColor = Color.rgb(239, 247, 254);
86 | private int mHourSeparatorHeight = 2;
87 | private int mTodayHeaderTextColor = Color.rgb(39, 137, 228);
88 | private int mEventTextSize = 12;
89 | private int mEventTextColor = Color.BLACK;
90 | private int mEventPadding = 8;
91 | private int mHeaderColumnBackgroundColor = Color.WHITE;
92 | private int mDefaultEventColor;
93 | private boolean mIsFirstDraw = true;
94 | private int mDayNameLength = LENGTH_LONG;
95 | private int mOverlappingEventGap = 0;
96 | private int mEventMarginVertical = 0;
97 | private Calendar mFirstVisibleDay;
98 | private Calendar mLastVisibleDay;
99 |
100 | // Listeners.
101 | private EventClickListener mEventClickListener;
102 | private EventLongPressListener mEventLongPressListener;
103 | private MonthChangeListener mMonthChangeListener;
104 | private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
105 |
106 | @Override
107 | public boolean onDown(MotionEvent e) {
108 | mScroller.forceFinished(true);
109 | mStickyScroller.forceFinished(true);
110 | return true;
111 | }
112 |
113 | @Override
114 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
115 | if (mCurrentScrollDirection == Direction.NONE) {
116 | if (Math.abs(distanceX) > Math.abs(distanceY)){
117 | mCurrentScrollDirection = Direction.HORIZONTAL;
118 | mCurrentFlingDirection = Direction.HORIZONTAL;
119 | }
120 | else {
121 | mCurrentFlingDirection = Direction.VERTICAL;
122 | mCurrentScrollDirection = Direction.VERTICAL;
123 | }
124 | }
125 | mDistanceX = distanceX;
126 | mDistanceY = distanceY;
127 | invalidate();
128 | return true;
129 | }
130 |
131 | @Override
132 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
133 | mScroller.forceFinished(true);
134 | mStickyScroller.forceFinished(true);
135 |
136 | if (mCurrentFlingDirection == Direction.HORIZONTAL){
137 | mScroller.fling((int) mCurrentOrigin.x, 0, (int) velocityX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
138 | }
139 | else if (mCurrentFlingDirection == Direction.VERTICAL){
140 | mScroller.fling(0, (int) mCurrentOrigin.y, 0, (int) velocityY, 0, 0, (int) -(mHourHeight * 24 + mHeaderTextHeight + mHeaderRowPadding * 2 - getHeight()), 0);
141 | }
142 |
143 | ViewCompat.postInvalidateOnAnimation(WeekView.this);
144 | return true;
145 | }
146 |
147 |
148 | @Override
149 | public boolean onSingleTapConfirmed(MotionEvent e) {
150 | if (mEventRects != null && mEventClickListener != null) {
151 | List reversedEventRects = mEventRects;
152 | Collections.reverse(reversedEventRects);
153 | for (EventRect event : reversedEventRects) {
154 | if (event.rectF != null && e.getX() > event.rectF.left && e.getX() < event.rectF.right && e.getY() > event.rectF.top && e.getY() < event.rectF.bottom) {
155 | mEventClickListener.onEventClick(event.originalEvent, event.rectF);
156 | playSoundEffect(SoundEffectConstants.CLICK);
157 | break;
158 | }
159 | }
160 | }
161 | return super.onSingleTapConfirmed(e);
162 | }
163 |
164 | @Override
165 | public void onLongPress(MotionEvent e) {
166 | super.onLongPress(e);
167 |
168 | if (mEventLongPressListener != null && mEventRects != null) {
169 | List reversedEventRects = mEventRects;
170 | Collections.reverse(reversedEventRects);
171 | for (EventRect event : reversedEventRects) {
172 | if (event.rectF != null && e.getX() > event.rectF.left && e.getX() < event.rectF.right && e.getY() > event.rectF.top && e.getY() < event.rectF.bottom) {
173 | mEventLongPressListener.onEventLongPress(event.originalEvent, event.rectF);
174 | performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
175 | break;
176 | }
177 | }
178 | }
179 | }
180 | };
181 |
182 |
183 | private enum Direction {
184 | NONE, HORIZONTAL, VERTICAL
185 | }
186 |
187 | public WeekView(Context context) {
188 | this(context, null);
189 | }
190 |
191 | public WeekView(Context context, AttributeSet attrs) {
192 | this(context, attrs, 0);
193 | }
194 |
195 | public WeekView(Context context, AttributeSet attrs, int defStyleAttr) {
196 | super(context, attrs, defStyleAttr);
197 |
198 | // Hold references.
199 | mContext = context;
200 |
201 | // Get the attribute values (if any).
202 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.WeekView, 0, 0);
203 | try {
204 | mFirstDayOfWeek = a.getInteger(R.styleable.WeekView_firstDayOfWeek, mFirstDayOfWeek);
205 | mHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourHeight, mHourHeight);
206 | mTextSize = a.getDimensionPixelSize(R.styleable.WeekView_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize, context.getResources().getDisplayMetrics()));
207 | mHeaderColumnPadding = a.getDimensionPixelSize(R.styleable.WeekView_headerColumnPadding, mHeaderColumnPadding);
208 | mColumnGap = a.getDimensionPixelSize(R.styleable.WeekView_columnGap, mColumnGap);
209 | mHeaderColumnTextColor = a.getColor(R.styleable.WeekView_headerColumnTextColor, mHeaderColumnTextColor);
210 | mNumberOfVisibleDays = a.getInteger(R.styleable.WeekView_noOfVisibleDays, mNumberOfVisibleDays);
211 | mHeaderRowPadding = a.getDimensionPixelSize(R.styleable.WeekView_headerRowPadding, mHeaderRowPadding);
212 | mHeaderRowBackgroundColor = a.getColor(R.styleable.WeekView_headerRowBackgroundColor, mHeaderRowBackgroundColor);
213 | mDayBackgroundColor = a.getColor(R.styleable.WeekView_dayBackgroundColor, mDayBackgroundColor);
214 | mHourSeparatorColor = a.getColor(R.styleable.WeekView_hourSeparatorColor, mHourSeparatorColor);
215 | mTodayBackgroundColor = a.getColor(R.styleable.WeekView_todayBackgroundColor, mTodayBackgroundColor);
216 | mHourSeparatorHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourSeparatorHeight, mHourSeparatorHeight);
217 | mTodayHeaderTextColor = a.getColor(R.styleable.WeekView_todayHeaderTextColor, mTodayHeaderTextColor);
218 | mEventTextSize = a.getDimensionPixelSize(R.styleable.WeekView_eventTextSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mEventTextSize, context.getResources().getDisplayMetrics()));
219 | mEventTextColor = a.getColor(R.styleable.WeekView_eventTextColor, mEventTextColor);
220 | mEventPadding = a.getDimensionPixelSize(R.styleable.WeekView_hourSeparatorHeight, mEventPadding);
221 | mHeaderColumnBackgroundColor = a.getColor(R.styleable.WeekView_headerColumnBackground, mHeaderColumnBackgroundColor);
222 | mDayNameLength = a.getInteger(R.styleable.WeekView_dayNameLength, mDayNameLength);
223 | mOverlappingEventGap = a.getDimensionPixelSize(R.styleable.WeekView_overlappingEventGap, mOverlappingEventGap);
224 | mEventMarginVertical = a.getDimensionPixelSize(R.styleable.WeekView_eventMarginVertical, mEventMarginVertical);
225 | } finally {
226 | a.recycle();
227 | }
228 |
229 | init();
230 | }
231 |
232 | private void init() {
233 | // Get the date today.
234 | mToday = Calendar.getInstance();
235 | mToday.set(Calendar.HOUR_OF_DAY, 0);
236 | mToday.set(Calendar.MINUTE, 0);
237 | mToday.set(Calendar.SECOND, 0);
238 |
239 | // Scrolling initialization.
240 | mGestureDetector = new GestureDetectorCompat(mContext, mGestureListener);
241 | mScroller = new OverScroller(mContext);
242 | mStickyScroller = new Scroller(mContext);
243 |
244 | // Measure settings for time column.
245 | mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
246 | mTimeTextPaint.setTextAlign(Paint.Align.RIGHT);
247 | mTimeTextPaint.setTextSize(mTextSize);
248 | mTimeTextPaint.setColor(mHeaderColumnTextColor);
249 | Rect rect = new Rect();
250 | mTimeTextPaint.getTextBounds("00 PM", 0, "00 PM".length(), rect);
251 | mTimeTextWidth = mTimeTextPaint.measureText("00 PM");
252 | mTimeTextHeight = rect.height();
253 | mHeaderMarginBottom = mTimeTextHeight / 2;
254 |
255 | // Measure settings for header row.
256 | mHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
257 | mHeaderTextPaint.setColor(mHeaderColumnTextColor);
258 | mHeaderTextPaint.setTextAlign(Paint.Align.CENTER);
259 | mHeaderTextPaint.setTextSize(mTextSize);
260 | mHeaderTextPaint.getTextBounds("00 PM", 0, "00 PM".length(), rect);
261 | mHeaderTextHeight = rect.height();
262 | mHeaderTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
263 |
264 | // Prepare header background paint.
265 | mHeaderBackgroundPaint = new Paint();
266 | mHeaderBackgroundPaint.setColor(mHeaderRowBackgroundColor);
267 |
268 | // Prepare day background color paint.
269 | mDayBackgroundPaint = new Paint();
270 | mDayBackgroundPaint.setColor(mDayBackgroundColor);
271 |
272 | // Prepare hour separator color paint.
273 | mHourSeparatorPaint = new Paint();
274 | mHourSeparatorPaint.setStyle(Paint.Style.STROKE);
275 | mHourSeparatorPaint.setStrokeWidth(mHourSeparatorHeight);
276 | mHourSeparatorPaint.setColor(mHourSeparatorColor);
277 |
278 | // Prepare today background color paint.
279 | mTodayBackgroundPaint = new Paint();
280 | mTodayBackgroundPaint.setColor(mTodayBackgroundColor);
281 |
282 | // Prepare today header text color paint.
283 | mTodayHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
284 | mTodayHeaderTextPaint.setTextAlign(Paint.Align.CENTER);
285 | mTodayHeaderTextPaint.setTextSize(mTextSize);
286 | mTodayHeaderTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
287 | mTodayHeaderTextPaint.setColor(mTodayHeaderTextColor);
288 |
289 | // Prepare event background color.
290 | mEventBackgroundPaint = new Paint();
291 | mEventBackgroundPaint.setColor(Color.rgb(174, 208, 238));
292 |
293 | // Prepare header column background color.
294 | mHeaderColumnBackgroundPaint = new Paint();
295 | mHeaderColumnBackgroundPaint.setColor(mHeaderColumnBackgroundColor);
296 |
297 | // Prepare event text size and color.
298 | mEventTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);
299 | mEventTextPaint.setStyle(Paint.Style.FILL);
300 | mEventTextPaint.setColor(mEventTextColor);
301 | mEventTextPaint.setTextSize(mEventTextSize);
302 | mStartDate = (Calendar) mToday.clone();
303 |
304 | // Set default event color.
305 | mDefaultEventColor = Color.parseColor("#9fc6e7");
306 | }
307 |
308 | @Override
309 | protected void onDraw(Canvas canvas) {
310 | super.onDraw(canvas);
311 |
312 | // Draw the header row.
313 | drawHeaderRowAndEvents(canvas);
314 |
315 | // Draw the time column and all the axes/separators.
316 | drawTimeColumnAndAxes(canvas);
317 |
318 | // Hide everything in the first cell (top left corner).
319 | canvas.drawRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderTextHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint);
320 |
321 | // Hide anything that is in the bottom margin of the header row.
322 | canvas.drawRect(mHeaderColumnWidth, mHeaderTextHeight + mHeaderRowPadding * 2, getWidth(), mHeaderRowPadding * 2 + mHeaderTextHeight + mHeaderMarginBottom + mTimeTextHeight/2 - mHourSeparatorHeight / 2, mHeaderColumnBackgroundPaint);
323 | }
324 |
325 | private void drawTimeColumnAndAxes(Canvas canvas) {
326 | // Do not let the view go above/below the limit due to scrolling. Set the max and min limit of the scroll.
327 | if (mCurrentScrollDirection == Direction.VERTICAL) {
328 | if (mCurrentOrigin.y - mDistanceY > 0) mCurrentOrigin.y = 0;
329 | else if (mCurrentOrigin.y - mDistanceY < -(mHourHeight * 24 + mHeaderTextHeight + mHeaderRowPadding * 2 - getHeight())) mCurrentOrigin.y = -(mHourHeight * 24 + mHeaderTextHeight + mHeaderRowPadding * 2 - getHeight());
330 | else mCurrentOrigin.y -= mDistanceY;
331 | }
332 |
333 | // Draw the background color for the header column.
334 | canvas.drawRect(0, mHeaderTextHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight(), mHeaderColumnBackgroundPaint);
335 |
336 | for (int i = 0; i < 24; i++) {
337 | float top = mHeaderTextHeight + mHeaderRowPadding * 2 + mCurrentOrigin.y + mHourHeight * i + mHeaderMarginBottom;
338 |
339 | // Draw the text if its y position is not outside of the visible area. The pivot point of the text is the point at the bottom-right corner.
340 | if (top < getHeight()) canvas.drawText(getTimeString(i), mTimeTextWidth + mHeaderColumnPadding, top + mTimeTextHeight, mTimeTextPaint);
341 | }
342 | }
343 |
344 | private void drawHeaderRowAndEvents(Canvas canvas) {
345 | // Calculate the available width for each day.
346 | mHeaderColumnWidth = mTimeTextWidth + mHeaderColumnPadding *2;
347 | mWidthPerDay = getWidth() - mHeaderColumnWidth - mColumnGap * (mNumberOfVisibleDays - 1);
348 | mWidthPerDay = mWidthPerDay/mNumberOfVisibleDays;
349 |
350 | // If the week view is being drawn for the first time, then consider the first day of week.
351 | if (mIsFirstDraw && mNumberOfVisibleDays >= 7) {
352 | if (mToday.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
353 | int difference = 7 + (mToday.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek);
354 | mCurrentOrigin.x += (mWidthPerDay + mColumnGap) * difference;
355 | }
356 | mIsFirstDraw = false;
357 | }
358 |
359 | // Consider scroll offset.
360 | if (mCurrentScrollDirection == Direction.HORIZONTAL) mCurrentOrigin.x -= mDistanceX;
361 | int leftDaysWithGaps = (int) -(Math.ceil(mCurrentOrigin.x / (mWidthPerDay + mColumnGap)));
362 | float startFromPixel = mCurrentOrigin.x + (mWidthPerDay + mColumnGap) * leftDaysWithGaps +
363 | mHeaderColumnWidth;
364 | float startPixel = startFromPixel;
365 |
366 | // Prepare to iterate for each day.
367 | Calendar day = (Calendar) mToday.clone();
368 | day.add(Calendar.HOUR, 6);
369 |
370 | // Prepare to iterate for each hour to draw the hour lines.
371 | int lineCount = (int) ((getHeight() - mHeaderTextHeight - mHeaderRowPadding * 2 -
372 | mHeaderMarginBottom) / mHourHeight) + 1;
373 | lineCount = (lineCount) * (mNumberOfVisibleDays+1);
374 | float[] hourLines = new float[lineCount * 4];
375 |
376 | // Clear the cache for events rectangles.
377 | if (mEventRects != null) {
378 | for (EventRect eventRect: mEventRects) {
379 | eventRect.rectF = null;
380 | }
381 | }
382 |
383 | // Iterate through each day.
384 | mFirstVisibleDay = (Calendar) mToday.clone();
385 | mFirstVisibleDay.add(Calendar.DATE, leftDaysWithGaps);
386 | for (int dayNumber = leftDaysWithGaps + 1;
387 | dayNumber <= leftDaysWithGaps + mNumberOfVisibleDays + 1;
388 | dayNumber++) {
389 |
390 | // Check if the day is today.
391 | day = (Calendar) mToday.clone();
392 | mLastVisibleDay = (Calendar) day.clone();
393 | day.add(Calendar.DATE, dayNumber - 1);
394 | mLastVisibleDay.add(Calendar.DATE, dayNumber - 2);
395 | boolean sameDay = isSameDay(day, mToday);
396 |
397 | // Get more events if necessary. We want to store the events 3 months beforehand. Get
398 | // events only when it is the first iteration of the loop.
399 | if (mEventRects == null || mRefreshEvents || (dayNumber == leftDaysWithGaps + 1 && mFetchedMonths[1] != day.get(Calendar.MONTH)+1 && day.get(Calendar.DAY_OF_MONTH) == 15)) {
400 | getMoreEvents(day);
401 | mRefreshEvents = false;
402 | }
403 |
404 | // Draw background color for each day.
405 | float start = (startPixel < mHeaderColumnWidth ? mHeaderColumnWidth : startPixel);
406 | if (mWidthPerDay + startPixel - start> 0)
407 | canvas.drawRect(start, mHeaderTextHeight + mHeaderRowPadding * 2 + mTimeTextHeight/2 + mHeaderMarginBottom, startPixel + mWidthPerDay, getHeight(), sameDay ? mTodayBackgroundPaint : mDayBackgroundPaint);
408 |
409 | // Prepare the separator lines for hours.
410 | int i = 0;
411 | for (int hourNumber = 0; hourNumber < 24; hourNumber++) {
412 | float top = mHeaderTextHeight + mHeaderRowPadding * 2 + mCurrentOrigin.y + mHourHeight * hourNumber + mTimeTextHeight/2 + mHeaderMarginBottom;
413 | if (top > mHeaderTextHeight + mHeaderRowPadding * 2 + mTimeTextHeight/2 + mHeaderMarginBottom - mHourSeparatorHeight && top < getHeight() && startPixel + mWidthPerDay - start > 0){
414 | hourLines[i * 4] = start;
415 | hourLines[i * 4 + 1] = top;
416 | hourLines[i * 4 + 2] = startPixel + mWidthPerDay;
417 | hourLines[i * 4 + 3] = top;
418 | i++;
419 | }
420 | }
421 |
422 | // Draw the lines for hours.
423 | canvas.drawLines(hourLines, mHourSeparatorPaint);
424 |
425 | // Draw the events.
426 | drawEvents(day, startPixel, canvas);
427 |
428 | // In the next iteration, start from the next day.
429 | startPixel += mWidthPerDay + mColumnGap;
430 | }
431 |
432 | // Draw the header background.
433 | canvas.drawRect(0, 0, getWidth(), mHeaderTextHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint);
434 |
435 | // Draw the header row texts.
436 | startPixel = startFromPixel;
437 | for (int dayNumber=leftDaysWithGaps+1; dayNumber <= leftDaysWithGaps + mNumberOfVisibleDays + 1; dayNumber++) {
438 | // Check if the day is today.
439 | day = (Calendar) mToday.clone();
440 | day.add(Calendar.DATE, dayNumber - 1);
441 | boolean sameDay = isSameDay(day, mToday);
442 |
443 | // Draw the day labels.
444 | String dayLabel = String.format("%s %d/%02d", getDayName(day), day.get(Calendar.MONTH) + 1, day.get(Calendar.DAY_OF_MONTH));
445 | canvas.drawText(dayLabel, startPixel + mWidthPerDay / 2, mHeaderTextHeight + mHeaderRowPadding, sameDay ? mTodayHeaderTextPaint : mHeaderTextPaint);
446 | startPixel += mWidthPerDay + mColumnGap;
447 | }
448 |
449 | }
450 |
451 | /**
452 | * Draw all the events of a particular day.
453 | * @param date The day.
454 | * @param startFromPixel The left position of the day area. The events will never go any left from this value.
455 | * @param canvas The canvas to draw upon.
456 | */
457 | private void drawEvents(Calendar date, float startFromPixel, Canvas canvas) {
458 | if (mEventRects != null && mEventRects.size() > 0) {
459 | for (int i = 0; i < mEventRects.size(); i++) {
460 | if (isSameDay(mEventRects.get(i).event.getStartTime(), date)) {
461 |
462 | // Calculate top.
463 | float top = mHourHeight * 24 * mEventRects.get(i).top / 1440 + mCurrentOrigin.y + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2 + mEventMarginVertical;
464 | float originalTop = top;
465 | if (top < mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2)
466 | top = mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2;
467 |
468 | // Calculate bottom.
469 | float bottom = mEventRects.get(i).bottom;
470 | bottom = mHourHeight * 24 * bottom / 1440 + mCurrentOrigin.y + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2 - mEventMarginVertical;
471 |
472 | // Calculate left and right.
473 | float left = startFromPixel + mEventRects.get(i).left * mWidthPerDay;
474 | if (left < startFromPixel)
475 | left += mOverlappingEventGap;
476 | float originalLeft = left;
477 | float right = left + mEventRects.get(i).width * mWidthPerDay;
478 | if (right < startFromPixel + mWidthPerDay)
479 | right -= mOverlappingEventGap;
480 | if (left < mHeaderColumnWidth) left = mHeaderColumnWidth;
481 |
482 | // Draw the event and the event name on top of it.
483 | RectF eventRectF = new RectF(left, top, right, bottom);
484 | if (bottom > mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2 && left < right &&
485 | eventRectF.right > mHeaderColumnWidth &&
486 | eventRectF.left < getWidth() &&
487 | eventRectF.bottom > mHeaderTextHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom &&
488 | eventRectF.top < getHeight() &&
489 | left < right
490 | ) {
491 | mEventRects.get(i).rectF = eventRectF;
492 | mEventBackgroundPaint.setColor(mEventRects.get(i).event.getColor() == 0 ? mDefaultEventColor : mEventRects.get(i).event.getColor());
493 | canvas.drawRect(mEventRects.get(i).rectF, mEventBackgroundPaint);
494 | drawText(mEventRects.get(i).event.getName(), mEventRects.get(i).rectF, canvas, originalTop, originalLeft);
495 | }
496 | else
497 | mEventRects.get(i).rectF = null;
498 | }
499 | }
500 | }
501 | }
502 |
503 |
504 | /**
505 | * Draw the name of the event on top of the event rectangle.
506 | * @param text The text to draw.
507 | * @param rect The rectangle on which the text is to be drawn.
508 | * @param canvas The canvas to draw upon.
509 | * @param originalTop The original top position of the rectangle. The rectangle may have some of its portion outside of the visible area.
510 | * @param originalLeft The original left position of the rectangle. The rectangle may have some of its portion outside of the visible area.
511 | */
512 | private void drawText(String text, RectF rect, Canvas canvas, float originalTop, float originalLeft) {
513 | if (rect.right - rect.left - mEventPadding * 2 < 0) return;
514 |
515 | // Get text dimensions
516 | StaticLayout textLayout = new StaticLayout(text, mEventTextPaint, (int) (rect.right - originalLeft - mEventPadding * 2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
517 |
518 | // Crop height
519 | int availableHeight = (int) (rect.bottom - originalTop - mEventPadding * 2);
520 | int lineHeight = textLayout.getHeight() / textLayout.getLineCount();
521 | if (lineHeight < availableHeight && textLayout.getHeight() > rect.height() - mEventPadding * 2) {
522 | int lineCount = textLayout.getLineCount();
523 | int availableLineCount = (int) Math.floor(lineCount * availableHeight / textLayout.getHeight());
524 | float widthAvailable = (rect.right - originalLeft - mEventPadding * 2) * availableLineCount;
525 | textLayout = new StaticLayout(TextUtils.ellipsize(text, mEventTextPaint, widthAvailable, TextUtils.TruncateAt.END), mEventTextPaint, (int) (rect.right - originalLeft - mEventPadding * 2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
526 | }
527 | else if (lineHeight >= availableHeight) {
528 | int width = (int) (rect.right - originalLeft - mEventPadding * 2);
529 | textLayout = new StaticLayout(TextUtils.ellipsize(text, mEventTextPaint, width, TextUtils.TruncateAt.END), mEventTextPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 1.0f, false);
530 | }
531 |
532 | // Draw text
533 | canvas.save();
534 | canvas.translate(originalLeft + mEventPadding, originalTop + mEventPadding);
535 | textLayout.draw(canvas);
536 | canvas.restore();
537 | }
538 |
539 |
540 | /**
541 | * A class to hold reference to the events and their visual representation. An EventRect is
542 | * actually the rectangle that is drawn on the calendar for a given event. There may be more
543 | * than one rectangle for a single event (an event that expands more than one day). In that
544 | * case two instances of the EventRect will be used for a single event. The given event will be
545 | * stored in "originalEvent". But the event that corresponds to rectangle the rectangle
546 | * instance will be stored in "event".
547 | */
548 | private class EventRect {
549 | public WeekViewEvent event;
550 | public WeekViewEvent originalEvent;
551 | public RectF rectF;
552 | public float left;
553 | public float width;
554 | public float top;
555 | public float bottom;
556 |
557 | /**
558 | * Create a new instance of event rect. An EventRect is actually the rectangle that is drawn
559 | * on the calendar for a given event. There may be more than one rectangle for a single
560 | * event (an event that expands more than one day). In that case two instances of the
561 | * EventRect will be used for a single event. The given event will be stored in
562 | * "originalEvent". But the event that corresponds to rectangle the rectangle instance will
563 | * be stored in "event".
564 | * @param event Represents the event which this instance of rectangle represents.
565 | * @param originalEvent The original event that was passed by the user.
566 | * @param rectF The rectangle.
567 | */
568 | public EventRect(WeekViewEvent event, WeekViewEvent originalEvent, RectF rectF) {
569 | this.event = event;
570 | this.rectF = rectF;
571 | this.originalEvent = originalEvent;
572 | }
573 | }
574 |
575 |
576 | /**
577 | * Gets more events of one/more month(s) if necessary. This method is called when the user is
578 | * scrolling the week view. The week view stores the events of three months: the visible month,
579 | * the previous month, the next month.
580 | * @param day The day where the user is currently is.
581 | */
582 | private void getMoreEvents(Calendar day) {
583 |
584 | // Delete all events if its not current month +- 1.
585 | deleteFarMonths(day);
586 |
587 | // Get more events if the month is changed.
588 | if (mEventRects == null)
589 | mEventRects = new ArrayList();
590 | if (mMonthChangeListener == null && !isInEditMode())
591 | throw new IllegalStateException("You must provide a MonthChangeListener");
592 |
593 | // If a refresh was requested then reset some variables.
594 | if (mRefreshEvents) {
595 | mEventRects.clear();
596 | mFetchedMonths = new int[3];
597 | }
598 |
599 | // Get events of previous month.
600 | int previousMonth = (day.get(Calendar.MONTH) == 0?12:day.get(Calendar.MONTH));
601 | int nextMonth = (day.get(Calendar.MONTH)+2 == 13 ?1:day.get(Calendar.MONTH)+2);
602 | int[] lastFetchedMonth = mFetchedMonths.clone();
603 | if (mFetchedMonths[0] < 1 || mFetchedMonths[0] != previousMonth || mRefreshEvents) {
604 | if (!containsValue(lastFetchedMonth, previousMonth) && !isInEditMode()){
605 | List events = mMonthChangeListener.onMonthChange((previousMonth==12)?day.get(Calendar.YEAR)-1:day.get(Calendar.YEAR), previousMonth);
606 | sortEvents(events);
607 | for (WeekViewEvent event: events) {
608 | cacheEvent(event);
609 | }
610 | }
611 | mFetchedMonths[0] = previousMonth;
612 | }
613 |
614 | // Get events of this month.
615 | if (mFetchedMonths[1] < 1 || mFetchedMonths[1] != day.get(Calendar.MONTH)+1 || mRefreshEvents) {
616 | if (!containsValue(lastFetchedMonth, day.get(Calendar.MONTH)+1) && !isInEditMode()) {
617 | List events = mMonthChangeListener.onMonthChange(day.get(Calendar.YEAR), day.get(Calendar.MONTH) + 1);
618 | sortEvents(events);
619 | for (WeekViewEvent event : events) {
620 | cacheEvent(event);
621 | }
622 | }
623 | mFetchedMonths[1] = day.get(Calendar.MONTH)+1;
624 | }
625 |
626 | // Get events of next month.
627 | if (mFetchedMonths[2] < 1 || mFetchedMonths[2] != nextMonth || mRefreshEvents) {
628 | if (!containsValue(lastFetchedMonth, nextMonth) && !isInEditMode()) {
629 | List events = mMonthChangeListener.onMonthChange(nextMonth == 1 ? day.get(Calendar.YEAR) + 1 : day.get(Calendar.YEAR), nextMonth);
630 | sortEvents(events);
631 | for (WeekViewEvent event : events) {
632 | cacheEvent(event);
633 | }
634 | }
635 | mFetchedMonths[2] = nextMonth;
636 | }
637 |
638 | // Prepare to calculate positions of each events.
639 | ArrayList tempEvents = new ArrayList(mEventRects);
640 | mEventRects = new ArrayList();
641 | Calendar dayCounter = (Calendar) day.clone();
642 | dayCounter.add(Calendar.MONTH, -1);
643 | dayCounter.set(Calendar.DAY_OF_MONTH, 1);
644 | Calendar maxDay = (Calendar) day.clone();
645 | maxDay.add(Calendar.MONTH, 1);
646 | maxDay.set(Calendar.DAY_OF_MONTH, maxDay.getActualMaximum(Calendar.DAY_OF_MONTH));
647 |
648 | // Iterate through each day to calculate the position of the events.
649 | while (dayCounter.getTimeInMillis() <= maxDay.getTimeInMillis()) {
650 | ArrayList eventRects = new ArrayList();
651 | for (EventRect eventRect : tempEvents) {
652 | if (isSameDay(eventRect.event.getStartTime(), dayCounter))
653 | eventRects.add(eventRect);
654 | }
655 |
656 | computePositionOfEvents(eventRects);
657 | dayCounter.add(Calendar.DATE, 1);
658 | }
659 | }
660 |
661 | private void cacheEvent(WeekViewEvent event) {
662 | if (!isSameDay(event.getStartTime(), event.getEndTime())) {
663 | Calendar endTime = (Calendar) event.getStartTime().clone();
664 | endTime.set(Calendar.HOUR_OF_DAY, 23);
665 | endTime.set(Calendar.MINUTE, 59);
666 | Calendar startTime = (Calendar) event.getEndTime().clone();
667 | startTime.set(Calendar.HOUR_OF_DAY, 00);
668 | startTime.set(Calendar.MINUTE, 0);
669 | WeekViewEvent event1 = new WeekViewEvent(event.getId(), event.getName(), event.getStartTime(), endTime);
670 | event1.setColor(event.getColor());
671 | WeekViewEvent event2 = new WeekViewEvent(event.getId(), event.getName(), startTime, event.getEndTime());
672 | event2.setColor(event.getColor());
673 | mEventRects.add(new EventRect(event1, event, null));
674 | mEventRects.add(new EventRect(event2, event, null));
675 | }
676 | else
677 | mEventRects.add(new EventRect(event, event, null));
678 | }
679 |
680 | /**
681 | * Sorts the events in ascending order.
682 | * @param events The events to be sorted.
683 | */
684 | private void sortEvents(List events) {
685 | Collections.sort(events, new Comparator() {
686 | @Override
687 | public int compare(WeekViewEvent event1, WeekViewEvent event2) {
688 | long start1 = event1.getStartTime().getTimeInMillis();
689 | long start2 = event2.getStartTime().getTimeInMillis();
690 | int comparator = start1 > start2 ? 1 : (start1 < start2 ? -1 : 0);
691 | if (comparator == 0) {
692 | long end1 = event1.getEndTime().getTimeInMillis();
693 | long end2 = event2.getEndTime().getTimeInMillis();
694 | comparator = end1 > end2 ? 1 : (end1 < end2 ? -1 : 0);
695 | }
696 | return comparator;
697 | }
698 | });
699 | }
700 |
701 | /**
702 | * Calculates the left and right positions of each events. This comes handy specially if events
703 | * are overlapping.
704 | * @param eventRects The events along with their wrapper class.
705 | */
706 | private void computePositionOfEvents(List eventRects) {
707 | // Make "collision groups" for all events that collide with others.
708 | List> collisionGroups = new ArrayList>();
709 | for (EventRect eventRect : eventRects) {
710 | boolean isPlaced = false;
711 | outerLoop:
712 | for (List collisionGroup : collisionGroups) {
713 | for (EventRect groupEvent : collisionGroup) {
714 | if (isEventsCollide(groupEvent.event, eventRect.event)) {
715 | collisionGroup.add(eventRect);
716 | isPlaced = true;
717 | break outerLoop;
718 | }
719 | }
720 | }
721 | if (!isPlaced) {
722 | List newGroup = new ArrayList();
723 | newGroup.add(eventRect);
724 | collisionGroups.add(newGroup);
725 | }
726 | }
727 |
728 | for (List collisionGroup : collisionGroups) {
729 | expandEventsToMaxWidth(collisionGroup);
730 | }
731 | }
732 |
733 | /**
734 | * Expands all the events to maximum possible width. The events will try to occupy maximum
735 | * space available horizontally.
736 | * @param collisionGroup The group of events which overlap with each other.
737 | */
738 | private void expandEventsToMaxWidth(List collisionGroup) {
739 | // Expand the events to maximum possible width.
740 | List> columns = new ArrayList>();
741 | columns.add(new ArrayList());
742 | for (EventRect eventRect : collisionGroup) {
743 | boolean isPlaced = false;
744 | for (List column : columns) {
745 | if (column.size() == 0) {
746 | column.add(eventRect);
747 | isPlaced = true;
748 | }
749 | else if (!isEventsCollide(eventRect.event, column.get(column.size()-1).event)) {
750 | column.add(eventRect);
751 | isPlaced = true;
752 | break;
753 | }
754 | }
755 | if (!isPlaced) {
756 | List newColumn = new ArrayList();
757 | newColumn.add(eventRect);
758 | columns.add(newColumn);
759 | }
760 | }
761 |
762 |
763 | // Calculate left and right position for all the events.
764 | int maxRowCount = columns.get(0).size();
765 | for (int i = 0; i < maxRowCount; i++) {
766 | // Set the left and right values of the event.
767 | float j = 0;
768 | for (List column : columns) {
769 | if (column.size() >= i+1) {
770 | EventRect eventRect = column.get(i);
771 | eventRect.width = 1f / columns.size();
772 | eventRect.left = j / columns.size();
773 | eventRect.top = eventRect.event.getStartTime().get(Calendar.HOUR_OF_DAY) * 60 + eventRect.event.getStartTime().get(Calendar.MINUTE);
774 | eventRect.bottom = eventRect.event.getEndTime().get(Calendar.HOUR_OF_DAY) * 60 + eventRect.event.getEndTime().get(Calendar.MINUTE);
775 | mEventRects.add(eventRect);
776 | }
777 | j++;
778 | }
779 | }
780 | }
781 |
782 |
783 | /**
784 | * Checks if two events overlap.
785 | * @param event1 The first event.
786 | * @param event2 The second event.
787 | * @return true if the events overlap.
788 | */
789 | private boolean isEventsCollide(WeekViewEvent event1, WeekViewEvent event2) {
790 | long start1 = event1.getStartTime().getTimeInMillis();
791 | long end1 = event1.getEndTime().getTimeInMillis();
792 | long start2 = event2.getStartTime().getTimeInMillis();
793 | long end2 = event2.getEndTime().getTimeInMillis();
794 | return !((start1 >= end2) || (end1 <= start2));
795 | }
796 |
797 |
798 | /**
799 | * Checks if time1 occurs after (or at the same time) time2.
800 | * @param time1 The time to check.
801 | * @param time2 The time to check against.
802 | * @return true if time1 and time2 are equal or if time1 is after time2. Otherwise false.
803 | */
804 | private boolean isTimeAfterOrEquals(Calendar time1, Calendar time2) {
805 | return !(time1 == null || time2 == null) && time1.getTimeInMillis() >= time2.getTimeInMillis();
806 | }
807 |
808 | /**
809 | * Deletes the events of the months that are too far away from the current month.
810 | * @param currentDay The current day.
811 | */
812 | private void deleteFarMonths(Calendar currentDay) {
813 |
814 | if (mEventRects == null) return;
815 |
816 | Calendar nextMonth = (Calendar) currentDay.clone();
817 | nextMonth.add(Calendar.MONTH, 1);
818 | nextMonth.set(Calendar.DAY_OF_MONTH, nextMonth.getActualMaximum(Calendar.DAY_OF_MONTH));
819 | nextMonth.set(Calendar.HOUR_OF_DAY, 12);
820 | nextMonth.set(Calendar.MINUTE, 59);
821 | nextMonth.set(Calendar.SECOND, 59);
822 |
823 | Calendar prevMonth = (Calendar) currentDay.clone();
824 | prevMonth.add(Calendar.MONTH, -1);
825 | prevMonth.set(Calendar.DAY_OF_MONTH, 1);
826 | prevMonth.set(Calendar.HOUR_OF_DAY, 0);
827 | prevMonth.set(Calendar.MINUTE, 0);
828 | prevMonth.set(Calendar.SECOND, 0);
829 |
830 | List newEvents = new ArrayList();
831 | for (EventRect eventRect : mEventRects) {
832 | boolean isFarMonth = eventRect.event.getStartTime().getTimeInMillis() > nextMonth.getTimeInMillis() || eventRect.event.getEndTime().getTimeInMillis() < prevMonth.getTimeInMillis();
833 | if (!isFarMonth) newEvents.add(eventRect);
834 | }
835 | mEventRects.clear();
836 | mEventRects.addAll(newEvents);
837 | }
838 |
839 |
840 | /////////////////////////////////////////////////////////////////
841 | //
842 | // Functions related to setting and getting the properties.
843 | //
844 | /////////////////////////////////////////////////////////////////
845 |
846 | public void setOnEventClickListener (EventClickListener listener) {
847 | this.mEventClickListener = listener;
848 | }
849 |
850 | public EventClickListener getEventClickListener() {
851 | return mEventClickListener;
852 | }
853 |
854 | public MonthChangeListener getMonthChangeListener() {
855 | return mMonthChangeListener;
856 | }
857 |
858 | public void setMonthChangeListener(MonthChangeListener monthChangeListener) {
859 | this.mMonthChangeListener = monthChangeListener;
860 | }
861 |
862 | public EventLongPressListener getEventLongPressListener() {
863 | return mEventLongPressListener;
864 | }
865 |
866 | public void setEventLongPressListener(EventLongPressListener eventLongPressListener) {
867 | this.mEventLongPressListener = eventLongPressListener;
868 | }
869 |
870 | /**
871 | * Get the number of visible days in a week.
872 | * @return The number of visible days in a week.
873 | */
874 | public int getNumberOfVisibleDays() {
875 | return mNumberOfVisibleDays;
876 | }
877 |
878 | /**
879 | * Set the number of visible days in a week.
880 | * @param numberOfVisibleDays The number of visible days in a week.
881 | */
882 | public void setNumberOfVisibleDays(int numberOfVisibleDays) {
883 | this.mNumberOfVisibleDays = numberOfVisibleDays;
884 | mCurrentOrigin.x = 0;
885 | mCurrentOrigin.y = 0;
886 | invalidate();
887 | }
888 |
889 | public int getHourHeight() {
890 | return mHourHeight;
891 | }
892 |
893 | public void setHourHeight(int hourHeight) {
894 | mHourHeight = hourHeight;
895 | invalidate();
896 | }
897 |
898 | public int getColumnGap() {
899 | return mColumnGap;
900 | }
901 |
902 | public void setColumnGap(int columnGap) {
903 | mColumnGap = columnGap;
904 | invalidate();
905 | }
906 |
907 | public int getFirstDayOfWeek() {
908 | return mFirstDayOfWeek;
909 | }
910 |
911 | /**
912 | * Set the first day of the week. First day of the week is used only when the week view is first
913 | * drawn. It does not of any effect after user starts scrolling horizontally.
914 | *
915 | * Note: This method will only work if the week view is set to display more than 6 days at
916 | * once.
917 | *
918 | * @param firstDayOfWeek The supported values are {@link java.util.Calendar#SUNDAY},
919 | * {@link java.util.Calendar#MONDAY}, {@link java.util.Calendar#TUESDAY},
920 | * {@link java.util.Calendar#WEDNESDAY}, {@link java.util.Calendar#THURSDAY},
921 | * {@link java.util.Calendar#FRIDAY}.
922 | */
923 | public void setFirstDayOfWeek(int firstDayOfWeek) {
924 | mFirstDayOfWeek = firstDayOfWeek;
925 | invalidate();
926 | }
927 |
928 | public int getTextSize() {
929 | return mTextSize;
930 | }
931 |
932 | public void setTextSize(int textSize) {
933 | mTextSize = textSize;
934 | mTodayHeaderTextPaint.setTextSize(mTextSize);
935 | mHeaderTextPaint.setTextSize(mTextSize);
936 | mTimeTextPaint.setTextSize(mTextSize);
937 | invalidate();
938 | }
939 |
940 | public int getHeaderColumnPadding() {
941 | return mHeaderColumnPadding;
942 | }
943 |
944 | public void setHeaderColumnPadding(int headerColumnPadding) {
945 | mHeaderColumnPadding = headerColumnPadding;
946 | invalidate();
947 | }
948 |
949 | public int getHeaderColumnTextColor() {
950 | return mHeaderColumnTextColor;
951 | }
952 |
953 | public void setHeaderColumnTextColor(int headerColumnTextColor) {
954 | mHeaderColumnTextColor = headerColumnTextColor;
955 | invalidate();
956 | }
957 |
958 | public int getHeaderRowPadding() {
959 | return mHeaderRowPadding;
960 | }
961 |
962 | public void setHeaderRowPadding(int headerRowPadding) {
963 | mHeaderRowPadding = headerRowPadding;
964 | invalidate();
965 | }
966 |
967 | public int getHeaderRowBackgroundColor() {
968 | return mHeaderRowBackgroundColor;
969 | }
970 |
971 | public void setHeaderRowBackgroundColor(int headerRowBackgroundColor) {
972 | mHeaderRowBackgroundColor = headerRowBackgroundColor;
973 | invalidate();
974 | }
975 |
976 | public int getDayBackgroundColor() {
977 | return mDayBackgroundColor;
978 | }
979 |
980 | public void setDayBackgroundColor(int dayBackgroundColor) {
981 | mDayBackgroundColor = dayBackgroundColor;
982 | invalidate();
983 | }
984 |
985 | public int getHourSeparatorColor() {
986 | return mHourSeparatorColor;
987 | }
988 |
989 | public void setHourSeparatorColor(int hourSeparatorColor) {
990 | mHourSeparatorColor = hourSeparatorColor;
991 | invalidate();
992 | }
993 |
994 | public int getTodayBackgroundColor() {
995 | return mTodayBackgroundColor;
996 | }
997 |
998 | public void setTodayBackgroundColor(int todayBackgroundColor) {
999 | mTodayBackgroundColor = todayBackgroundColor;
1000 | invalidate();
1001 | }
1002 |
1003 | public int getHourSeparatorHeight() {
1004 | return mHourSeparatorHeight;
1005 | }
1006 |
1007 | public void setHourSeparatorHeight(int hourSeparatorHeight) {
1008 | mHourSeparatorHeight = hourSeparatorHeight;
1009 | invalidate();
1010 | }
1011 |
1012 | public int getTodayHeaderTextColor() {
1013 | return mTodayHeaderTextColor;
1014 | }
1015 |
1016 | public void setTodayHeaderTextColor(int todayHeaderTextColor) {
1017 | mTodayHeaderTextColor = todayHeaderTextColor;
1018 | invalidate();
1019 | }
1020 |
1021 | public int getEventTextSize() {
1022 | return mEventTextSize;
1023 | }
1024 |
1025 | public void setEventTextSize(int eventTextSize) {
1026 | mEventTextSize = eventTextSize;
1027 | mEventTextPaint.setTextSize(mEventTextSize);
1028 | invalidate();
1029 | }
1030 |
1031 | public int getEventTextColor() {
1032 | return mEventTextColor;
1033 | }
1034 |
1035 | public void setEventTextColor(int eventTextColor) {
1036 | mEventTextColor = eventTextColor;
1037 | invalidate();
1038 | }
1039 |
1040 | public int getEventPadding() {
1041 | return mEventPadding;
1042 | }
1043 |
1044 | public void setEventPadding(int eventPadding) {
1045 | mEventPadding = eventPadding;
1046 | invalidate();
1047 | }
1048 |
1049 | public int getHeaderColumnBackgroundColor() {
1050 | return mHeaderColumnBackgroundColor;
1051 | }
1052 |
1053 | public void setHeaderColumnBackgroundColor(int headerColumnBackgroundColor) {
1054 | mHeaderColumnBackgroundColor = headerColumnBackgroundColor;
1055 | invalidate();
1056 | }
1057 |
1058 | public int getDefaultEventColor() {
1059 | return mDefaultEventColor;
1060 | }
1061 |
1062 | public void setDefaultEventColor(int defaultEventColor) {
1063 | mDefaultEventColor = defaultEventColor;
1064 | invalidate();
1065 | }
1066 |
1067 | public int getDayNameLength() {
1068 | return mDayNameLength;
1069 | }
1070 |
1071 | /**
1072 | * Set the length of the day name displayed in the header row. Example of short day names is
1073 | * 'M' for 'Monday' and example of long day names is 'Mon' for 'Monday'.
1074 | * @param length Supported values are {@link com.alamkanak.weekview.WeekView#LENGTH_SHORT} and
1075 | * {@link com.alamkanak.weekview.WeekView#LENGTH_LONG}.
1076 | */
1077 | public void setDayNameLength(int length) {
1078 | if (length != LENGTH_LONG && length != LENGTH_SHORT) {
1079 | throw new IllegalArgumentException("length parameter must be either LENGTH_LONG or LENGTH_SHORT");
1080 | }
1081 | this.mDayNameLength = length;
1082 | }
1083 |
1084 | public int getOverlappingEventGap() {
1085 | return mOverlappingEventGap;
1086 | }
1087 |
1088 | /**
1089 | * Set the gap between overlapping events.
1090 | * @param overlappingEventGap The gap between overlapping events.
1091 | */
1092 | public void setOverlappingEventGap(int overlappingEventGap) {
1093 | this.mOverlappingEventGap = overlappingEventGap;
1094 | invalidate();
1095 | }
1096 |
1097 | public int getEventMarginVertical() {
1098 | return mEventMarginVertical;
1099 | }
1100 |
1101 | /**
1102 | * Set the top and bottom margin of the event. The event will release this margin from the top
1103 | * and bottom edge. This margin is useful for differentiation consecutive events.
1104 | * @param eventMarginVertical The top and bottom margin.
1105 | */
1106 | public void setEventMarginVertical(int eventMarginVertical) {
1107 | this.mEventMarginVertical = eventMarginVertical;
1108 | invalidate();
1109 | }
1110 |
1111 | /**
1112 | * Returns the first visible day in the week view.
1113 | * @return The first visible day in the week view.
1114 | */
1115 | public Calendar getFirstVisibleDay() {
1116 | return mFirstVisibleDay;
1117 | }
1118 |
1119 | /**
1120 | * Returns the last visible day in the week view.
1121 | * @return The last visible day in the week view.
1122 | */
1123 | public Calendar getLastVisibleDay() {
1124 | return mLastVisibleDay;
1125 | }
1126 |
1127 | /////////////////////////////////////////////////////////////////
1128 | //
1129 | // Functions related to scrolling.
1130 | //
1131 | /////////////////////////////////////////////////////////////////
1132 |
1133 | @Override
1134 | public boolean onTouchEvent(MotionEvent event) {
1135 | if (event.getAction() == MotionEvent.ACTION_UP) {
1136 |
1137 | if (mCurrentScrollDirection == Direction.HORIZONTAL) {
1138 | float leftDays = Math.round(mCurrentOrigin.x / (mWidthPerDay + mColumnGap));
1139 | int nearestOrigin = (int) (mCurrentOrigin.x - leftDays * (mWidthPerDay+mColumnGap));
1140 | mStickyScroller.startScroll((int) mCurrentOrigin.x, 0, - nearestOrigin, 0);
1141 | ViewCompat.postInvalidateOnAnimation(WeekView.this);
1142 | }
1143 | mCurrentScrollDirection = Direction.NONE;
1144 | }
1145 | return mGestureDetector.onTouchEvent(event);
1146 | }
1147 |
1148 |
1149 | @Override
1150 | public void computeScroll() {
1151 | super.computeScroll();
1152 | if (mScroller.computeScrollOffset()) {
1153 | if (Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) < mWidthPerDay + mColumnGap && Math.abs(mScroller.getFinalX() - mScroller.getStartX()) != 0) {
1154 | mScroller.forceFinished(true);
1155 | float leftDays = Math.round(mCurrentOrigin.x / (mWidthPerDay + mColumnGap));
1156 | int nearestOrigin = (int) (mCurrentOrigin.x - leftDays * (mWidthPerDay+mColumnGap));
1157 | mStickyScroller.startScroll((int) mCurrentOrigin.x, 0, - nearestOrigin, 0);
1158 | ViewCompat.postInvalidateOnAnimation(WeekView.this);
1159 | }
1160 | else {
1161 | if (mCurrentFlingDirection == Direction.VERTICAL) mCurrentOrigin.y = mScroller.getCurrY();
1162 | else mCurrentOrigin.x = mScroller.getCurrX();
1163 | ViewCompat.postInvalidateOnAnimation(this);
1164 | }
1165 | }
1166 | if (mStickyScroller.computeScrollOffset()) {
1167 | mCurrentOrigin.x = mStickyScroller.getCurrX();
1168 | ViewCompat.postInvalidateOnAnimation(this);
1169 | }
1170 | }
1171 |
1172 |
1173 | /////////////////////////////////////////////////////////////////
1174 | //
1175 | // Public methods.
1176 | //
1177 | /////////////////////////////////////////////////////////////////
1178 |
1179 | /**
1180 | * Show today on the week view.
1181 | */
1182 | public void goToToday() {
1183 | Calendar today = Calendar.getInstance();
1184 | goToDate(today);
1185 | }
1186 |
1187 | /**
1188 | * Show a specific day on the week view.
1189 | * @param date The date to show.
1190 | */
1191 | public void goToDate(Calendar date) {
1192 | mScroller.forceFinished(true);
1193 | date.set(Calendar.HOUR_OF_DAY, 0);
1194 | date.set(Calendar.MINUTE, 0);
1195 | date.set(Calendar.SECOND, 0);
1196 |
1197 | mRefreshEvents = true;
1198 |
1199 | Calendar today = Calendar.getInstance();
1200 | today.set(Calendar.HOUR_OF_DAY, 0);
1201 | today.set(Calendar.MINUTE, 0);
1202 | today.set(Calendar.SECOND, 0);
1203 |
1204 | int dateDifference = (int) (date.getTimeInMillis() - today.getTimeInMillis()) / (1000 * 60 * 60 * 24);
1205 | mCurrentOrigin.x = - dateDifference * (mWidthPerDay + mColumnGap);
1206 |
1207 | invalidate();
1208 | }
1209 |
1210 | /**
1211 | * Refreshes the view and loads the events again.
1212 | */
1213 | public void notifyDatasetChanged(){
1214 | mRefreshEvents = true;
1215 | invalidate();
1216 | }
1217 |
1218 |
1219 |
1220 | /////////////////////////////////////////////////////////////////
1221 | //
1222 | // Interfaces.
1223 | //
1224 | /////////////////////////////////////////////////////////////////
1225 |
1226 | public interface EventClickListener {
1227 | public void onEventClick(WeekViewEvent event, RectF eventRect);
1228 | }
1229 |
1230 | public interface MonthChangeListener {
1231 | public List onMonthChange(int newYear, int newMonth);
1232 | }
1233 |
1234 | public interface EventLongPressListener {
1235 | public void onEventLongPress(WeekViewEvent event, RectF eventRect);
1236 | }
1237 |
1238 | /////////////////////////////////////////////////////////////////
1239 | //
1240 | // Helper methods.
1241 | //
1242 | /////////////////////////////////////////////////////////////////
1243 |
1244 | /**
1245 | * Checks if an integer array contains a particular value.
1246 | * @param list The haystack.
1247 | * @param value The needle.
1248 | * @return True if the array contains the value. Otherwise returns false.
1249 | */
1250 | private boolean containsValue(int[] list, int value) {
1251 | for (int i = 0; i < list.length; i++){
1252 | if (list[i] == value)
1253 | return true;
1254 | }
1255 | return false;
1256 | }
1257 |
1258 |
1259 | /**
1260 | * Converts an int (0-23) to time string (e.g. 12 PM).
1261 | * @param hour The time. Limit: 0-23.
1262 | * @return The string representation of the time.
1263 | */
1264 | private String getTimeString(int hour) {
1265 | String amPm;
1266 | if (hour >= 0 && hour < 12) amPm = "AM";
1267 | else amPm = "PM";
1268 | if (hour == 0) hour = 12;
1269 | if (hour > 12) hour -= 12;
1270 | return String.format("%02d %s", hour, amPm);
1271 | }
1272 |
1273 |
1274 | /**
1275 | * Checks if two times are on the same day.
1276 | * @param dayOne The first day.
1277 | * @param dayTwo The second day.
1278 | * @return Whether the times are on the same day.
1279 | */
1280 | private boolean isSameDay(Calendar dayOne, Calendar dayTwo) {
1281 | return dayOne.get(Calendar.YEAR) == dayTwo.get(Calendar.YEAR) && dayOne.get(Calendar.DAY_OF_YEAR) == dayTwo.get(Calendar.DAY_OF_YEAR);
1282 | }
1283 |
1284 |
1285 | /**
1286 | * Get the day name of a given date.
1287 | * @param date The date.
1288 | * @return The first the characters of the day name.
1289 | */
1290 | private String getDayName(Calendar date) {
1291 | int dayOfWeek = date.get(Calendar.DAY_OF_WEEK);
1292 | if (Calendar.MONDAY == dayOfWeek) return (mDayNameLength == LENGTH_SHORT ? "M" : "MON");
1293 | else if (Calendar.TUESDAY == dayOfWeek) return (mDayNameLength == LENGTH_SHORT ? "T" : "TUE");
1294 | else if (Calendar.WEDNESDAY == dayOfWeek) return (mDayNameLength == LENGTH_SHORT ? "W" : "WED");
1295 | else if (Calendar.THURSDAY == dayOfWeek) return (mDayNameLength == LENGTH_SHORT ? "T" : "THU");
1296 | else if (Calendar.FRIDAY == dayOfWeek) return (mDayNameLength == LENGTH_SHORT ? "F" : "FRI");
1297 | else if (Calendar.SATURDAY == dayOfWeek) return (mDayNameLength == LENGTH_SHORT ? "S" : "SAT");
1298 | else if (Calendar.SUNDAY == dayOfWeek) return (mDayNameLength == LENGTH_SHORT ? "S" : "SUN");
1299 | return "";
1300 | }
1301 | }
1302 |
--------------------------------------------------------------------------------