├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── Calendar.iml ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── 1.gif └── 2.gif ├── library ├── .gitignore ├── build.gradle ├── library.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── zhangzhikai │ │ └── calendarview │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── zhikaizhang │ │ │ └── calendarview │ │ │ ├── CalendarView.java │ │ │ ├── Constant.java │ │ │ ├── ICalendarView.java │ │ │ └── RenderUtil.java │ └── res │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── cn │ └── zhangzhikai │ └── calendar_view_library │ └── ExampleUnitTest.java ├── pickerview ├── .gitignore ├── build.gradle ├── pickerview.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bigkoo │ │ └── pickerview │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── bigkoo │ │ └── pickerview │ │ ├── MyTimePickerView.java │ │ ├── OptionsPickerView.java │ │ ├── TimePickerView.java │ │ ├── adapter │ │ ├── ArrayWheelAdapter.java │ │ ├── NumericWheelAdapter.java │ │ └── WheelAdapter.java │ │ ├── lib │ │ ├── InertiaTimerTask.java │ │ ├── LoopViewGestureListener.java │ │ ├── MessageHandler.java │ │ ├── OnItemSelectedRunnable.java │ │ ├── SmoothScrollTimerTask.java │ │ └── WheelView.java │ │ ├── listener │ │ ├── OnDismissListener.java │ │ └── OnItemSelectedListener.java │ │ ├── utils │ │ └── PickerViewAnimateUtil.java │ │ └── view │ │ ├── BasePickerView.java │ │ ├── WheelOptions.java │ │ └── WheelTime.java │ └── res │ ├── anim │ ├── slide_in_bottom.xml │ └── slide_out_bottom.xml │ ├── drawable │ └── selector_pickerview_btn.xml │ ├── layout │ ├── include_pickerview_topbar.xml │ ├── layout_basepickerview.xml │ ├── my_time_picker_view.xml │ ├── pickerview_options.xml │ └── pickerview_time.xml │ └── values │ ├── attrs.xml │ ├── bools.xml │ ├── colors.xml │ ├── dimens.xml │ ├── integers.xml │ └── strings.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── sample.iml └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── zhangzhikai │ │ └── demo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── zhikaizhang │ │ │ └── calendarviewdemo │ │ │ ├── Demo0.java │ │ │ ├── Demo1.java │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-hdpi │ │ ├── down.png │ │ └── right.png │ │ ├── drawable │ │ └── bg.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── mode0.xml │ │ └── mode1.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── zhangzhikai │ └── calendar │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Calendar -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Calendar.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android-CalendarView 2 | ==================== 3 | #### normal mode 4 | ![](https://github.com/laserwave/Android-CalendarView/blob/master/images/1.gif) 5 | 6 | #### record mode 7 | ![](https://github.com/laserwave/Android-CalendarView/blob/master/images/2.gif) 8 | 9 | Support `API LEVEL >= 7`. 10 | 11 | Including In Your Project 12 | ------------------------- 13 | 14 | #### Gradle 15 | Add the following code in the build.gradle of your module. 16 | ```groovy 17 | dependencies { 18 | compile 'cn.zhikaizhang.calendar:library:1.0.1' 19 | } 20 | ``` 21 | #### Download the source code 22 | You can also download the source code of the project and import the library module into your project as a module so that you can modify the source code. 23 | 24 | Usage 25 | ----- 26 | *For a working implementation of this project see the `sample/` folder.* 27 | 28 | Step1. Include one of the widgets in your xml. You can set the mode 0 for the normal mode and 1 for the record mode. You can also specify the mode in your java code. 29 | ```xml 30 | 35 | 36 | ``` 37 | Step2. Refresh the CalendarView. You will use refresh0() for the normal calendar mode and refresh1() for the record mode. 38 | ```java 39 | calendarView.setMode(0); 40 | //refresh the CalendarView with new values of year and month 41 | calendarView.refresh0(year, month); 42 | 43 | calendarView.setMode(1); 44 | //simulate to get data 45 | int days = calendarView.daysOfCurrentMonth(); 46 | boolean data[] = new boolean[days+1]; 47 | int today = Calendar.getInstance().get(Calendar.DAY_OF_MONTH); 48 | for(int i = 1; i <= days; i++){ 49 | if(i <= today){ 50 | data[i] = (Math.random() > 0.5); 51 | }else{ 52 | data[i] = false; 53 | } 54 | } 55 | calendarView.refresh1(data); 56 | ``` 57 | Step3. Set the appearance you like. You can set the language to English or Chinese. You can also modify the color and size of the text. 58 | ```java 59 | /** 60 | * modify the language of head of the calendar 61 | * legal values of style : 0 - 3 62 | * 0, 1, 2 : Chinese, 3 : English 63 | */ 64 | calendarView.setWeekTextStyle(style); 65 | 66 | //set the text color of the head 67 | calendarView.setWeekTextColor(Color.BLACK); 68 | 69 | /** 70 | * set the scale of text size of the head 71 | * legal values : 0.0f - 1.0f 72 | */ 73 | calendarView.setWeekTextSizeScale(0.5f); 74 | 75 | //set the text color of the calendar cell 76 | calendarView.setCalendarTextColor(Color.BLACK); 77 | 78 | /** 79 | * set the scale of text size of the calendar cell 80 | * legal values : 0.0f - 1.0f 81 | */ 82 | calendarView.setTextSizeScale(0.5f); 83 | 84 | /** 85 | * set the color of the text of selected day 86 | */ 87 | calendarView.setSelectedDayTextColor(Color.WHITE); 88 | 89 | /** 90 | * set the color of the background of selected day 91 | */ 92 | calendarView.setSelectedDayBgColor(Color.rgb(124, 180, 246)); 93 | 94 | /** 95 | * set the color of the background of today 96 | */ 97 | calendarView.setTodayBgColor(Color.rgb(124, 180, 246)); 98 | ``` 99 | Step4. Implement the callback. Set the OnRefreshListener to do what you want after you refresh the calendar and set the OnItemClickListener to do what you want after you click a day. 100 | ```java 101 | calendarView.setOnRefreshListener(new ICalendarView.OnRefreshListener() { 102 | @Override 103 | public void onRefresh() { 104 | yearMonthTextView.setText(getYearMonthText(calendarView.getYear(), calendarView.getMonth())); 105 | } 106 | }); 107 | 108 | calendarView.setOnItemClickListener(new ICalendarView.OnItemClickListener() { 109 | @Override 110 | public void onItemClick(int day) { 111 | int year = calendarView.getYear(); 112 | int month = calendarView.getMonth(); 113 | Toast.makeText(Demo0.this, year + "-" + month + "-" + day, Toast.LENGTH_SHORT).show(); 114 | } 115 | }); 116 | ``` 117 | 118 | Author 119 | ====== 120 | 121 | * ZhikaiZhang 122 | * Email 123 | * Blog 124 | * [android自定义控件之日历控件的实现](http://zhikaizhang.cn/2016/05/21/android%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%A7%E4%BB%B6%E4%B9%8B%E6%97%A5%E5%8E%86%E6%8E%A7%E4%BB%B6%E7%9A%84%E5%AE%9E%E7%8E%B0/) 125 | 126 | License 127 | ======= 128 | 129 | Copyright 2016 ZhikaiZhang 130 | 131 | Licensed under the Apache License, Version 2.0 (the "License"); 132 | you may not use this file except in compliance with the License. 133 | You may obtain a copy of the License at 134 | 135 | http://www.apache.org/licenses/LICENSE-2.0 136 | 137 | Unless required by applicable law or agreed to in writing, software 138 | distributed under the License is distributed on an "AS IS" BASIS, 139 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 140 | See the License for the specific language governing permissions and 141 | limitations under the License. 142 | 143 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.0' 9 | //classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' 10 | //classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laserwave/calendar_view/d1bd8457868457fae4f9228ede552c63ba9e6f3a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 19 19:24:43 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laserwave/calendar_view/d1bd8457868457fae4f9228ede552c63ba9e6f3a/images/1.gif -------------------------------------------------------------------------------- /images/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laserwave/calendar_view/d1bd8457868457fae4f9228ede552c63ba9e6f3a/images/2.gif -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //apply plugin: 'com.github.dcendents.android-maven' 3 | //apply plugin: 'com.jfrog.bintray' 4 | version = "1.0.1" 5 | 6 | android { 7 | compileSdkVersion 23 8 | buildToolsVersion "23.0.2" 9 | resourcePrefix "calendarview__" 10 | defaultConfig { 11 | minSdkVersion 7 12 | targetSdkVersion 23 13 | versionCode 1 14 | versionName "1.0" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | testCompile 'junit:junit:4.12' 27 | compile 'com.android.support:appcompat-v7:23.2.0' 28 | } 29 | /* 30 | def siteUrl = 'https://github.com/laserwave/Android-CalendarView' 31 | def gitUrl = 'https://github.com/laserwave/Android-CalendarView.git' 32 | group = "cn.zhikaizhang.calendar" // Maven Group ID for the artifact 33 | install { 34 | repositories.mavenInstaller { 35 | // This generates POM.xml with proper parameters 36 | pom { 37 | project { 38 | packaging 'aar' 39 | // Add your description here 40 | name 'Android-CalendarView' //项目描述 41 | url siteUrl 42 | // Set your license 43 | licenses { 44 | license { 45 | name 'The Apache Software License, Version 2.0' 46 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 47 | } 48 | } 49 | developers { 50 | developer { 51 | id 'zhikaizhang' //填写的一些基本信息 52 | name 'zhikaizhang' 53 | email 'zhikaizhang@seu.edu.cn' 54 | } 55 | } 56 | scm { 57 | connection gitUrl 58 | developerConnection gitUrl 59 | url siteUrl 60 | } 61 | } 62 | } 63 | } 64 | } 65 | task sourcesJar(type: Jar) { 66 | from android.sourceSets.main.java.srcDirs 67 | classifier = 'sources' 68 | } 69 | task javadoc(type: Javadoc) { 70 | source = android.sourceSets.main.java.srcDirs 71 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 72 | } 73 | task javadocJar(type: Jar, dependsOn: javadoc) { 74 | classifier = 'javadoc' 75 | from javadoc.destinationDir 76 | } 77 | artifacts { 78 | archives javadocJar 79 | archives sourcesJar 80 | } 81 | Properties properties = new Properties() 82 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 83 | bintray { 84 | user = properties.getProperty("bintray.user") 85 | key = properties.getProperty("bintray.apikey") 86 | configurations = ['archives'] 87 | pkg { 88 | repo = "maven" 89 | name = "Android-CalendarView" //发布到JCenter上的项目名字 90 | websiteUrl = siteUrl 91 | vcsUrl = gitUrl 92 | licenses = ["Apache-2.0"] 93 | publish = true 94 | } 95 | } 96 | */ 97 | -------------------------------------------------------------------------------- /library/library.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /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:\AndroidStudio\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 | -------------------------------------------------------------------------------- /library/src/androidTest/java/cn/zhangzhikai/calendarview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.zhangzhikai.calendarview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/java/cn/zhikaizhang/calendarview/CalendarView.java: -------------------------------------------------------------------------------- 1 | package cn.zhikaizhang.calendarview; 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.Typeface; 9 | import android.util.AttributeSet; 10 | import android.util.DisplayMetrics; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.WindowManager; 14 | 15 | import java.util.Calendar; 16 | 17 | import cn.zhikaizhang.library.R; 18 | 19 | public class CalendarView extends View implements View.OnTouchListener, ICalendarView { 20 | 21 | private int selectedYear; 22 | 23 | private int selectedMonth; 24 | 25 | /** 26 | * correspond to xxxx(year)-xx(month)-1(day) 27 | */ 28 | private Calendar calendar; 29 | 30 | /** 31 | * the calendar needs a 6*7 matrix to store 32 | * date[i] represents the day of position i 33 | */ 34 | private int[] date = new int[42]; 35 | 36 | /** 37 | * the width of the telephone screen 38 | */ 39 | private int screenWidth; 40 | 41 | /** 42 | * the index in date[] of the first day of current month 43 | */ 44 | private int curStartIndex; 45 | 46 | /** 47 | * the index in date[] of the last day of current month 48 | */ 49 | private int curEndIndex; 50 | 51 | /** 52 | * the index in date[] of today 53 | */ 54 | private int todayIndex = -1; 55 | 56 | /** 57 | * [only used for MODE_SHOW_DATA_OF_THIS_MONTH] 58 | * data[i] indicates whether completing the plan on the ith day of current month (such as running in JOY RUN) 59 | */ 60 | private boolean[] data = new boolean[32]; 61 | 62 | /** 63 | * record the index in date[] of the last ACTION_DOWN event 64 | */ 65 | private int actionDownIndex = -1; 66 | 67 | /** 68 | * record the selected index in date[] 69 | */ 70 | private int selectedIndex = -1; 71 | 72 | private OnItemClickListener onItemClickListener; 73 | 74 | private OnRefreshListener onRefreshListener; 75 | 76 | /** 77 | * two legal values: 78 | * MODE_CALENDAR (0) : normal calendar 79 | * MODE_SHOW_DATA_OF_THIS_MONTH (1) : record the completion of the task in the month 80 | */ 81 | private int mode; 82 | 83 | 84 | /** 85 | * following are some parameters for rendering the widget 86 | */ 87 | private float cellWidth; 88 | private float cellHeight; 89 | private String[] weekText; 90 | private int textColor = Constant.TEXT_COLOR; 91 | private int backgroundColor = Constant.BACKGROUND_COLOR; 92 | private Paint textPaint; 93 | private Paint weekTextPaint; 94 | private Paint grayPaint; 95 | private Paint whitePaint; 96 | private Paint bluePaint; 97 | private Paint blackPaint; 98 | private Paint todayBgPaint; 99 | private Paint selectedDayBgPaint; 100 | private Paint selectedDayTextPaint; 101 | 102 | 103 | public CalendarView(Context context, AttributeSet attrs, int defStyle) { 104 | 105 | super(context, attrs, defStyle); 106 | 107 | /** 108 | * read the attribute in the layout xml 109 | */ 110 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CalendarView); 111 | mode = typedArray.getInt(R.styleable.CalendarView_mode, Constant.MODE_SHOW_DATA_OF_THIS_MONTH); 112 | 113 | /** 114 | * default choose the current month of real life 115 | */ 116 | calendar = Calendar.getInstance(); 117 | selectedYear = calendar.get(Calendar.YEAR); 118 | selectedMonth = calendar.get(Calendar.MONTH) + 1; 119 | calendar.set(Calendar.DAY_OF_MONTH, 1); 120 | 121 | /** 122 | * get the width of the screen of the phone 123 | */ 124 | WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 125 | DisplayMetrics dm = new DisplayMetrics(); 126 | wm.getDefaultDisplay().getMetrics(dm); 127 | screenWidth = dm.widthPixels; 128 | 129 | /** 130 | * set the width and height of a cell of the calendar 131 | */ 132 | cellWidth = screenWidth / 7f; 133 | cellHeight = cellWidth * 0.7f; 134 | 135 | setBackgroundColor(backgroundColor); 136 | 137 | weekText = getResources().getStringArray(Constant.WEEK_TEXT[0]); 138 | 139 | setOnTouchListener(this); 140 | 141 | textPaint = RenderUtil.getPaint(textColor); 142 | textPaint.setTextSize(cellHeight * 0.4f); 143 | 144 | /** 145 | * paint for head of the calendar 146 | */ 147 | weekTextPaint = RenderUtil.getPaint(textColor); 148 | weekTextPaint.setTextSize(cellHeight * 0.4f); 149 | weekTextPaint.setTypeface(Typeface.DEFAULT_BOLD); 150 | 151 | whitePaint = RenderUtil.getPaint(Color.WHITE); 152 | 153 | blackPaint = RenderUtil.getPaint(Color.BLACK); 154 | 155 | bluePaint = RenderUtil.getPaint(Color.rgb(92, 158, 237)); 156 | 157 | grayPaint = RenderUtil.getPaint(Color.rgb(200, 200, 200)); 158 | 159 | todayBgPaint = RenderUtil.getPaint(Color.rgb(124, 180, 246)); 160 | todayBgPaint.setStrokeWidth(3); 161 | todayBgPaint.setStyle(Paint.Style.STROKE); 162 | 163 | selectedDayBgPaint = RenderUtil.getPaint(Color.rgb(124, 180, 246)); 164 | 165 | selectedDayTextPaint = RenderUtil.getPaint(Color.WHITE); 166 | selectedDayTextPaint.setTextSize(cellHeight * 0.4f); 167 | 168 | initial(); 169 | 170 | } 171 | 172 | public CalendarView(Context context, AttributeSet attrs) { 173 | this(context, attrs, 0); 174 | } 175 | 176 | public CalendarView(Context context) { 177 | this(context, null); 178 | } 179 | 180 | @Override 181 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 182 | widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.EXACTLY); 183 | heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(measureHeight(), View.MeasureSpec.EXACTLY); 184 | setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); 185 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 186 | } 187 | 188 | /** 189 | * calculate the total height of the widget 190 | */ 191 | private int measureHeight(){ 192 | /** 193 | * the weekday of the first day of the month, Sunday's result is 1 and Monday 2 and Saturday 7, etc. 194 | */ 195 | int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); 196 | /** 197 | * the number of days of current month 198 | */ 199 | int daysOfMonth = daysOfCurrentMonth(); 200 | /** 201 | * calculate the total lines, which equals to 1 (head of the calendar) + 1 (the first line) + n/7 + (n%7==0?0:1) 202 | * and n means number of days except first line of the calendar 203 | */ 204 | int n = -1; 205 | if(dayOfWeek >= 2 && dayOfWeek <= 7){ 206 | n = daysOfMonth - (8 - dayOfWeek + 1); 207 | }else if(dayOfWeek == 1){ 208 | n = daysOfMonth - 1; 209 | } 210 | int lines = 2 + n / 7 + (n % 7 == 0 ? 0 : 1); 211 | return (int) (cellHeight * lines); 212 | } 213 | 214 | /** 215 | * calculate the values of date[] and the legal range of index of date[] 216 | */ 217 | private void initial() { 218 | int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); 219 | int monthStart = -1; 220 | if(dayOfWeek >= 2 && dayOfWeek <= 7){ 221 | monthStart = dayOfWeek - 2; 222 | }else if(dayOfWeek == 1){ 223 | monthStart = 6; 224 | } 225 | curStartIndex = monthStart; 226 | date[monthStart] = 1; 227 | int daysOfMonth = daysOfCurrentMonth(); 228 | for (int i = 1; i < daysOfMonth; i++) { 229 | date[monthStart + i] = i + 1; 230 | } 231 | curEndIndex = monthStart + daysOfMonth; 232 | if(mode == Constant.MODE_SHOW_DATA_OF_THIS_MONTH){ 233 | todayIndex = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + monthStart - 1; 234 | }else if(mode == Constant.MODE_CALENDAR){ 235 | //the year and month selected is the current year and month 236 | if(calendar.get(Calendar.YEAR) == Calendar.getInstance().get(Calendar.YEAR) 237 | && calendar.get(Calendar.MONTH) == Calendar.getInstance().get(Calendar.MONTH)){ 238 | todayIndex = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + monthStart - 1; 239 | }else{ 240 | todayIndex = -1; 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * y is bigger than the head of the calendar, meaning that the coordination may represent a day 247 | * of the calendar 248 | */ 249 | private boolean coordIsCalendarCell(float y){ 250 | return y > cellHeight; 251 | } 252 | 253 | /** 254 | * calculate the index of date[] according to the coordination 255 | */ 256 | private int getIndexByCoordinate(float x, float y){ 257 | int m = (int) (Math.floor(x / cellWidth) + 1); 258 | int n = (int) (Math.floor((y - cellHeight) / cellHeight) + 1); 259 | return (n - 1) * 7 + m - 1; 260 | } 261 | 262 | /** 263 | * whether the index is legal 264 | * @param i the index in date[] 265 | * @return 266 | */ 267 | private boolean isLegalIndex(int i){ 268 | return !isIllegalIndex(i); 269 | } 270 | 271 | /** 272 | * whether the index is illegal 273 | * @param i the index in date[] 274 | * @return 275 | */ 276 | private boolean isIllegalIndex(int i){ 277 | return i < curStartIndex || i >= curEndIndex; 278 | } 279 | 280 | /** 281 | * calculate the x position according to the index in date[] 282 | * @param i the index in date[] 283 | * @return 284 | */ 285 | private int getXByIndex(int i) { 286 | return i % 7 + 1; 287 | } 288 | 289 | /** 290 | * calculate the y position according to the index in date[] 291 | * @param i the index in date[] 292 | * @return 293 | */ 294 | private int getYByIndex(int i) { 295 | return i / 7 + 1; 296 | } 297 | 298 | 299 | /** 300 | * render 301 | */ 302 | @Override 303 | protected void onDraw(Canvas canvas) { 304 | 305 | super.onDraw(canvas); 306 | 307 | /** 308 | * render the head 309 | */ 310 | float baseline = RenderUtil.getBaseline(0, cellHeight, weekTextPaint); 311 | for (int i = 0; i < 7; i++) { 312 | float weekTextX = RenderUtil.getStartX(cellWidth * i + cellWidth * 0.5f, weekTextPaint, weekText[i]); 313 | canvas.drawText(weekText[i], weekTextX, baseline, weekTextPaint); 314 | } 315 | 316 | if(mode == Constant.MODE_CALENDAR){ 317 | for (int i = curStartIndex; i < curEndIndex; i++) { 318 | if(i == todayIndex && i == selectedIndex){ 319 | drawCircle(canvas, i, selectedDayBgPaint, cellHeight * 0.48f); 320 | drawText(canvas, i, selectedDayTextPaint, "" + date[i]); 321 | }else if(i == todayIndex){ 322 | drawCircle(canvas, i, todayBgPaint, cellHeight * 0.48f); 323 | drawText(canvas, i, textPaint, "" + date[i]); 324 | }else if(i == selectedIndex){ 325 | drawCircle(canvas, i, selectedDayBgPaint, cellHeight * 0.48f); 326 | drawText(canvas, i, selectedDayTextPaint, "" + date[i]); 327 | }else{ 328 | drawText(canvas, i, textPaint, "" + date[i]); 329 | } 330 | 331 | } 332 | }else if(mode == Constant.MODE_SHOW_DATA_OF_THIS_MONTH){ 333 | for (int i = curStartIndex; i < curEndIndex; i++) { 334 | if(i < todayIndex){ 335 | if(data[date[i]]){ 336 | drawCircle(canvas, i, bluePaint, cellHeight * 0.37f); 337 | drawCircle(canvas, i, whitePaint, cellHeight * 0.31f); 338 | drawCircle(canvas, i, blackPaint, cellHeight * 0.1f); 339 | }else{ 340 | drawCircle(canvas, i, grayPaint, cellHeight * 0.1f); 341 | } 342 | }else if(i == todayIndex){ 343 | if(data[date[i]]){ 344 | drawCircle(canvas, i, bluePaint, cellHeight * 0.37f); 345 | drawCircle(canvas, i, whitePaint, cellHeight * 0.31f); 346 | drawCircle(canvas, i, blackPaint, cellHeight * 0.1f); 347 | }else{ 348 | drawCircle(canvas, i, grayPaint, cellHeight * 0.37f); 349 | drawCircle(canvas, i, whitePaint, cellHeight * 0.31f); 350 | drawCircle(canvas, i, blackPaint, cellHeight * 0.1f); 351 | } 352 | }else{ 353 | drawText(canvas, i, textPaint, "" + date[i]); 354 | } 355 | } 356 | } 357 | 358 | } 359 | 360 | /** 361 | * draw text, around the middle of the cell decided by the index 362 | */ 363 | private void drawText(Canvas canvas, int index, Paint paint, String text) { 364 | if(isIllegalIndex(index)){ 365 | return; 366 | } 367 | int x = getXByIndex(index); 368 | int y = getYByIndex(index); 369 | float top = cellHeight + (y - 1) * cellHeight; 370 | float bottom = top + cellHeight; 371 | float baseline = RenderUtil.getBaseline(top, bottom, paint); 372 | float startX = RenderUtil.getStartX(cellWidth * (x - 1) + cellWidth * 0.5f, paint, text); 373 | canvas.drawText(text, startX, baseline, paint); 374 | } 375 | 376 | /** 377 | * draw circle, around the middle of the cell decided by the index 378 | */ 379 | private void drawCircle(Canvas canvas, int index, Paint paint, float radius){ 380 | if(isIllegalIndex(index)){ 381 | return; 382 | } 383 | int x = getXByIndex(index); 384 | int y = getYByIndex(index); 385 | float centreY = cellHeight + (y - 1) * cellHeight + cellHeight * 0.5f; 386 | float centreX = cellWidth * (x - 1) + cellWidth * 0.5f; 387 | canvas.drawCircle(centreX, centreY, radius, paint); 388 | } 389 | 390 | @Override 391 | public boolean onTouch(View v, MotionEvent event) { 392 | float x = event.getX(); 393 | float y = event.getY(); 394 | switch (event.getAction()) { 395 | case MotionEvent.ACTION_DOWN: 396 | if(coordIsCalendarCell(y)){ 397 | int index = getIndexByCoordinate(x, y); 398 | if(isLegalIndex(index)) { 399 | actionDownIndex = index; 400 | selectedIndex = -1; 401 | } 402 | } 403 | break; 404 | case MotionEvent.ACTION_UP: 405 | if(coordIsCalendarCell(y)){ 406 | int actionUpIndex = getIndexByCoordinate(x, y); 407 | if(isLegalIndex(actionUpIndex)){ 408 | if(actionDownIndex == actionUpIndex){ 409 | selectedIndex = actionUpIndex; 410 | actionDownIndex = -1; 411 | int day = date[actionUpIndex]; 412 | if(onItemClickListener != null){ 413 | onItemClickListener.onItemClick(day); 414 | } 415 | invalidate(); 416 | } 417 | } 418 | } 419 | break; 420 | } 421 | return true; 422 | } 423 | 424 | private static int leap(int year){ 425 | return (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)?1:0; 426 | } 427 | 428 | @Override 429 | public int daysOfCurrentMonth(){ 430 | return Constant.DAYS_OF_MONTH[leap(selectedYear)][selectedMonth]; 431 | } 432 | 433 | @Override 434 | public int getYear(){ 435 | return selectedYear; 436 | } 437 | 438 | /** 439 | * legal values : 1-12 440 | */ 441 | @Override 442 | public int getMonth(){ 443 | return selectedMonth; 444 | } 445 | 446 | @Override 447 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) { 448 | this.onItemClickListener = onItemClickListener; 449 | } 450 | 451 | /** 452 | * used for MODE_CALENDAR 453 | * legal values of month: 1-12 454 | */ 455 | @Override 456 | public void refresh0(int year, int month) { 457 | if(mode == Constant.MODE_CALENDAR){ 458 | selectedYear = year; 459 | selectedMonth = month; 460 | selectedIndex = -1; 461 | calendar.set(Calendar.YEAR, selectedYear); 462 | calendar.set(Calendar.MONTH, selectedMonth - 1); 463 | calendar.set(Calendar.DAY_OF_MONTH, 1); 464 | initial(); 465 | invalidate(); 466 | if(onRefreshListener != null){ 467 | onRefreshListener.onRefresh(); 468 | } 469 | } 470 | } 471 | 472 | /** 473 | * used for MODE_SHOW_DATA_OF_THIS_MONTH 474 | * the index 1 to 31(big month), 1 to 30(small month), 1 - 28(Feb of normal year), 1 - 29(Feb of leap year) 475 | * is better to be accessible in the parameter data, illegal indexes will be ignored with default false value 476 | */ 477 | @Override 478 | public void refresh1(boolean[] data) { 479 | /** 480 | * the month and year may change (eg. Jan 31st becomes Feb 1st after refreshing) 481 | */ 482 | if(mode == Constant.MODE_SHOW_DATA_OF_THIS_MONTH){ 483 | calendar = Calendar.getInstance(); 484 | selectedYear = calendar.get(Calendar.YEAR); 485 | selectedMonth = calendar.get(Calendar.MONTH) + 1; 486 | calendar.set(Calendar.DAY_OF_MONTH, 1); 487 | for(int i = 1; i <= daysOfCurrentMonth(); i++){ 488 | if(i < data.length){ 489 | this.data[i] = data[i]; 490 | }else{ 491 | this.data[i] = false; 492 | } 493 | } 494 | initial(); 495 | invalidate(); 496 | if(onRefreshListener != null){ 497 | onRefreshListener.onRefresh(); 498 | } 499 | } 500 | } 501 | 502 | @Override 503 | public void setMode(int mode) { 504 | if(mode == Constant.MODE_SHOW_DATA_OF_THIS_MONTH || mode == Constant.MODE_CALENDAR){ 505 | this.mode = mode; 506 | } 507 | } 508 | 509 | /** 510 | * legal values : 0-3 511 | */ 512 | @Override 513 | public void setWeekTextStyle(int style) { 514 | if(style >= 0 && style <= 3){ 515 | weekText = getResources().getStringArray(Constant.WEEK_TEXT[style]); 516 | } 517 | } 518 | 519 | @Override 520 | public void setWeekTextColor(int color) { 521 | weekTextPaint.setColor(color); 522 | } 523 | 524 | @Override 525 | public void setCalendarTextColor(int color) { 526 | textPaint.setColor(color); 527 | } 528 | 529 | /** 530 | * legal values : 0-1 531 | */ 532 | @Override 533 | public void setWeekTextSizeScale(float scale) { 534 | if(scale >= 0 && scale <= 1){ 535 | weekTextPaint.setTextSize(cellHeight * 0.5f * scale); 536 | } 537 | } 538 | 539 | /** 540 | * legal values : 0-1 541 | */ 542 | @Override 543 | public void setTextSizeScale(float scale) { 544 | if(scale >= 0 && scale <= 1) { 545 | textPaint.setTextSize(cellHeight * 0.5f * scale); 546 | selectedDayTextPaint.setTextSize(cellHeight * 0.5f * scale); 547 | } 548 | } 549 | 550 | 551 | @Override 552 | public void setSelectedDayTextColor(int color) { 553 | selectedDayTextPaint.setColor(color); 554 | } 555 | 556 | @Override 557 | public void setSelectedDayBgColor(int color) { 558 | selectedDayBgPaint.setColor(color); 559 | } 560 | 561 | @Override 562 | public void setTodayBgColor(int color) { 563 | todayBgPaint.setColor(color); 564 | } 565 | 566 | @Override 567 | public Calendar getCalendar() { 568 | return calendar; 569 | } 570 | 571 | 572 | @Override 573 | public void setOnRefreshListener(OnRefreshListener onRefreshListener) { 574 | this.onRefreshListener = onRefreshListener; 575 | } 576 | 577 | /** 578 | * used for MODE_SHOW_DATA_OF_THIS_MONTH 579 | */ 580 | @Override 581 | public int daysCompleteTheTask() { 582 | int k = 0; 583 | for(int i = 1; i <= daysOfCurrentMonth(); i++){ 584 | k += data[i]?1:0; 585 | } 586 | return k; 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /library/src/main/java/cn/zhikaizhang/calendarview/Constant.java: -------------------------------------------------------------------------------- 1 | package cn.zhikaizhang.calendarview; 2 | 3 | import android.graphics.Color; 4 | 5 | import cn.zhikaizhang.library.R; 6 | 7 | public class Constant { 8 | 9 | 10 | public static final int[] WEEK_TEXT = {R.array.calendarview__weektext_0, R.array.calendarview__weektext_1, R.array.calendarview__weektext_2, R.array.calendarview__weektext_3}; 11 | 12 | public static final int TEXT_COLOR = Color.BLACK; 13 | 14 | public static final int BACKGROUND_COLOR = Color.WHITE; 15 | 16 | public static final int DAYS_OF_MONTH[][] = new int[][]{{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; 17 | 18 | public static final int MODE_CALENDAR = 0; 19 | 20 | public static final int MODE_SHOW_DATA_OF_THIS_MONTH = 1; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /library/src/main/java/cn/zhikaizhang/calendarview/ICalendarView.java: -------------------------------------------------------------------------------- 1 | package cn.zhikaizhang.calendarview; 2 | 3 | import java.util.Calendar; 4 | 5 | public interface ICalendarView { 6 | 7 | /** 8 | * only used for MODE_CALENDAR 9 | */ 10 | void refresh0(int year, int month); 11 | 12 | void setSelectedDayTextColor(int color); 13 | 14 | void setSelectedDayBgColor(int color); 15 | 16 | void setTodayBgColor(int color); 17 | 18 | /** 19 | * only used for MODE_SHOW_DATA_OF_THIS_MONTH 20 | */ 21 | void refresh1(boolean data[]); 22 | 23 | int daysCompleteTheTask(); 24 | 25 | /** 26 | * used for both 27 | */ 28 | void setWeekTextStyle(int style); 29 | 30 | void setWeekTextColor(int color); 31 | 32 | void setCalendarTextColor(int color); 33 | 34 | void setWeekTextSizeScale(float scale); 35 | 36 | void setTextSizeScale(float scale); 37 | 38 | void setMode(int mode); 39 | 40 | int getYear(); 41 | 42 | int getMonth(); 43 | 44 | int daysOfCurrentMonth(); 45 | 46 | Calendar getCalendar(); 47 | 48 | void setOnItemClickListener(OnItemClickListener onItemClickListener); 49 | 50 | void setOnRefreshListener(OnRefreshListener onRefreshListener); 51 | 52 | interface OnItemClickListener{ 53 | void onItemClick(int day); 54 | } 55 | 56 | interface OnRefreshListener{ 57 | void onRefresh(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/java/cn/zhikaizhang/calendarview/RenderUtil.java: -------------------------------------------------------------------------------- 1 | package cn.zhikaizhang.calendarview; 2 | 3 | import android.graphics.Paint; 4 | 5 | public class RenderUtil { 6 | 7 | /** 8 | * get the baseline to draw between top and bottom in the middle 9 | */ 10 | public static float getBaseline(float top, float bottom, Paint paint){ 11 | Paint.FontMetrics fontMetrics = paint.getFontMetrics(); 12 | return (top + bottom - fontMetrics.bottom - fontMetrics.top) / 2; 13 | } 14 | 15 | /** 16 | * get the x position to draw around the middle 17 | */ 18 | public static float getStartX(float middle, Paint paint, String text){ 19 | return middle - paint.measureText(text) * 0.5f; 20 | } 21 | 22 | public static Paint getPaint(int color){ 23 | Paint paint = new Paint(); 24 | paint.setColor(color); 25 | paint.setAntiAlias(true); 26 | return paint; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.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 | Mon 33 | Tus 34 | Wed 35 | Thu 36 | Fri 37 | Sat 38 | Sun 39 | 40 | 41 | -------------------------------------------------------------------------------- /library/src/test/java/cn/zhangzhikai/calendar_view_library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.zhangzhikai.calendarview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /pickerview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /pickerview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | version = "2.0.8" 4 | 5 | android { 6 | compileSdkVersion 23 7 | buildToolsVersion "23.0.2" 8 | 9 | defaultConfig { 10 | minSdkVersion 7 11 | targetSdkVersion 23 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | } 26 | 27 | //def siteUrl = 'https://github.com/saiwu-bigkoo/Android-PickerView' // #CONFIG# // project homepage 28 | //def gitUrl = 'https://github.com/saiwu-bigkoo/Android-PickerView.git' // #CONFIG# // project git 29 | //group = "com.bigkoo" 30 | 31 | //install { 32 | // repositories.mavenInstaller { 33 | // // This generates POM.xml with proper parameters 34 | // pom { 35 | // project { 36 | // packaging 'aar' 37 | // name 'PickerView For Android' // #CONFIG# // project title 38 | // url siteUrl 39 | // // Set your license 40 | // licenses { 41 | // license { 42 | // name 'The Apache Software License, Version 2.0' 43 | // url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 44 | // } 45 | // } 46 | // developers { 47 | // developer { 48 | // id 'sai' // #CONFIG# // your user id (you can write your nickname) 49 | // name 'sai.wu' // #CONFIG# // your user name 50 | // email 'sai.wu@bigkoo.com' // #CONFIG# // your email 51 | // } 52 | // } 53 | // scm { 54 | // connection gitUrl 55 | // developerConnection gitUrl 56 | // url siteUrl 57 | // } 58 | // } 59 | // } 60 | // } 61 | //} 62 | 63 | task sourcesJar(type: Jar) { 64 | from android.sourceSets.main.java.srcDirs 65 | classifier = 'sources' 66 | } 67 | 68 | task javadoc(type: Javadoc) { 69 | source = android.sourceSets.main.java.srcDirs 70 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 71 | } 72 | 73 | task javadocJar(type: Jar, dependsOn: javadoc) { 74 | classifier = 'javadoc' 75 | from javadoc.destinationDir 76 | } 77 | 78 | artifacts { 79 | archives javadocJar 80 | archives sourcesJar 81 | } 82 | 83 | Properties properties = new Properties() 84 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 85 | //bintray { 86 | // user = properties.getProperty("bintray.user") 87 | // key = properties.getProperty("bintray.apikey") 88 | // configurations = ['archives'] 89 | // pkg { 90 | // repo = "maven" 91 | // name = "PickerView" // #CONFIG# project name in jcenter 92 | // websiteUrl = siteUrl 93 | // vcsUrl = gitUrl 94 | // licenses = ["Apache-2.0"] 95 | // publish = true 96 | // } 97 | //} -------------------------------------------------------------------------------- /pickerview/pickerview.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /pickerview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Sai/Documents/software/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 | -------------------------------------------------------------------------------- /pickerview/src/androidTest/java/com/bigkoo/pickerview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /pickerview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/MyTimePickerView.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.bigkoo.pickerview.view.WheelTime; 7 | 8 | import java.util.Calendar; 9 | 10 | public class MyTimePickerView extends TimePickerView { 11 | 12 | 13 | public MyTimePickerView(Context context) { 14 | super(context, Type.YEAR_MONTH); 15 | } 16 | 17 | @Override 18 | public int getContentViewId() { 19 | return R.layout.my_time_picker_view; 20 | } 21 | 22 | @Override 23 | public void initialWheelTime() { 24 | final View timepickerview = findViewById(R.id.timepicker); 25 | wheelTime = new WheelTime(timepickerview, type); 26 | Calendar calendar = Calendar.getInstance(); 27 | calendar.setTimeInMillis(System.currentTimeMillis()); 28 | int year = calendar.get(Calendar.YEAR); 29 | int month = calendar.get(Calendar.MONTH); 30 | setRange(1, year + 1000); 31 | wheelTime.setEndMonthDay(month + 1, 0); 32 | wheelTime.setPicker(year, month, 0); 33 | } 34 | 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/OptionsPickerView.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.bigkoo.pickerview.view.BasePickerView; 9 | import com.bigkoo.pickerview.view.WheelOptions; 10 | 11 | import java.util.ArrayList; 12 | 13 | /** 14 | * Created by Sai on 15/11/22. 15 | */ 16 | public class OptionsPickerView extends BasePickerView implements View.OnClickListener { 17 | WheelOptions wheelOptions; 18 | private View btnSubmit, btnCancel; 19 | private TextView tvTitle; 20 | private OnOptionsSelectListener optionsSelectListener; 21 | private static final String TAG_SUBMIT = "submit"; 22 | private static final String TAG_CANCEL = "cancel"; 23 | public OptionsPickerView(Context context) { 24 | super(context); 25 | LayoutInflater.from(context).inflate(getLayoutId(), contentContainer); 26 | // -----确定和取消按钮 27 | btnSubmit = findViewById(R.id.btnSubmit); 28 | btnSubmit.setTag(TAG_SUBMIT); 29 | btnCancel = findViewById(R.id.btnCancel); 30 | btnCancel.setTag(TAG_CANCEL); 31 | btnSubmit.setOnClickListener(this); 32 | btnCancel.setOnClickListener(this); 33 | //顶部标题 34 | tvTitle = (TextView) findViewById(R.id.tvTitle); 35 | // ----转轮 36 | final View optionspicker = findViewById(R.id.optionspicker); 37 | wheelOptions = new WheelOptions(optionspicker); 38 | } 39 | 40 | public int getLayoutId(){ 41 | return R.layout.pickerview_options; 42 | } 43 | 44 | public void setPicker(ArrayList optionsItems) { 45 | wheelOptions.setPicker(optionsItems, null, null, false); 46 | } 47 | 48 | public void setPicker(ArrayList options1Items, 49 | ArrayList> options2Items, boolean linkage) { 50 | wheelOptions.setPicker(options1Items, options2Items, null, linkage); 51 | } 52 | 53 | public void setPicker(ArrayList options1Items, 54 | ArrayList> options2Items, 55 | ArrayList>> options3Items, 56 | boolean linkage) { 57 | wheelOptions.setPicker(options1Items, options2Items, options3Items, 58 | linkage); 59 | } 60 | /** 61 | * 设置选中的item位置 62 | * @param option1 63 | */ 64 | public void setSelectOptions(int option1){ 65 | wheelOptions.setCurrentItems(option1, 0, 0); 66 | } 67 | /** 68 | * 设置选中的item位置 69 | * @param option1 70 | * @param option2 71 | */ 72 | public void setSelectOptions(int option1, int option2){ 73 | wheelOptions.setCurrentItems(option1, option2, 0); 74 | } 75 | /** 76 | * 设置选中的item位置 77 | * @param option1 78 | * @param option2 79 | * @param option3 80 | */ 81 | public void setSelectOptions(int option1, int option2, int option3){ 82 | wheelOptions.setCurrentItems(option1, option2, option3); 83 | } 84 | /** 85 | * 设置选项的单位 86 | * @param label1 87 | */ 88 | public void setLabels(String label1){ 89 | wheelOptions.setLabels(label1, null, null); 90 | } 91 | /** 92 | * 设置选项的单位 93 | * @param label1 94 | * @param label2 95 | */ 96 | public void setLabels(String label1,String label2){ 97 | wheelOptions.setLabels(label1, label2, null); 98 | } 99 | /** 100 | * 设置选项的单位 101 | * @param label1 102 | * @param label2 103 | * @param label3 104 | */ 105 | public void setLabels(String label1,String label2,String label3){ 106 | wheelOptions.setLabels(label1, label2, label3); 107 | } 108 | /** 109 | * 设置是否循环滚动 110 | * @param cyclic 111 | */ 112 | public void setCyclic(boolean cyclic){ 113 | wheelOptions.setCyclic(cyclic); 114 | } 115 | public void setCyclic(boolean cyclic1,boolean cyclic2,boolean cyclic3) { 116 | wheelOptions.setCyclic(cyclic1,cyclic2,cyclic3); 117 | } 118 | 119 | 120 | @Override 121 | public void onClick(View v) 122 | { 123 | String tag=(String) v.getTag(); 124 | if(tag.equals(TAG_CANCEL)) 125 | { 126 | dismiss(); 127 | return; 128 | } 129 | else 130 | { 131 | if(optionsSelectListener!=null) 132 | { 133 | int[] optionsCurrentItems=wheelOptions.getCurrentItems(); 134 | optionsSelectListener.onOptionsSelect(optionsCurrentItems[0], optionsCurrentItems[1], optionsCurrentItems[2]); 135 | } 136 | dismiss(); 137 | return; 138 | } 139 | } 140 | 141 | public interface OnOptionsSelectListener { 142 | public void onOptionsSelect(int options1, int option2, int options3); 143 | } 144 | 145 | public void setOnoptionsSelectListener( 146 | OnOptionsSelectListener optionsSelectListener) { 147 | this.optionsSelectListener = optionsSelectListener; 148 | } 149 | 150 | public void setTitle(String title){ 151 | tvTitle.setText(title); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/TimePickerView.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.bigkoo.pickerview.view.BasePickerView; 9 | import com.bigkoo.pickerview.view.WheelTime; 10 | 11 | import java.text.ParseException; 12 | import java.util.Calendar; 13 | import java.util.Date; 14 | 15 | /** 16 | * Created by Sai on 15/11/22. 17 | */ 18 | public class TimePickerView extends BasePickerView implements View.OnClickListener { 19 | public enum Type { 20 | ALL, YEAR_MONTH_DAY, HOURS_MINS, MONTH_DAY_HOUR_MIN , YEAR_MONTH 21 | }// 四种选择模式,年月日时分,年月日,时分,月日时分 22 | 23 | WheelTime wheelTime; 24 | private View btnSubmit, btnCancel; 25 | private TextView tvTitle; 26 | private static final String TAG_SUBMIT = "submit"; 27 | protected static final String TAG_CANCEL = "cancel"; 28 | protected static final String TAG_NOTHING = "donothing"; 29 | protected OnTimeSelectListener timeSelectListener; 30 | Type type; 31 | 32 | public TimePickerView(Context context, Type type) { 33 | super(context); 34 | 35 | LayoutInflater.from(context).inflate(getContentViewId(), contentContainer); 36 | // -----确定和取消按钮 37 | btnSubmit = findViewById(R.id.btnSubmit); 38 | btnSubmit.setTag(TAG_SUBMIT); 39 | btnCancel = findViewById(R.id.btnCancel); 40 | btnCancel.setTag(TAG_CANCEL); 41 | btnSubmit.setOnClickListener(this); 42 | btnCancel.setOnClickListener(this); 43 | //顶部标题 44 | tvTitle = (TextView) findViewById(R.id.tvTitle); 45 | this.type = type; 46 | 47 | initialWheelTime(); 48 | 49 | 50 | } 51 | 52 | public int getContentViewId(){ 53 | return R.layout.pickerview_time; 54 | } 55 | 56 | /** 57 | * 设置可以选择的时间范围 58 | * 59 | * @param startYear 60 | * @param endYear 61 | */ 62 | public void setRange(int startYear, int endYear) { 63 | wheelTime.setStartYear(startYear); 64 | wheelTime.setEndYear(endYear); 65 | } 66 | 67 | /** 68 | * 设置选中时间 69 | * @param date 70 | */ 71 | public void setTime(Date date) { 72 | Calendar calendar = Calendar.getInstance(); 73 | if (date == null) 74 | calendar.setTimeInMillis(System.currentTimeMillis()); 75 | else 76 | calendar.setTime(date); 77 | int year = calendar.get(Calendar.YEAR); 78 | int month = calendar.get(Calendar.MONTH); 79 | int day = calendar.get(Calendar.DAY_OF_MONTH); 80 | int hours = calendar.get(Calendar.HOUR_OF_DAY); 81 | int minute = calendar.get(Calendar.MINUTE); 82 | wheelTime.setPicker(year, month, day, hours, minute); 83 | } 84 | 85 | /** 86 | * 设置是否循环滚动 87 | * 88 | * @param cyclic 89 | */ 90 | public void setCyclic(boolean cyclic) { 91 | wheelTime.setCyclic(cyclic); 92 | } 93 | 94 | @Override 95 | public void onClick(View v) { 96 | String tag = (String) v.getTag(); 97 | if (tag.equals(TAG_CANCEL)) { 98 | dismiss(); 99 | return; 100 | } else if(tag.equals(TAG_NOTHING)){ 101 | } 102 | else { 103 | if (timeSelectListener != null) { 104 | try { 105 | Date date = WheelTime.dateFormat.parse(wheelTime.getTime()); 106 | timeSelectListener.onTimeSelect(date); 107 | } catch (ParseException e) { 108 | e.printStackTrace(); 109 | } 110 | } 111 | dismiss(); 112 | return; 113 | } 114 | } 115 | 116 | public interface OnTimeSelectListener { 117 | public void onTimeSelect(Date date); 118 | } 119 | 120 | public void setOnTimeSelectListener(OnTimeSelectListener timeSelectListener) { 121 | this.timeSelectListener = timeSelectListener; 122 | } 123 | 124 | public void setTitle(String title){ 125 | tvTitle.setText(title); 126 | } 127 | 128 | public void initialWheelTime(){ 129 | // ----时间转轮 130 | final View timepickerview = findViewById(R.id.timepicker); 131 | wheelTime = new WheelTime(timepickerview, type); 132 | 133 | //默认选中当前时间 134 | Calendar calendar = Calendar.getInstance(); 135 | calendar.setTimeInMillis(System.currentTimeMillis()); 136 | int year = calendar.get(Calendar.YEAR); 137 | int month = calendar.get(Calendar.MONTH); 138 | int day = calendar.get(Calendar.DAY_OF_MONTH); 139 | int hours = calendar.get(Calendar.HOUR_OF_DAY); 140 | int minute = calendar.get(Calendar.MINUTE); 141 | wheelTime.setPicker(year, month, day, hours, minute); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/adapter/ArrayWheelAdapter.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.adapter; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * The simple Array wheel adapter 7 | * @param the element type 8 | */ 9 | public class ArrayWheelAdapter implements WheelAdapter { 10 | 11 | /** The default items length */ 12 | public static final int DEFAULT_LENGTH = 4; 13 | 14 | // items 15 | private ArrayList items; 16 | // length 17 | private int length; 18 | 19 | /** 20 | * Constructor 21 | * @param items the items 22 | * @param length the max items length 23 | */ 24 | public ArrayWheelAdapter(ArrayList items, int length) { 25 | this.items = items; 26 | this.length = length; 27 | } 28 | 29 | /** 30 | * Contructor 31 | * @param items the items 32 | */ 33 | public ArrayWheelAdapter(ArrayList items) { 34 | this(items, DEFAULT_LENGTH); 35 | } 36 | 37 | @Override 38 | public Object getItem(int index) { 39 | if (index >= 0 && index < items.size()) { 40 | return items.get(index); 41 | } 42 | return ""; 43 | } 44 | 45 | @Override 46 | public int getItemsCount() { 47 | return items.size(); 48 | } 49 | 50 | @Override 51 | public int indexOf(Object o){ 52 | return items.indexOf(o); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/adapter/NumericWheelAdapter.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.adapter; 2 | 3 | 4 | /** 5 | * Numeric Wheel adapter. 6 | */ 7 | public class NumericWheelAdapter implements WheelAdapter { 8 | 9 | /** The default min value */ 10 | public static final int DEFAULT_MAX_VALUE = 9; 11 | 12 | /** The default max value */ 13 | private static final int DEFAULT_MIN_VALUE = 0; 14 | 15 | // Values 16 | private int minValue; 17 | private int maxValue; 18 | 19 | /** 20 | * Default constructor 21 | */ 22 | public NumericWheelAdapter() { 23 | this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE); 24 | } 25 | 26 | /** 27 | * Constructor 28 | * @param minValue the wheel min value 29 | * @param maxValue the wheel max value 30 | */ 31 | public NumericWheelAdapter(int minValue, int maxValue) { 32 | this.minValue = minValue; 33 | this.maxValue = maxValue; 34 | } 35 | 36 | @Override 37 | public Object getItem(int index) { 38 | if (index >= 0 && index < getItemsCount()) { 39 | int value = minValue + index; 40 | return value; 41 | } 42 | return 0; 43 | } 44 | 45 | @Override 46 | public int getItemsCount() { 47 | return maxValue - minValue + 1; 48 | } 49 | 50 | @Override 51 | public int indexOf(Object o){ 52 | return (int)o - minValue; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/adapter/WheelAdapter.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.adapter; 2 | 3 | public interface WheelAdapter { 4 | /** 5 | * Gets items count 6 | * @return the count of wheel items 7 | */ 8 | public int getItemsCount(); 9 | 10 | /** 11 | * Gets a wheel item by index. 12 | * 13 | * @param index the item index 14 | * @return the wheel item text or null 15 | */ 16 | public T getItem(int index); 17 | 18 | /** 19 | * Gets maximum item length. It is used to determine the wheel width. 20 | * If -1 is returned there will be used the default wheel width. 21 | * 22 | * @return the maximum item length or -1 23 | */ 24 | public int indexOf(T o); 25 | } 26 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/lib/InertiaTimerTask.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.lib; 2 | 3 | import java.util.TimerTask; 4 | 5 | final class InertiaTimerTask extends TimerTask { 6 | 7 | float a; 8 | final float velocityY; 9 | final WheelView loopView; 10 | 11 | InertiaTimerTask(WheelView loopview, float velocityY) { 12 | super(); 13 | loopView = loopview; 14 | this.velocityY = velocityY; 15 | a = Integer.MAX_VALUE; 16 | } 17 | 18 | @Override 19 | public final void run() { 20 | if (a == Integer.MAX_VALUE) { 21 | if (Math.abs(velocityY) > 2000F) { 22 | if (velocityY > 0.0F) { 23 | a = 2000F; 24 | } else { 25 | a = -2000F; 26 | } 27 | } else { 28 | a = velocityY; 29 | } 30 | } 31 | if (Math.abs(a) >= 0.0F && Math.abs(a) <= 20F) { 32 | loopView.cancelFuture(); 33 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_SMOOTH_SCROLL); 34 | return; 35 | } 36 | int i = (int) ((a * 10F) / 1000F); 37 | loopView.totalScrollY = loopView.totalScrollY - i; 38 | if (!loopView.isLoop) { 39 | float itemHeight = loopView.itemHeight; 40 | float top = (-loopView.initPosition) * itemHeight; 41 | float bottom = (loopView.getItemsCount() - 1 - loopView.initPosition) * itemHeight; 42 | if(loopView.totalScrollY - itemHeight*0.3 < top){ 43 | top = loopView.totalScrollY + i; 44 | } 45 | else if(loopView.totalScrollY + itemHeight*0.3 > bottom){ 46 | bottom = loopView.totalScrollY + i; 47 | } 48 | 49 | if (loopView.totalScrollY <= top){ 50 | a = 40F; 51 | loopView.totalScrollY = (int)top; 52 | } else if (loopView.totalScrollY >= bottom) { 53 | loopView.totalScrollY = (int)bottom; 54 | a = -40F; 55 | } 56 | } 57 | if (a < 0.0F) { 58 | a = a + 20F; 59 | } else { 60 | a = a - 20F; 61 | } 62 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/lib/LoopViewGestureListener.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.lib; 2 | 3 | import android.view.MotionEvent; 4 | 5 | final class LoopViewGestureListener extends android.view.GestureDetector.SimpleOnGestureListener { 6 | 7 | final WheelView loopView; 8 | 9 | LoopViewGestureListener(WheelView loopview) { 10 | loopView = loopview; 11 | } 12 | 13 | @Override 14 | public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 15 | loopView.scrollBy(velocityY); 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/lib/MessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.lib; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | 6 | final class MessageHandler extends Handler { 7 | public static final int WHAT_INVALIDATE_LOOP_VIEW = 1000; 8 | public static final int WHAT_SMOOTH_SCROLL = 2000; 9 | public static final int WHAT_ITEM_SELECTED = 3000; 10 | 11 | final WheelView loopview; 12 | 13 | MessageHandler(WheelView loopview) { 14 | this.loopview = loopview; 15 | } 16 | 17 | @Override 18 | public final void handleMessage(Message msg) { 19 | switch (msg.what) { 20 | case WHAT_INVALIDATE_LOOP_VIEW: 21 | loopview.invalidate(); 22 | break; 23 | 24 | case WHAT_SMOOTH_SCROLL: 25 | loopview.smoothScroll(WheelView.ACTION.FLING); 26 | break; 27 | 28 | case WHAT_ITEM_SELECTED: 29 | loopview.onItemSelected(); 30 | break; 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/lib/OnItemSelectedRunnable.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.lib; 2 | 3 | final class OnItemSelectedRunnable implements Runnable { 4 | final WheelView loopView; 5 | 6 | OnItemSelectedRunnable(WheelView loopview) { 7 | loopView = loopview; 8 | } 9 | 10 | @Override 11 | public final void run() { 12 | loopView.onItemSelectedListener.onItemSelected(loopView.getCurrentItem()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/lib/SmoothScrollTimerTask.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.lib; 2 | 3 | import java.util.TimerTask; 4 | 5 | final class SmoothScrollTimerTask extends TimerTask { 6 | 7 | int realTotalOffset; 8 | int realOffset; 9 | int offset; 10 | final WheelView loopView; 11 | 12 | SmoothScrollTimerTask(WheelView loopview, int offset) { 13 | this.loopView = loopview; 14 | this.offset = offset; 15 | realTotalOffset = Integer.MAX_VALUE; 16 | realOffset = 0; 17 | } 18 | 19 | @Override 20 | public final void run() { 21 | if (realTotalOffset == Integer.MAX_VALUE) { 22 | realTotalOffset = offset; 23 | } 24 | //把要滚动的范围细分成十小份,按是小份单位来重绘 25 | realOffset = (int) ((float) realTotalOffset * 0.1F); 26 | 27 | if (realOffset == 0) { 28 | if (realTotalOffset < 0) { 29 | realOffset = -1; 30 | } else { 31 | realOffset = 1; 32 | } 33 | } 34 | 35 | if (Math.abs(realTotalOffset) <= 1) { 36 | loopView.cancelFuture(); 37 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_ITEM_SELECTED); 38 | } else { 39 | loopView.totalScrollY = loopView.totalScrollY + realOffset; 40 | 41 | //这里如果不是循环模式,则点击空白位置需要回滚,不然就会出现选到-1 item的 情况 42 | if (!loopView.isLoop) { 43 | float itemHeight = loopView.itemHeight; 44 | float top = (float) (-loopView.initPosition) * itemHeight; 45 | float bottom = (float) (loopView.getItemsCount() - 1 - loopView.initPosition) * itemHeight; 46 | if (loopView.totalScrollY <= top||loopView.totalScrollY >= bottom) { 47 | loopView.totalScrollY = loopView.totalScrollY - realOffset; 48 | loopView.cancelFuture(); 49 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_ITEM_SELECTED); 50 | return; 51 | } 52 | } 53 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW); 54 | realTotalOffset = realTotalOffset - realOffset; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/lib/WheelView.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.lib; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.graphics.Typeface; 9 | import android.os.Handler; 10 | import android.util.AttributeSet; 11 | import android.view.GestureDetector; 12 | import android.view.Gravity; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | 16 | import com.bigkoo.pickerview.R; 17 | import com.bigkoo.pickerview.adapter.WheelAdapter; 18 | import com.bigkoo.pickerview.listener.OnItemSelectedListener; 19 | 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.lang.reflect.Method; 22 | import java.util.concurrent.Executors; 23 | import java.util.concurrent.ScheduledExecutorService; 24 | import java.util.concurrent.ScheduledFuture; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | /** 28 | * 3d滚轮控件 29 | */ 30 | public class WheelView extends View { 31 | 32 | public enum ACTION { 33 | // 点击,滑翔(滑到尽头),拖拽事件 34 | CLICK, FLING, DAGGLE 35 | } 36 | Context context; 37 | 38 | Handler handler; 39 | private GestureDetector gestureDetector; 40 | OnItemSelectedListener onItemSelectedListener; 41 | 42 | // Timer mTimer; 43 | ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor(); 44 | private ScheduledFuture mFuture; 45 | 46 | Paint paintOuterText; 47 | Paint paintCenterText; 48 | Paint paintIndicator; 49 | 50 | WheelAdapter adapter; 51 | 52 | private String label;//附加单位 53 | int textSize;//选项的文字大小 54 | boolean customTextSize;//自定义文字大小,为true则用于使setTextSize函数无效,只能通过xml修改 55 | int maxTextWidth; 56 | int maxTextHeight; 57 | float itemHeight;//每行高度 58 | 59 | int textColorOut; 60 | int textColorCenter; 61 | int dividerColor; 62 | 63 | // 条目间距倍数 64 | static final float lineSpacingMultiplier = 1.4F; 65 | boolean isLoop; 66 | 67 | // 第一条线Y坐标值 68 | float firstLineY; 69 | //第二条线Y坐标 70 | float secondLineY; 71 | //中间Y坐标 72 | float centerY; 73 | 74 | //滚动总高度y值 75 | int totalScrollY; 76 | //初始化默认选中第几个 77 | int initPosition; 78 | //选中的Item是第几个 79 | private int selectedItem; 80 | int preCurrentIndex; 81 | //滚动偏移值,用于记录滚动了多少个item 82 | int change; 83 | 84 | // 显示几个条目 85 | int itemsVisible = 11; 86 | 87 | int measuredHeight; 88 | int measuredWidth; 89 | 90 | // 半圆周长 91 | int halfCircumference; 92 | // 半径 93 | int radius; 94 | 95 | private int mOffset = 0; 96 | private float previousY = 0; 97 | long startTime = 0; 98 | 99 | // 修改这个值可以改变滑行速度 100 | private static final int VELOCITYFLING = 5; 101 | int widthMeasureSpec; 102 | 103 | private int mGravity = Gravity.CENTER; 104 | private int drawCenterContentStart = 0;//中间选中文字开始绘制位置 105 | private int drawOutContentStart = 0;//非中间文字开始绘制位置 106 | private static final float SCALECONTENT = 0.8F;//非中间文字则用此控制高度,压扁形成3d错觉 107 | private static final float CENTERCONTENTOFFSET = 6;//中间文字文字居中需要此偏移值 108 | private static final String GETPICKERVIEWTEXT = "getPickerViewText";//反射的方法名 109 | 110 | public WheelView(Context context) { 111 | this(context, null); 112 | } 113 | 114 | public WheelView(Context context, AttributeSet attrs) { 115 | super(context, attrs); 116 | textColorOut = getResources().getColor(R.color.pickerview_wheelview_textcolor_out); 117 | textColorCenter = getResources().getColor(R.color.pickerview_wheelview_textcolor_center); 118 | dividerColor = getResources().getColor(R.color.pickerview_wheelview_textcolor_divider); 119 | //配合customTextSize使用,customTextSize为true才会发挥效果 120 | textSize = getResources().getDimensionPixelSize(R.dimen.pickerview_textsize); 121 | customTextSize = getResources().getBoolean(R.bool.pickerview_customTextSize); 122 | if(attrs != null) { 123 | TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.wheelview,0,0); 124 | mGravity = a.getInt(R.styleable.wheelview_gravity, Gravity.CENTER); 125 | textColorOut = a.getColor(R.styleable.wheelview_textColorOut, textColorOut); 126 | textColorCenter = a.getColor(R.styleable.wheelview_textColorCenter,textColorCenter); 127 | dividerColor = a.getColor(R.styleable.wheelview_dividerColor,dividerColor); 128 | textSize = a.getDimensionPixelOffset(R.styleable.wheelview_textSize,textSize); 129 | } 130 | initLoopView(context); 131 | } 132 | 133 | private void initLoopView(Context context) { 134 | this.context = context; 135 | handler = new MessageHandler(this); 136 | gestureDetector = new GestureDetector(context, new LoopViewGestureListener(this)); 137 | gestureDetector.setIsLongpressEnabled(false); 138 | 139 | isLoop = true; 140 | 141 | totalScrollY = 0; 142 | initPosition = -1; 143 | 144 | initPaints(); 145 | 146 | } 147 | 148 | private void initPaints() { 149 | paintOuterText = new Paint(); 150 | paintOuterText.setColor(textColorOut); 151 | paintOuterText.setAntiAlias(true); 152 | paintOuterText.setTypeface(Typeface.MONOSPACE); 153 | paintOuterText.setTextSize(textSize); 154 | 155 | paintCenterText = new Paint(); 156 | paintCenterText.setColor(textColorCenter); 157 | paintCenterText.setAntiAlias(true); 158 | paintCenterText.setTextScaleX(1.1F); 159 | paintCenterText.setTypeface(Typeface.MONOSPACE); 160 | paintCenterText.setTextSize(textSize); 161 | 162 | paintIndicator = new Paint(); 163 | paintIndicator.setColor(dividerColor); 164 | paintIndicator.setAntiAlias(true); 165 | 166 | if (android.os.Build.VERSION.SDK_INT >= 11) { 167 | setLayerType(LAYER_TYPE_SOFTWARE, null); 168 | } 169 | } 170 | 171 | private void remeasure() { 172 | if (adapter == null) { 173 | return; 174 | } 175 | 176 | measureTextWidthHeight(); 177 | 178 | //最大Text的高度乘间距倍数得到 可见文字实际的总高度,半圆的周长 179 | halfCircumference = (int) (itemHeight * (itemsVisible - 1)) ; 180 | //整个圆的周长除以PI得到直径,这个直径用作控件的总高度 181 | measuredHeight = (int) ((halfCircumference * 2) / Math.PI); 182 | //求出半径 183 | radius = (int) (halfCircumference / Math.PI); 184 | //控件宽度,这里支持weight 185 | measuredWidth = MeasureSpec.getSize(widthMeasureSpec); 186 | //计算两条横线和控件中间点的Y位置 187 | firstLineY = (measuredHeight - itemHeight) / 2.0F; 188 | secondLineY = (measuredHeight + itemHeight) / 2.0F; 189 | centerY = (measuredHeight + maxTextHeight) / 2.0F - CENTERCONTENTOFFSET; 190 | //初始化显示的item的position,根据是否loop 191 | if (initPosition == -1) { 192 | if (isLoop) { 193 | initPosition = (adapter.getItemsCount() + 1) / 2; 194 | } else { 195 | initPosition = 0; 196 | } 197 | } 198 | 199 | preCurrentIndex = initPosition; 200 | } 201 | 202 | /** 203 | * 计算最大len的Text的宽高度 204 | */ 205 | private void measureTextWidthHeight() { 206 | Rect rect = new Rect(); 207 | for (int i = 0; i < adapter.getItemsCount(); i++) { 208 | String s1 = getContentText(adapter.getItem(i)); 209 | paintCenterText.getTextBounds(s1, 0, s1.length(), rect); 210 | int textWidth = rect.width(); 211 | if (textWidth > maxTextWidth) { 212 | maxTextWidth = textWidth; 213 | } 214 | paintCenterText.getTextBounds("\u661F\u671F", 0, 2, rect); // 星期 215 | int textHeight = rect.height(); 216 | if (textHeight > maxTextHeight) { 217 | maxTextHeight = textHeight; 218 | } 219 | } 220 | itemHeight = lineSpacingMultiplier * maxTextHeight; 221 | } 222 | 223 | void smoothScroll(ACTION action) { 224 | cancelFuture(); 225 | if (action== ACTION.FLING||action== ACTION.DAGGLE) { 226 | mOffset = (int) ((totalScrollY%itemHeight + itemHeight) % itemHeight); 227 | if ((float) mOffset > itemHeight / 2.0F) { 228 | mOffset = (int) (itemHeight - (float) mOffset); 229 | } else { 230 | mOffset = -mOffset; 231 | } 232 | } 233 | //停止的时候,位置有偏移,不是全部都能正确停止到中间位置的,这里把文字位置挪回中间去 234 | mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, mOffset), 0, 10, TimeUnit.MILLISECONDS); 235 | } 236 | 237 | protected final void scrollBy(float velocityY) { 238 | cancelFuture(); 239 | 240 | mFuture = mExecutor.scheduleWithFixedDelay(new InertiaTimerTask(this, velocityY), 0, VELOCITYFLING, TimeUnit.MILLISECONDS); 241 | } 242 | 243 | public void cancelFuture() { 244 | if (mFuture!=null&&!mFuture.isCancelled()) { 245 | mFuture.cancel(true); 246 | mFuture = null; 247 | } 248 | } 249 | 250 | /** 251 | * 设置是否循环滚动 252 | * @param cyclic 253 | */ 254 | public final void setCyclic(boolean cyclic) { 255 | isLoop = cyclic; 256 | } 257 | 258 | public final void setTextSize(float size) { 259 | if (size > 0.0F&&!customTextSize) { 260 | textSize = (int) (context.getResources().getDisplayMetrics().density * size); 261 | paintOuterText.setTextSize(textSize); 262 | paintCenterText.setTextSize(textSize); 263 | } 264 | } 265 | 266 | public final void setCurrentItem(int currentItem) { 267 | this.initPosition = currentItem; 268 | totalScrollY = 0;//回归顶部,不然重设setCurrentItem的话位置会偏移的,就会显示出不对位置的数据 269 | invalidate(); 270 | } 271 | 272 | public final void setOnItemSelectedListener(OnItemSelectedListener OnItemSelectedListener) { 273 | this.onItemSelectedListener = OnItemSelectedListener; 274 | } 275 | 276 | public final void setAdapter(WheelAdapter adapter) { 277 | this.adapter = adapter; 278 | remeasure(); 279 | invalidate(); 280 | } 281 | 282 | public final WheelAdapter getAdapter(){ 283 | return adapter; 284 | } 285 | 286 | public final int getCurrentItem() { 287 | return selectedItem; 288 | } 289 | 290 | protected final void onItemSelected() { 291 | if (onItemSelectedListener != null) { 292 | postDelayed(new OnItemSelectedRunnable(this), 200L); 293 | } 294 | } 295 | 296 | @Override 297 | protected void onDraw(Canvas canvas) { 298 | if (adapter == null) { 299 | return; 300 | } 301 | //可见的item数组 302 | Object visibles[] = new Object[itemsVisible]; 303 | //滚动的Y值高度除去每行Item的高度,得到滚动了多少个item,即change数 304 | change = (int) (totalScrollY / itemHeight); 305 | try { 306 | //滚动中实际的预选中的item(即经过了中间位置的item) = 滑动前的位置 + 滑动相对位置 307 | preCurrentIndex = initPosition + change % adapter.getItemsCount(); 308 | }catch (ArithmeticException e){ 309 | System.out.println("出错了!adapter.getItemsCount() == 0,联动数据不匹配"); 310 | } 311 | if (!isLoop) {//不循环的情况 312 | if (preCurrentIndex < 0) { 313 | preCurrentIndex = 0; 314 | } 315 | if (preCurrentIndex > adapter.getItemsCount() - 1) { 316 | preCurrentIndex = adapter.getItemsCount() - 1; 317 | } 318 | } else {//循环 319 | if (preCurrentIndex < 0) {//举个例子:如果总数是5,preCurrentIndex = -1,那么preCurrentIndex按循环来说,其实是0的上面,也就是4的位置 320 | preCurrentIndex = adapter.getItemsCount() + preCurrentIndex; 321 | } 322 | if (preCurrentIndex > adapter.getItemsCount() - 1) {//同理上面,自己脑补一下 323 | preCurrentIndex = preCurrentIndex - adapter.getItemsCount(); 324 | } 325 | } 326 | 327 | //跟滚动流畅度有关,总滑动距离与每个item高度取余,即并不是一格格的滚动,每个item不一定滚到对应Rect里的,这个item对应格子的偏移值 328 | int itemHeightOffset = (int) (totalScrollY % itemHeight); 329 | // 设置数组中每个元素的值 330 | int counter = 0; 331 | while (counter < itemsVisible) { 332 | int index = preCurrentIndex - (itemsVisible / 2 - counter);//索引值,即当前在控件中间的item看作数据源的中间,计算出相对源数据源的index值 333 | 334 | //判断是否循环,如果是循环数据源也使用相对循环的position获取对应的item值,如果不是循环则超出数据源范围使用""空白字符串填充,在界面上形成空白无数据的item项 335 | if (isLoop) { 336 | index = getLoopMappingIndex(index); 337 | visibles[counter] = adapter.getItem(index); 338 | } else if (index < 0) { 339 | visibles[counter] = ""; 340 | } else if (index > adapter.getItemsCount() - 1) { 341 | visibles[counter] = ""; 342 | } else { 343 | visibles[counter] = adapter.getItem(index); 344 | } 345 | 346 | counter++; 347 | 348 | } 349 | 350 | //中间两条横线 351 | canvas.drawLine(0.0F, firstLineY, measuredWidth, firstLineY, paintIndicator); 352 | canvas.drawLine(0.0F, secondLineY, measuredWidth, secondLineY, paintIndicator); 353 | //单位的Label 354 | if(label != null) { 355 | int drawRightContentStart = measuredWidth - getTextWidth(paintCenterText,label); 356 | //靠右并留出空隙 357 | canvas.drawText(label, drawRightContentStart - CENTERCONTENTOFFSET, centerY, paintCenterText); 358 | } 359 | counter = 0; 360 | while (counter < itemsVisible) { 361 | canvas.save(); 362 | // L(弧长)=α(弧度)* r(半径) (弧度制) 363 | // 求弧度--> (L * π ) / (π * r) (弧长X派/半圆周长) 364 | float itemHeight = maxTextHeight * lineSpacingMultiplier; 365 | double radian = ((itemHeight * counter - itemHeightOffset) * Math.PI) / halfCircumference; 366 | // 弧度转换成角度(把半圆以Y轴为轴心向右转90度,使其处于第一象限及第四象限 367 | float angle = (float) (90D - (radian / Math.PI) * 180D); 368 | // 九十度以上的不绘制 369 | if (angle >= 90F || angle <= -90F) { 370 | canvas.restore(); 371 | } else { 372 | String contentText = getContentText(visibles[counter]); 373 | 374 | //计算开始绘制的位置 375 | measuredCenterContentStart(contentText); 376 | measuredOutContentStart(contentText); 377 | float translateY = (float) (radius - Math.cos(radian) * radius - (Math.sin(radian) * maxTextHeight) / 2D); 378 | //根据Math.sin(radian)来更改canvas坐标系原点,然后缩放画布,使得文字高度进行缩放,形成弧形3d视觉差 379 | canvas.translate(0.0F, translateY); 380 | canvas.scale(1.0F, (float) Math.sin(radian)); 381 | if (translateY <= firstLineY && maxTextHeight + translateY >= firstLineY) { 382 | // 条目经过第一条线 383 | canvas.save(); 384 | canvas.clipRect(0, 0, measuredWidth, firstLineY - translateY); 385 | canvas.scale(1.0F, (float) Math.sin(radian) * SCALECONTENT); 386 | canvas.drawText(contentText, drawOutContentStart, maxTextHeight, paintOuterText); 387 | canvas.restore(); 388 | canvas.save(); 389 | canvas.clipRect(0, firstLineY - translateY, measuredWidth, (int) (itemHeight)); 390 | canvas.scale(1.0F, (float) Math.sin(radian) * 1F); 391 | canvas.drawText(contentText, drawCenterContentStart, maxTextHeight - CENTERCONTENTOFFSET, paintCenterText); 392 | canvas.restore(); 393 | } else if (translateY <= secondLineY && maxTextHeight + translateY >= secondLineY) { 394 | // 条目经过第二条线 395 | canvas.save(); 396 | canvas.clipRect(0, 0, measuredWidth, secondLineY - translateY); 397 | canvas.scale(1.0F, (float) Math.sin(radian) * 1.0F); 398 | canvas.drawText(contentText, drawCenterContentStart, maxTextHeight - CENTERCONTENTOFFSET, paintCenterText); 399 | canvas.restore(); 400 | canvas.save(); 401 | canvas.clipRect(0, secondLineY - translateY, measuredWidth, (int) (itemHeight)); 402 | canvas.scale(1.0F, (float) Math.sin(radian) * SCALECONTENT); 403 | canvas.drawText(contentText, drawOutContentStart, maxTextHeight, paintOuterText); 404 | canvas.restore(); 405 | } else if (translateY >= firstLineY && maxTextHeight + translateY <= secondLineY) { 406 | // 中间条目 407 | canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight)); 408 | canvas.drawText(contentText, drawCenterContentStart, maxTextHeight - CENTERCONTENTOFFSET, paintCenterText); 409 | int preSelectedItem = adapter.indexOf(visibles[counter]); 410 | if(preSelectedItem != -1){ 411 | selectedItem = preSelectedItem; 412 | } 413 | } else { 414 | // 其他条目 415 | canvas.save(); 416 | canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight)); 417 | canvas.scale(1.0F, (float) Math.sin(radian) * SCALECONTENT); 418 | canvas.drawText(contentText, drawOutContentStart, maxTextHeight, paintOuterText); 419 | canvas.restore(); 420 | } 421 | canvas.restore(); 422 | } 423 | counter++; 424 | } 425 | } 426 | 427 | //递归计算出对应的index 428 | private int getLoopMappingIndex(int index){ 429 | if(index < 0){ 430 | index = index + adapter.getItemsCount(); 431 | index = getLoopMappingIndex(index); 432 | } 433 | else if (index > adapter.getItemsCount() - 1) { 434 | index = index - adapter.getItemsCount(); 435 | index = getLoopMappingIndex(index); 436 | } 437 | return index; 438 | } 439 | 440 | /** 441 | * 根据传进来的对象反射出getPickerViewText()方法,来获取需要显示的值 442 | * @param item 443 | * @return 444 | */ 445 | private String getContentText(Object item) { 446 | String contentText = item.toString(); 447 | try { 448 | Class clz = item.getClass(); 449 | Method m = clz.getMethod(GETPICKERVIEWTEXT); 450 | contentText = m.invoke(item, new Object[0]).toString(); 451 | } catch (NoSuchMethodException e) { 452 | } catch (InvocationTargetException e) { 453 | } catch (IllegalAccessException e) { 454 | } catch (Exception e){ 455 | } 456 | return contentText; 457 | } 458 | 459 | private void measuredCenterContentStart(String content) { 460 | Rect rect = new Rect(); 461 | paintCenterText.getTextBounds(content, 0, content.length(), rect); 462 | switch (mGravity){ 463 | case Gravity.CENTER: 464 | drawCenterContentStart = (int)((measuredWidth - rect.width()) * 0.5); 465 | break; 466 | case Gravity.LEFT: 467 | drawCenterContentStart = 0; 468 | break; 469 | case Gravity.RIGHT: 470 | drawCenterContentStart = measuredWidth - rect.width(); 471 | break; 472 | } 473 | } 474 | private void measuredOutContentStart(String content) { 475 | Rect rect = new Rect(); 476 | paintOuterText.getTextBounds(content, 0, content.length(), rect); 477 | switch (mGravity){ 478 | case Gravity.CENTER: 479 | drawOutContentStart = (int)((measuredWidth - rect.width()) * 0.5); 480 | break; 481 | case Gravity.LEFT: 482 | drawOutContentStart = 0; 483 | break; 484 | case Gravity.RIGHT: 485 | drawOutContentStart = measuredWidth - rect.width(); 486 | break; 487 | } 488 | } 489 | 490 | @Override 491 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 492 | this.widthMeasureSpec = widthMeasureSpec; 493 | remeasure(); 494 | setMeasuredDimension(measuredWidth, measuredHeight); 495 | } 496 | 497 | @Override 498 | public boolean onTouchEvent(MotionEvent event) { 499 | boolean eventConsumed = gestureDetector.onTouchEvent(event); 500 | switch (event.getAction()) { 501 | case MotionEvent.ACTION_DOWN: 502 | startTime = System.currentTimeMillis(); 503 | cancelFuture(); 504 | previousY = event.getRawY(); 505 | break; 506 | 507 | case MotionEvent.ACTION_MOVE: 508 | float dy = previousY - event.getRawY(); 509 | previousY = event.getRawY(); 510 | totalScrollY = (int) (totalScrollY + dy); 511 | 512 | // 边界处理。 513 | if (!isLoop) { 514 | float top = -initPosition * itemHeight; 515 | float bottom = (adapter.getItemsCount() - 1 - initPosition) * itemHeight; 516 | if(totalScrollY - itemHeight*0.3 < top){ 517 | top = totalScrollY - dy; 518 | } 519 | else if(totalScrollY + itemHeight*0.3 > bottom){ 520 | bottom = totalScrollY - dy; 521 | } 522 | 523 | if (totalScrollY < top) { 524 | totalScrollY = (int) top; 525 | } else if (totalScrollY > bottom) { 526 | totalScrollY = (int) bottom; 527 | } 528 | } 529 | break; 530 | 531 | case MotionEvent.ACTION_UP: 532 | default: 533 | if (!eventConsumed) { 534 | float y = event.getY(); 535 | double l = Math.acos((radius - y) / radius) * radius; 536 | int circlePosition = (int) ((l + itemHeight / 2) / itemHeight); 537 | 538 | float extraOffset = (totalScrollY % itemHeight + itemHeight) % itemHeight; 539 | mOffset = (int) ((circlePosition - itemsVisible / 2) * itemHeight - extraOffset); 540 | 541 | if ((System.currentTimeMillis() - startTime) > 120) { 542 | // 处理拖拽事件 543 | smoothScroll(ACTION.DAGGLE); 544 | } else { 545 | // 处理条目点击事件 546 | smoothScroll(ACTION.CLICK); 547 | } 548 | } 549 | break; 550 | } 551 | invalidate(); 552 | 553 | return true; 554 | } 555 | 556 | /** 557 | * 获取Item个数 558 | * @return 559 | */ 560 | public int getItemsCount() { 561 | return adapter != null ? adapter.getItemsCount() : 0; 562 | } 563 | 564 | /** 565 | * 附加在右边的单位字符串 566 | * @param label 567 | */ 568 | public void setLabel(String label){ 569 | this.label = label; 570 | } 571 | 572 | public void setGravity(int gravity) { 573 | this.mGravity = gravity; 574 | } 575 | 576 | public int getTextWidth(Paint paint, String str) { 577 | int iRet = 0; 578 | if (str != null && str.length() > 0) { 579 | int len = str.length(); 580 | float[] widths = new float[len]; 581 | paint.getTextWidths(str, widths); 582 | for (int j = 0; j < len; j++) { 583 | iRet += (int) Math.ceil(widths[j]); 584 | } 585 | } 586 | return iRet; 587 | } 588 | } -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/listener/OnDismissListener.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.listener; 2 | 3 | /** 4 | * Created by Sai on 15/8/9. 5 | */ 6 | public interface OnDismissListener { 7 | public void onDismiss(Object o); 8 | } 9 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/listener/OnItemSelectedListener.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.listener; 2 | 3 | 4 | public interface OnItemSelectedListener { 5 | void onItemSelected(int index); 6 | } 7 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/utils/PickerViewAnimateUtil.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.utils; 2 | 3 | import android.view.Gravity; 4 | 5 | import com.bigkoo.pickerview.R; 6 | 7 | /** 8 | * Created by Sai on 15/8/9. 9 | */ 10 | public class PickerViewAnimateUtil { 11 | private static final int INVALID = -1; 12 | /** 13 | * Get default animation resource when not defined by the user 14 | * 15 | * @param gravity the gravity of the dialog 16 | * @param isInAnimation determine if is in or out animation. true when is is 17 | * @return the id of the animation resource 18 | */ 19 | public static int getAnimationResource(int gravity, boolean isInAnimation) { 20 | switch (gravity) { 21 | case Gravity.BOTTOM: 22 | return isInAnimation ? R.anim.slide_in_bottom : R.anim.slide_out_bottom; 23 | } 24 | return INVALID; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/view/BasePickerView.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.view; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.view.Gravity; 6 | import android.view.LayoutInflater; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.view.animation.Animation; 11 | import android.view.animation.AnimationUtils; 12 | import android.widget.FrameLayout; 13 | 14 | import com.bigkoo.pickerview.utils.PickerViewAnimateUtil; 15 | import com.bigkoo.pickerview.R; 16 | import com.bigkoo.pickerview.listener.OnDismissListener; 17 | 18 | /** 19 | * Created by Sai on 15/11/22. 20 | * 精仿iOSPickerViewController控件 21 | */ 22 | public class BasePickerView { 23 | private final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( 24 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM 25 | ); 26 | 27 | protected Context context; 28 | protected ViewGroup contentContainer; 29 | private ViewGroup decorView;//activity的根View 30 | private ViewGroup rootView;//附加View 的 根View 31 | 32 | private OnDismissListener onDismissListener; 33 | private boolean isDismissing; 34 | 35 | private Animation outAnim; 36 | private Animation inAnim; 37 | private int gravity = Gravity.BOTTOM; 38 | 39 | public BasePickerView(Context context){ 40 | this.context = context; 41 | 42 | initViews(); 43 | init(); 44 | initEvents(); 45 | } 46 | 47 | protected void initViews(){ 48 | LayoutInflater layoutInflater = LayoutInflater.from(context); 49 | decorView = (ViewGroup) ((Activity)context).getWindow().getDecorView().findViewById(android.R.id.content); 50 | rootView = (ViewGroup) layoutInflater.inflate(R.layout.layout_basepickerview, decorView, false); 51 | rootView.setLayoutParams(new FrameLayout.LayoutParams( 52 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT 53 | )); 54 | contentContainer = (ViewGroup) rootView.findViewById(R.id.content_container); 55 | contentContainer.setLayoutParams(params); 56 | } 57 | 58 | protected void init() { 59 | inAnim = getInAnimation(); 60 | outAnim = getOutAnimation(); 61 | } 62 | protected void initEvents() { 63 | } 64 | /** 65 | * show的时候调用 66 | * 67 | * @param view 这个View 68 | */ 69 | private void onAttached(View view) { 70 | decorView.addView(view); 71 | contentContainer.startAnimation(inAnim); 72 | } 73 | /** 74 | * 添加这个View到Activity的根视图 75 | */ 76 | public void show() { 77 | if (isShowing()) { 78 | return; 79 | } 80 | onAttached(rootView); 81 | } 82 | /** 83 | * 检测该View是不是已经添加到根视图 84 | * 85 | * @return 如果视图已经存在该View返回true 86 | */ 87 | public boolean isShowing() { 88 | View view = decorView.findViewById(R.id.outmost_container); 89 | return view != null; 90 | } 91 | public void dismiss() { 92 | if (isDismissing) { 93 | return; 94 | } 95 | 96 | //消失动画 97 | outAnim.setAnimationListener(new Animation.AnimationListener() { 98 | @Override 99 | public void onAnimationStart(Animation animation) { 100 | 101 | } 102 | 103 | @Override 104 | public void onAnimationEnd(Animation animation) { 105 | decorView.post(new Runnable() { 106 | @Override 107 | public void run() { 108 | //从activity根视图移除 109 | decorView.removeView(rootView); 110 | isDismissing = false; 111 | if (onDismissListener != null) { 112 | onDismissListener.onDismiss(BasePickerView.this); 113 | } 114 | } 115 | }); 116 | } 117 | 118 | @Override 119 | public void onAnimationRepeat(Animation animation) { 120 | 121 | } 122 | }); 123 | contentContainer.startAnimation(outAnim); 124 | isDismissing = true; 125 | } 126 | public Animation getInAnimation() { 127 | int res = PickerViewAnimateUtil.getAnimationResource(this.gravity, true); 128 | return AnimationUtils.loadAnimation(context, res); 129 | } 130 | 131 | public Animation getOutAnimation() { 132 | int res = PickerViewAnimateUtil.getAnimationResource(this.gravity, false); 133 | return AnimationUtils.loadAnimation(context, res); 134 | } 135 | 136 | public BasePickerView setOnDismissListener(OnDismissListener onDismissListener) { 137 | this.onDismissListener = onDismissListener; 138 | return this; 139 | } 140 | 141 | public BasePickerView setCancelable(boolean isCancelable) { 142 | View view = rootView.findViewById(R.id.outmost_container); 143 | 144 | if (isCancelable) { 145 | view.setOnTouchListener(onCancelableTouchListener); 146 | } 147 | else{ 148 | view.setOnTouchListener(null); 149 | } 150 | return this; 151 | } 152 | /** 153 | * Called when the user touch on black overlay in order to dismiss the dialog 154 | */ 155 | private final View.OnTouchListener onCancelableTouchListener = new View.OnTouchListener() { 156 | @Override 157 | public boolean onTouch(View v, MotionEvent event) { 158 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 159 | dismiss(); 160 | } 161 | return false; 162 | } 163 | }; 164 | 165 | public View findViewById(int id){ 166 | return contentContainer.findViewById(id); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/view/WheelOptions.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.view; 2 | 3 | import java.util.ArrayList; 4 | import android.view.View; 5 | 6 | import com.bigkoo.pickerview.R; 7 | import com.bigkoo.pickerview.adapter.ArrayWheelAdapter; 8 | import com.bigkoo.pickerview.lib.WheelView; 9 | import com.bigkoo.pickerview.listener.OnItemSelectedListener; 10 | 11 | public class WheelOptions { 12 | private View view; 13 | private WheelView wv_option1; 14 | private WheelView wv_option2; 15 | private WheelView wv_option3; 16 | 17 | private ArrayList mOptions1Items; 18 | private ArrayList> mOptions2Items; 19 | private ArrayList>> mOptions3Items; 20 | 21 | private boolean linkage = false; 22 | private OnItemSelectedListener wheelListener_option1; 23 | private OnItemSelectedListener wheelListener_option2; 24 | 25 | public View getView() { 26 | return view; 27 | } 28 | 29 | public void setView(View view) { 30 | this.view = view; 31 | } 32 | 33 | public WheelOptions(View view) { 34 | super(); 35 | this.view = view; 36 | setView(view); 37 | } 38 | 39 | public void setPicker(ArrayList optionsItems) { 40 | setPicker(optionsItems, null, null, false); 41 | } 42 | 43 | public void setPicker(ArrayList options1Items, 44 | ArrayList> options2Items, boolean linkage) { 45 | setPicker(options1Items, options2Items, null, linkage); 46 | } 47 | 48 | public void setPicker(ArrayList options1Items, 49 | ArrayList> options2Items, 50 | ArrayList>> options3Items, 51 | boolean linkage) { 52 | this.linkage = linkage; 53 | this.mOptions1Items = options1Items; 54 | this.mOptions2Items = options2Items; 55 | this.mOptions3Items = options3Items; 56 | int len = ArrayWheelAdapter.DEFAULT_LENGTH; 57 | if (this.mOptions3Items == null) 58 | len = 8; 59 | if (this.mOptions2Items == null) 60 | len = 12; 61 | // 选项1 62 | wv_option1 = (WheelView) view.findViewById(R.id.options1); 63 | wv_option1.setAdapter(new ArrayWheelAdapter(mOptions1Items, len));// 设置显示数据 64 | wv_option1.setCurrentItem(0);// 初始化时显示的数据 65 | // 选项2 66 | wv_option2 = (WheelView) view.findViewById(R.id.options2); 67 | if (mOptions2Items != null) 68 | wv_option2.setAdapter(new ArrayWheelAdapter(mOptions2Items.get(0)));// 设置显示数据 69 | wv_option2.setCurrentItem(wv_option1.getCurrentItem());// 初始化时显示的数据 70 | // 选项3 71 | wv_option3 = (WheelView) view.findViewById(R.id.options3); 72 | if (mOptions3Items != null) 73 | wv_option3.setAdapter(new ArrayWheelAdapter(mOptions3Items.get(0) 74 | .get(0)));// 设置显示数据 75 | wv_option3.setCurrentItem(wv_option3.getCurrentItem());// 初始化时显示的数据 76 | 77 | int textSize = 25; 78 | 79 | wv_option1.setTextSize(textSize); 80 | wv_option2.setTextSize(textSize); 81 | wv_option3.setTextSize(textSize); 82 | 83 | if (this.mOptions2Items == null) 84 | wv_option2.setVisibility(View.GONE); 85 | if (this.mOptions3Items == null) 86 | wv_option3.setVisibility(View.GONE); 87 | 88 | // 联动监听器 89 | wheelListener_option1 = new OnItemSelectedListener() { 90 | 91 | @Override 92 | public void onItemSelected(int index) { 93 | int opt2Select = 0; 94 | if (mOptions2Items != null) { 95 | opt2Select = wv_option2.getCurrentItem();//上一个opt2的选中位置 96 | //新opt2的位置,判断如果旧位置没有超过数据范围,则沿用旧位置,否则选中最后一项 97 | opt2Select = opt2Select >= mOptions2Items.get(index).size() - 1 ? mOptions2Items.get(index).size() - 1 : opt2Select; 98 | 99 | wv_option2.setAdapter(new ArrayWheelAdapter(mOptions2Items 100 | .get(index))); 101 | wv_option2.setCurrentItem(opt2Select); 102 | } 103 | if (mOptions3Items != null) { 104 | wheelListener_option2.onItemSelected(opt2Select); 105 | } 106 | } 107 | }; 108 | wheelListener_option2 = new OnItemSelectedListener() { 109 | 110 | @Override 111 | public void onItemSelected(int index) { 112 | if (mOptions3Items != null) { 113 | int opt1Select = wv_option1.getCurrentItem(); 114 | opt1Select = opt1Select >= mOptions3Items.size() - 1 ? mOptions3Items.size() - 1 : opt1Select; 115 | index = index >= mOptions2Items.get(opt1Select).size() - 1 ? mOptions2Items.get(opt1Select).size() - 1 : index; 116 | int opt3 = wv_option3.getCurrentItem();//上一个opt3的选中位置 117 | //新opt3的位置,判断如果旧位置没有超过数据范围,则沿用旧位置,否则选中最后一项 118 | opt3 = opt3 >= mOptions3Items.get(opt1Select).get(index).size() - 1 ? mOptions3Items.get(opt1Select).get(index).size() - 1 : opt3; 119 | 120 | wv_option3.setAdapter(new ArrayWheelAdapter(mOptions3Items 121 | .get(wv_option1.getCurrentItem()).get( 122 | index))); 123 | wv_option3.setCurrentItem(opt3); 124 | 125 | } 126 | } 127 | }; 128 | 129 | // // 添加联动监听 130 | if (options2Items != null && linkage) 131 | wv_option1.setOnItemSelectedListener(wheelListener_option1); 132 | if (options3Items != null && linkage) 133 | wv_option2.setOnItemSelectedListener(wheelListener_option2); 134 | } 135 | 136 | /** 137 | * 设置选项的单位 138 | * 139 | * @param label1 140 | * @param label2 141 | * @param label3 142 | */ 143 | public void setLabels(String label1, String label2, String label3) { 144 | if (label1 != null) 145 | wv_option1.setLabel(label1); 146 | if (label2 != null) 147 | wv_option2.setLabel(label2); 148 | if (label3 != null) 149 | wv_option3.setLabel(label3); 150 | } 151 | 152 | /** 153 | * 设置是否循环滚动 154 | * 155 | * @param cyclic 156 | */ 157 | public void setCyclic(boolean cyclic) { 158 | wv_option1.setCyclic(cyclic); 159 | wv_option2.setCyclic(cyclic); 160 | wv_option3.setCyclic(cyclic); 161 | } 162 | 163 | /** 164 | * 分别设置第一二三级是否循环滚动 165 | * 166 | * @param cyclic1,cyclic2,cyclic3 167 | */ 168 | public void setCyclic(boolean cyclic1,boolean cyclic2,boolean cyclic3) { 169 | wv_option1.setCyclic(cyclic1); 170 | wv_option2.setCyclic(cyclic2); 171 | wv_option3.setCyclic(cyclic3); 172 | } 173 | /** 174 | * 设置第二级是否循环滚动 175 | * 176 | * @param cyclic 177 | */ 178 | public void setOption2Cyclic(boolean cyclic) { 179 | wv_option2.setCyclic(cyclic); 180 | } 181 | /** 182 | * 设置第三级是否循环滚动 183 | * 184 | * @param cyclic 185 | */ 186 | public void setOption3Cyclic(boolean cyclic) { 187 | wv_option3.setCyclic(cyclic); 188 | } 189 | 190 | /** 191 | * 返回当前选中的结果对应的位置数组 因为支持三级联动效果,分三个级别索引,0,1,2 192 | * 193 | * @return 194 | */ 195 | public int[] getCurrentItems() { 196 | int[] currentItems = new int[3]; 197 | currentItems[0] = wv_option1.getCurrentItem(); 198 | currentItems[1] = wv_option2.getCurrentItem(); 199 | currentItems[2] = wv_option3.getCurrentItem(); 200 | return currentItems; 201 | } 202 | 203 | public void setCurrentItems(int option1, int option2, int option3) { 204 | if(linkage){ 205 | itemSelected(option1, option2, option3); 206 | } 207 | wv_option1.setCurrentItem(option1); 208 | wv_option2.setCurrentItem(option2); 209 | wv_option3.setCurrentItem(option3); 210 | } 211 | 212 | private void itemSelected(int opt1Select, int opt2Select, int opt3Select) { 213 | if (mOptions2Items != null) { 214 | wv_option2.setAdapter(new ArrayWheelAdapter(mOptions2Items 215 | .get(opt1Select))); 216 | wv_option2.setCurrentItem(opt2Select); 217 | } 218 | if (mOptions3Items != null) { 219 | wv_option3.setAdapter(new ArrayWheelAdapter(mOptions3Items 220 | .get(opt1Select).get( 221 | opt2Select))); 222 | wv_option3.setCurrentItem(opt3Select); 223 | } 224 | } 225 | 226 | 227 | } 228 | -------------------------------------------------------------------------------- /pickerview/src/main/java/com/bigkoo/pickerview/view/WheelTime.java: -------------------------------------------------------------------------------- 1 | package com.bigkoo.pickerview.view; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import com.bigkoo.pickerview.R; 9 | import com.bigkoo.pickerview.TimePickerView.Type; 10 | import com.bigkoo.pickerview.adapter.NumericWheelAdapter; 11 | import com.bigkoo.pickerview.lib.WheelView; 12 | import com.bigkoo.pickerview.listener.OnItemSelectedListener; 13 | 14 | import android.content.Context; 15 | import android.view.View; 16 | 17 | 18 | public class WheelTime { 19 | public static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 20 | private View view; 21 | private WheelView wv_year; 22 | private WheelView wv_month; 23 | private WheelView wv_day; 24 | private WheelView wv_hours; 25 | private WheelView wv_mins; 26 | 27 | private Type type; 28 | public static final int DEFULT_START_YEAR = 1990; 29 | public static final int DEFULT_END_YEAR = 2100; 30 | private int startYear = DEFULT_START_YEAR; 31 | private int endYear = DEFULT_END_YEAR; 32 | 33 | private int endMonth = 12; 34 | private int endDay = 31; 35 | 36 | 37 | // private OnItemChangeListener onItemChangeListener = new OnItemChangeListener() { 38 | // @Override 39 | // public void onItemChange(int year, int month, int day) { 40 | // } 41 | // }; 42 | 43 | public WheelTime(View view) { 44 | this(view, Type.ALL); 45 | } 46 | public WheelTime(View view,Type type) { 47 | super(); 48 | this.view = view; 49 | this.type = type; 50 | setView(view); 51 | } 52 | public void setPicker(int year ,int month,int day){ 53 | this.setPicker(year, month, day, 0, 0); 54 | } 55 | 56 | /** 57 | * 58 | * @param year 真实年 59 | * @param month 真实月 - 1 60 | * @param day 真实日 61 | */ 62 | public void setPicker(int year ,int month ,int day,int h,int m) { 63 | // 添加大小月月份并将其转换为list,方便之后的判断 64 | String[] months_big = { "1", "3", "5", "7", "8", "10", "12" }; 65 | String[] months_little = { "4", "6", "9", "11" }; 66 | 67 | final List list_big = Arrays.asList(months_big); 68 | final List list_little = Arrays.asList(months_little); 69 | 70 | Context context = view.getContext(); 71 | // 年 72 | wv_year = (WheelView) view.findViewById(R.id.year); 73 | wv_year.setAdapter(new NumericWheelAdapter(startYear, endYear));// 设置"年"的显示数据 74 | wv_year.setLabel(context.getString(R.string.pickerview_year));// 添加文字 75 | wv_year.setCurrentItem(year - startYear);// 初始化时显示的数据 76 | 77 | // 月 78 | wv_month = (WheelView) view.findViewById(R.id.month); 79 | if(year == endYear){ 80 | wv_month.setAdapter(new NumericWheelAdapter(1, endMonth)); 81 | }else { 82 | wv_month.setAdapter(new NumericWheelAdapter(1, 12)); 83 | } 84 | wv_month.setLabel(context.getString(R.string.pickerview_month)); 85 | wv_month.setCurrentItem(month); 86 | 87 | // 日 88 | wv_day = (WheelView) view.findViewById(R.id.day); 89 | // 判断大小月及是否闰年,用来确定"日"的数据 90 | if(year == endYear && month + 1 == endMonth){ 91 | wv_day.setAdapter(new NumericWheelAdapter(1, endDay)); 92 | }else{ 93 | if (list_big.contains(String.valueOf(month + 1))) { 94 | wv_day.setAdapter(new NumericWheelAdapter(1, 31)); 95 | } else if (list_little.contains(String.valueOf(month + 1))) { 96 | wv_day.setAdapter(new NumericWheelAdapter(1, 30)); 97 | } else { 98 | // 闰年 99 | if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){ 100 | wv_day.setAdapter(new NumericWheelAdapter(1, 29)); 101 | } 102 | else{ 103 | wv_day.setAdapter(new NumericWheelAdapter(1, 28)); 104 | } 105 | } 106 | } 107 | wv_day.setLabel(context.getString(R.string.pickerview_day)); 108 | wv_day.setCurrentItem(day - 1); 109 | 110 | wv_hours = (WheelView)view.findViewById(R.id.hour); 111 | wv_hours.setAdapter(new NumericWheelAdapter(0, 23)); 112 | wv_hours.setLabel(context.getString(R.string.pickerview_hours));// 添加文字 113 | wv_hours.setCurrentItem(h); 114 | 115 | wv_mins = (WheelView)view.findViewById(R.id.min); 116 | wv_mins.setAdapter(new NumericWheelAdapter(0, 59)); 117 | wv_mins.setLabel(context.getString(R.string.pickerview_minutes));// 添加文字 118 | wv_mins.setCurrentItem(m); 119 | 120 | // 添加"年"监听 121 | OnItemSelectedListener wheelListener_year = new OnItemSelectedListener() { 122 | @Override 123 | public void onItemSelected(int index) { 124 | 125 | int year_num = index + startYear; 126 | 127 | if(year_num == endYear){ 128 | wv_month.setAdapter(new NumericWheelAdapter(1, endMonth)); 129 | if(wv_month.getCurrentItem() + 1 > endMonth){ 130 | wv_month.setCurrentItem(endMonth - 1); 131 | wv_day.setAdapter(new NumericWheelAdapter(1, endDay)); 132 | wv_day.setCurrentItem(endDay - 1); 133 | } 134 | }else{ 135 | wv_month.setAdapter(new NumericWheelAdapter(1, 12)); 136 | 137 | // 判断大小月及是否闰年,用来确定"日"的数据 138 | int maxItem = 30; 139 | if (list_big.contains(String.valueOf(wv_month.getCurrentItem() + 1))) { 140 | wv_day.setAdapter(new NumericWheelAdapter(1, 31)); 141 | maxItem = 31; 142 | } else if (list_little.contains(String.valueOf(wv_month.getCurrentItem() + 1))) { 143 | wv_day.setAdapter(new NumericWheelAdapter(1, 30)); 144 | maxItem = 30; 145 | } else { 146 | if ((year_num % 4 == 0 && year_num % 100 != 0)|| year_num % 400 == 0){ 147 | wv_day.setAdapter(new NumericWheelAdapter(1, 29)); 148 | maxItem = 29; 149 | } 150 | else{ 151 | wv_day.setAdapter(new NumericWheelAdapter(1, 28)); 152 | maxItem = 28; 153 | } 154 | } 155 | if (wv_day.getCurrentItem() > maxItem - 1) { 156 | wv_day.setCurrentItem(maxItem - 1); 157 | } 158 | // onItemChangeListener.onItemChange(wv_year.getCurrentItem() + startYear, wv_month.getCurrentItem() + 1, wv_day.getCurrentItem() + 1); 159 | } 160 | 161 | 162 | } 163 | }; 164 | // 添加"月"监听 165 | OnItemSelectedListener wheelListener_month = new OnItemSelectedListener() { 166 | @Override 167 | public void onItemSelected(int index) { 168 | int year_num = wv_year.getCurrentItem() + startYear; 169 | int month_num = index + 1; 170 | int maxItem = 30; 171 | 172 | if(year_num == endYear && month_num == endMonth){ 173 | wv_day.setAdapter(new NumericWheelAdapter(1, endDay)); 174 | if(wv_day.getCurrentItem() + 1 > endDay){ 175 | wv_day.setCurrentItem(endDay - 1); 176 | } 177 | // onItemChangeListener.onItemChange(wv_year.getCurrentItem() + startYear, wv_month.getCurrentItem() + 1, wv_day.getCurrentItem() + 1); 178 | }else{ 179 | // 判断大小月及是否闰年,用来确定"日"的数据 180 | if (list_big.contains(String.valueOf(month_num))) { 181 | wv_day.setAdapter(new NumericWheelAdapter(1, 31)); 182 | maxItem = 31; 183 | } else if (list_little.contains(String.valueOf(month_num))) { 184 | wv_day.setAdapter(new NumericWheelAdapter(1, 30)); 185 | maxItem = 30; 186 | } else { 187 | if ((year_num % 4 == 0 && year_num % 100 != 0) 188 | || year_num % 400 == 0){ 189 | wv_day.setAdapter(new NumericWheelAdapter(1, 29)); 190 | maxItem = 29; 191 | } 192 | else{ 193 | wv_day.setAdapter(new NumericWheelAdapter(1, 28)); 194 | maxItem = 28; 195 | } 196 | } 197 | if (wv_day.getCurrentItem() > maxItem - 1) { 198 | wv_day.setCurrentItem(maxItem - 1); 199 | } 200 | // onItemChangeListener.onItemChange(wv_year.getCurrentItem() + startYear, wv_month.getCurrentItem() + 1, wv_day.getCurrentItem() + 1); 201 | } 202 | 203 | 204 | } 205 | }; 206 | wv_year.setOnItemSelectedListener(wheelListener_year); 207 | wv_month.setOnItemSelectedListener(wheelListener_month); 208 | 209 | // wv_day.setOnItemSelectedListener(new OnItemSelectedListener() { 210 | // @Override 211 | // public void onItemSelected(int index) { 212 | // onItemChangeListener.onItemChange(wv_year.getCurrentItem() + startYear, wv_month.getCurrentItem() + 1, wv_day.getCurrentItem() + 1); 213 | // } 214 | // }); 215 | 216 | // 根据屏幕密度来指定选择器字体的大小(不同屏幕可能不同) 217 | int textSize = 6; 218 | switch(type){ 219 | case ALL: 220 | textSize = textSize * 3; 221 | break; 222 | case YEAR_MONTH_DAY: 223 | textSize = textSize * 4; 224 | wv_hours.setVisibility(View.GONE); 225 | wv_mins.setVisibility(View.GONE); 226 | break; 227 | case HOURS_MINS: 228 | textSize = textSize * 4; 229 | wv_year.setVisibility(View.GONE); 230 | wv_month.setVisibility(View.GONE); 231 | wv_day.setVisibility(View.GONE); 232 | break; 233 | case MONTH_DAY_HOUR_MIN: 234 | textSize = textSize * 3; 235 | wv_year.setVisibility(View.GONE); 236 | break; 237 | case YEAR_MONTH: 238 | textSize = textSize * 4; 239 | wv_day.setVisibility(View.GONE); 240 | wv_hours.setVisibility(View.GONE); 241 | wv_mins.setVisibility(View.GONE); 242 | } 243 | wv_day.setTextSize(textSize); 244 | wv_month.setTextSize(textSize); 245 | wv_year.setTextSize(textSize); 246 | wv_hours.setTextSize(textSize); 247 | wv_mins.setTextSize(textSize); 248 | 249 | } 250 | 251 | /** 252 | * 设置是否循环滚动 253 | * @param cyclic 254 | */ 255 | public void setCyclic(boolean cyclic){ 256 | wv_year.setCyclic(cyclic); 257 | wv_month.setCyclic(cyclic); 258 | wv_day.setCyclic(cyclic); 259 | wv_hours.setCyclic(cyclic); 260 | wv_mins.setCyclic(cyclic); 261 | } 262 | public String getTime() { 263 | StringBuffer sb = new StringBuffer(); 264 | sb.append((wv_year.getCurrentItem() + startYear)).append("-") 265 | .append((wv_month.getCurrentItem() + 1)).append("-") 266 | .append((wv_day.getCurrentItem() + 1)).append(" ") 267 | .append(wv_hours.getCurrentItem()).append(":") 268 | .append(wv_mins.getCurrentItem()); 269 | return sb.toString(); 270 | } 271 | 272 | public View getView() { 273 | return view; 274 | } 275 | 276 | public void setView(View view) { 277 | this.view = view; 278 | } 279 | 280 | public int getStartYear() { 281 | return startYear; 282 | } 283 | 284 | public void setStartYear(int startYear) { 285 | this.startYear = startYear; 286 | } 287 | 288 | public int getEndYear() { 289 | return endYear; 290 | } 291 | 292 | public void setEndYear(int endYear) { 293 | this.endYear = endYear; 294 | } 295 | 296 | public void setEndMonthDay(int endMonth, int endDay){ 297 | this.endMonth = endMonth; 298 | this.endDay = endDay; 299 | } 300 | 301 | // public interface OnItemChangeListener{ 302 | // void onItemChange(int year, int month, int day); 303 | // } 304 | 305 | // public void setOnItemChangeListener(OnItemChangeListener onItemChangeListener){ 306 | // this.onItemChangeListener = onItemChangeListener; 307 | // } 308 | } 309 | -------------------------------------------------------------------------------- /pickerview/src/main/res/anim/slide_in_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /pickerview/src/main/res/anim/slide_out_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /pickerview/src/main/res/drawable/selector_pickerview_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pickerview/src/main/res/layout/include_pickerview_topbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |