├── 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 | 5 | 9 | 10 | 14 | 19 | 23 | 24 | 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 | ![](images/screen-shot.png) 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 | --------------------------------------------------------------------------------