├── .gitignore ├── ART ├── hugely_exaggerated_wrap_demo.png ├── label_bottomindicator_demo.png ├── label_support.png ├── screen.gif ├── screen1.png ├── screen2.png └── web_hi_res_512.png ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── gradle-mvn-push.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── badoualy │ │ └── stepperindicator │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── badoualy │ │ │ └── stepperindicator │ │ │ └── StepperIndicator.java │ └── res │ │ ├── drawable-hdpi │ │ └── ic_done_white_18dp.png │ │ ├── drawable-mdpi │ │ └── ic_done_white_18dp.png │ │ ├── drawable-xhdpi │ │ └── ic_done_white_18dp.png │ │ ├── drawable-xxhdpi │ │ └── ic_done_white_18dp.png │ │ ├── drawable-xxxhdpi │ │ └── ic_done_white_18dp.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ └── dimens.xml │ └── test │ └── java │ └── com │ └── badoualy │ └── stepperindicator │ └── ExampleUnitTest.java ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── badoualy │ │ └── stepperindicator │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── badoualy │ │ │ └── stepperindicator │ │ │ └── sample │ │ │ ├── MainActivity.java │ │ │ ├── PageFragment.java │ │ │ └── PagerAdapter.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── fragment_page.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── badoualy │ └── stepperindicator │ └── ExampleUnitTestSample.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | #IntelliJ files 30 | *.iml 31 | .idea/ 32 | 33 | # OS 34 | .DS_STORE 35 | thumbs.db -------------------------------------------------------------------------------- /ART/hugely_exaggerated_wrap_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/ART/hugely_exaggerated_wrap_demo.png -------------------------------------------------------------------------------- /ART/label_bottomindicator_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/ART/label_bottomindicator_demo.png -------------------------------------------------------------------------------- /ART/label_support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/ART/label_support.png -------------------------------------------------------------------------------- /ART/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/ART/screen.gif -------------------------------------------------------------------------------- /ART/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/ART/screen1.png -------------------------------------------------------------------------------- /ART/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/ART/screen2.png -------------------------------------------------------------------------------- /ART/web_hi_res_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/ART/web_hi_res_512.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yannick Badoual 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Release](https://jitpack.io/v/badoualy/stepper-indicator.svg)](https://jitpack.io/#badoualy/stepper-indicator) 2 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-stepper--indicator-green.svg?style=true)](https://android-arsenal.com/details/1/3711) 3 | 4 | # ![](https://github.com/badoualy/stepper-indicator/blob/master/sample/src/main/res/mipmap-mdpi/ic_launcher.png) Stepper indicator 5 | > ### Designed by the awesome https://dribbble.com/LeslyPyram :) 6 | > #### iOS (swift) version available at https://github.com/chenyun122/StepIndicator 7 | 8 | 9 | 10 | Sample 11 | ---------------- 12 | 13 | You can checkout the [Sample Application](https://play.google.com/store/apps/details?id=com.badoualy.stepperindicator.sample) on the Play Store 14 | 15 | Setup 16 | ---------------- 17 | 18 | First, add jitpack in your build.gradle at the end of repositories: 19 | ```gradle 20 | repositories { 21 | // ... 22 | maven { url "https://jitpack.io" } 23 | } 24 | ``` 25 | 26 | Then, add the library dependency: 27 | ```gradle 28 | compile 'com.github.badoualy:stepper-indicator:1.0.7' 29 | ``` 30 | 31 | Now go do some awesome stuff! 32 | 33 | Usage 34 | ---------------- 35 | 36 | ```xml 37 | 41 | ``` 42 | 43 | Attributes: 44 | 45 | | Name | Description | Default value | 46 | |------------------------|-----------------------------------------------------|-----------------| 47 | | stpi_animDuration | duration of the line tracing animation | 250 ms | 48 | | stpi_stepCount | number of pages/steps | | 49 | | stpi_circleColor | color of the stroke circle | #b3bdc2 (grey) | 50 | | stpi_circleRadius | radius of the circle | 10dp | 51 | | stpi_circleStrokeWidth | width of circle's radius | 4dp | 52 | | stpi_indicatorColor | color for the current page indicator | #00b47c (green) | 53 | | stpi_indicatorRadius | radius for the circle of the current page indicator | 4dp | 54 | | stpi_lineColor | color of the line between indicators | #b3bdc2 (grey) | 55 | | stpi_lineDoneColor | color of a line when step is done | #00b47c (green) | 56 | | stpi_lineStrokeWidth | width of the line stroke | 2dp | 57 | | stpi_lineMargin | margin at each side of the line | 5dp | 58 | | stpi_showDoneIcon | show the done check icon or not | true | 59 | 60 | ```java 61 | indicator.setViewPager(pager); 62 | // or keep last page as "end page" 63 | indicator.setViewPager(pager, pager.getAdapter().getCount() - 1); // 64 | // or manual change 65 | indicator.setStepCount(3); 66 | indicator.setCurrentStep(2); 67 | ``` 68 | 69 | Licence 70 | ---------------- 71 | ``` 72 | The MIT License (MIT) 73 | 74 | Copyright (c) 2016 Yannick Badoual 75 | 76 | Permission is hereby granted, free of charge, to any person obtaining a copy 77 | of this software and associated documentation files (the "Software"), to deal 78 | in the Software without restriction, including without limitation the rights 79 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 80 | copies of the Software, and to permit persons to whom the Software is 81 | furnished to do so, subject to the following conditions: 82 | 83 | The above copyright notice and this permission notice shall be included in all 84 | copies or substantial portions of the Software. 85 | 86 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 87 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 88 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 89 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 90 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 91 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 92 | SOFTWARE. 93 | ``` 94 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | maven { 19 | url "https://maven.google.com" 20 | } 21 | } 22 | } 23 | 24 | task wrapper(type: Wrapper) { 25 | gradleVersion = '2.14' 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Maven stuff 2 | GROUP=com.github.badoualy 3 | VERSION_CODE=104 4 | VERSION_NAME=1.0.4 5 | 6 | POM_NAME=Stepper Indicator 7 | POM_ARTIFACT_ID=stepper-indicator 8 | POM_PACKAGING=aar 9 | POM_DESCRIPTION=Stepper indicator 10 | POM_URL=https://github.com/badoualy/stepper-indicator 11 | POM_SCM_URL=https://github.com/badoualy/stepper-indicator 12 | POM_SCM_CONNECTION=scm:git@github.com:badoualy/stepper-indicator.git 13 | POM_SCM_DEV_CONNECTION=scm:git@github.com:badoualy/stepper-indicator.git 14 | POM_LICENCE_NAME=The MIT License (MIT) 15 | POM_LICENCE_URL=https://opensource.org/licenses/MIT 16 | POM_LICENCE_DIST=repo 17 | POM_DEVELOPER_ID=badoualy 18 | POM_DEVELOPER_NAME=Yannick Badoual -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 26 15:22:41 IST 2017 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-3.3-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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | resourcePrefix 'stpi' 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | testCompile 'junit:junit:4.12' 27 | compile 'com.android.support:appcompat-v7:26.0.0' 28 | } 29 | 30 | apply from: 'gradle-mvn-push.gradle' 31 | 32 | repositories { 33 | mavenCentral() 34 | } -------------------------------------------------------------------------------- /library/gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | apply plugin: 'maven' 17 | apply plugin: 'signing' 18 | 19 | def isReleaseBuild() { 20 | return VERSION_NAME.contains("SNAPSHOT") == false 21 | } 22 | 23 | def getReleaseRepositoryUrl() { 24 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 25 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 26 | } 27 | 28 | def getSnapshotRepositoryUrl() { 29 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 30 | : "https://oss.sonatype.org/content/repositories/snapshots/" 31 | } 32 | 33 | def getRepositoryUsername() { 34 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 35 | } 36 | 37 | def getRepositoryPassword() { 38 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 39 | } 40 | 41 | afterEvaluate { project -> 42 | uploadArchives { 43 | repositories { 44 | mavenDeployer { 45 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 46 | 47 | pom.groupId = GROUP 48 | pom.artifactId = POM_ARTIFACT_ID 49 | pom.version = VERSION_NAME 50 | 51 | repository(url: getReleaseRepositoryUrl()) { 52 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 53 | } 54 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 55 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 56 | } 57 | 58 | pom.project { 59 | name POM_NAME 60 | packaging POM_PACKAGING 61 | description POM_DESCRIPTION 62 | url POM_URL 63 | 64 | scm { 65 | url POM_SCM_URL 66 | connection POM_SCM_CONNECTION 67 | developerConnection POM_SCM_DEV_CONNECTION 68 | } 69 | 70 | licenses { 71 | license { 72 | name POM_LICENCE_NAME 73 | url POM_LICENCE_URL 74 | distribution POM_LICENCE_DIST 75 | } 76 | } 77 | 78 | developers { 79 | developer { 80 | id POM_DEVELOPER_ID 81 | name POM_DEVELOPER_NAME 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | signing { 90 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 91 | sign configurations.archives 92 | } 93 | 94 | android.libraryVariants.all { variant -> 95 | def javadocTask = task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) { 96 | description "Generates Javadoc for $variant.name." 97 | source = variant.javaCompile.source 98 | ext.androidJar = project.files(android.getBootClasspath().join(File.pathSeparator)) 99 | classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar) 100 | exclude '**/BuildConfig.java' 101 | exclude '**/R.java' 102 | } 103 | 104 | javadocTask.dependsOn variant.javaCompile 105 | 106 | def jarJavadocTask = task("jar${variant.name.capitalize()}Javadoc", type: Jar) { 107 | description "Generate Javadoc Jar for $variant.name" 108 | classifier = 'javadoc' 109 | from javadocTask.destinationDir 110 | } 111 | 112 | jarJavadocTask.dependsOn javadocTask 113 | artifacts.add('archives', jarJavadocTask) 114 | 115 | def jarSourceTask = task("jar${variant.name.capitalize()}Sources", type: Jar) { 116 | description "Generates Java Sources for $variant.name." 117 | classifier = 'sources' 118 | from variant.javaCompile.source 119 | } 120 | 121 | jarSourceTask.dependsOn variant.javaCompile 122 | artifacts.add('archives', jarSourceTask) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /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 C:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/badoualy/stepperindicator/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator; 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /library/src/main/java/com/badoualy/stepperindicator/StepperIndicator.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator; 2 | 3 | import android.animation.AnimatorSet; 4 | import android.animation.ObjectAnimator; 5 | import android.annotation.TargetApi; 6 | import android.content.Context; 7 | import android.content.res.Resources; 8 | import android.content.res.TypedArray; 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.DashPathEffect; 12 | import android.graphics.Paint; 13 | import android.graphics.Path; 14 | import android.graphics.PathEffect; 15 | import android.graphics.Rect; 16 | import android.graphics.RectF; 17 | import android.graphics.drawable.Drawable; 18 | import android.os.Build; 19 | import android.os.Parcel; 20 | import android.os.Parcelable; 21 | import android.support.annotation.Nullable; 22 | import android.support.annotation.UiThread; 23 | import android.support.v4.content.ContextCompat; 24 | import android.support.v4.view.PagerAdapter; 25 | import android.support.v4.view.ViewPager; 26 | import android.text.Layout; 27 | import android.text.StaticLayout; 28 | import android.text.TextPaint; 29 | import android.util.AttributeSet; 30 | import android.util.Log; 31 | import android.util.TypedValue; 32 | import android.view.GestureDetector; 33 | import android.view.MotionEvent; 34 | import android.view.View; 35 | import android.view.animation.DecelerateInterpolator; 36 | 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | import java.util.Random; 40 | 41 | /** 42 | *

Step indicator that can be used with (or without) a {@link ViewPager} to display current progress through an 43 | * Onboarding or any process in multiple steps.

44 | *

The default main primary color if not specified in the XML attributes will use the theme primary color defined 45 | * via {@code colorPrimary} attribute.

46 | *

If this view is used on a device below API 11, animations will not be used.

47 | *

Usage of stepper custom attributes:

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 | * 106 | * 107 | * 108 | * 109 | * 110 | * 111 | * 112 | * 113 | * 114 | * 115 | * 116 | * 117 | * 118 | * 119 | * 120 | * 121 | * 122 | * 123 | * 124 | * 125 | * 126 | * 127 | * 128 | * 129 | * 130 | * 131 | * 132 | * 133 | * 134 | * 135 | * 136 | * 137 | * 138 | * 139 | * 140 | * 141 | * 142 | * 143 | * 144 | * 145 | * 146 | * 147 | * 149 | * 150 | * 151 | * 152 | * 153 | * 155 | * 156 | * 157 | * 158 | * 159 | * 160 | * 161 | * 162 | * 163 | * 164 | * 165 | * 166 | * 167 | * 168 | * 169 | * 170 | * 171 | * 172 | * 173 | * 174 | * 175 | * 176 | * 177 | * 178 | * 179 | * 180 | * 181 | * 182 | *
NameDescriptionDefault value
stpi_animDurationduration of the line tracing animation250 ms
stpi_stepCountnumber of pages/steps
stpi_circleColorcolor of the stroke circle#b3bdc2 (grey)
stpi_circleRadiusradius of the circle10dp
stpi_circleStrokeWidthwidth of circle's radius4dp
stpi_indicatorColorcolor for the current page indicator#00b47c (green)
stpi_indicatorRadiusradius for the circle of the current page indicator4dp
stpi_lineColorcolor of the line between indicators#b3bdc2 (grey)
stpi_lineDoneColorcolor of a line when step is done#00b47c (green)
stpi_lineStrokeWidthwidth of the line stroke2dp
stpi_lineMarginmargin at each side of the line5dp
stpi_showDoneIconshow the done check icon or nottrue
stpi_showStepNumberInsteaddisplay text number for each step instead of bulletsfalse
stpi_useBottomIndicatordisplay the indicator for the current step at the bottom instead of inside bulletfalse
stpi_useBottomIndicatorWithStepColorsuse the same color for the bottom indicator as the step colorfalse
stpi_bottomIndicatorHeightset the height for the bottom indicator component3dp
stpi_bottomIndicatorWidthset the width for the bottom indicator component50dp
stpi_bottomIndicatorMarginTopset the top margin for the bottom indicator component10dp
stpi_stepsCircleColorsuse multiple colors for each step (array of colors with the size at least the same size as the stpi_stepCount 148 | * value)
stpi_stepsIndicatorColorsuse multiple colors for each step indicator (array of colors with the size at least the same size as the 154 | * stpi_stepCount value)
stpi_labelssupply an array of strings to show labels for every step indicator
stpi_showLabelsShow labels for each step indicator. Useful for timelines or checkpoints.false
stpi_labelMarginTopTop margin for the labels2dp
stpi_labelSizeSize for the labels12sp
stpi_labelColorColor for the labelsandroid:textColorSecondary defined in your project
183 | *

184 | *

Updated by Ionut Negru on 08/08/16 to add the stepClickListener feature.

185 | *

Updated by Ionut Negru on 09/08/16 to add support for customizations like: multiple colors, step text number, 186 | * bottom indicator.

187 | *

Updated by Rakshak R.Hegde to add support for labels and it's customisations. Supports label too.

188 | */ 189 | @SuppressWarnings("unused") 190 | public class StepperIndicator extends View implements ViewPager.OnPageChangeListener { 191 | 192 | private static final String TAG = "StepperIndicator"; 193 | 194 | /** 195 | * Duration of the line drawing animation (ms) 196 | */ 197 | private static final int DEFAULT_ANIMATION_DURATION = 200; 198 | /** 199 | * Max multiplier of the radius when a step is being animated to the "done" state before going to it's normal radius 200 | */ 201 | private static final float EXPAND_MARK = 1.3f; 202 | 203 | private static final int STEP_INVALID = -1; 204 | 205 | /** 206 | * Paint used to draw circle 207 | */ 208 | private Paint circlePaint; 209 | /** 210 | * List of {@link Paint} objects used to draw the circle for each step. 211 | */ 212 | private List stepsCirclePaintList; 213 | 214 | /** 215 | * The radius for the circle which describes an step. 216 | *

217 | * This is either declared via XML or default is used. 218 | *

219 | */ 220 | private float circleRadius; 221 | 222 | /** 223 | *

224 | * Flag indicating if the steps should be displayed with an number instead of empty circles and current animated 225 | * with bullet. 226 | *

227 | */ 228 | private boolean showStepTextNumber; 229 | 230 | /** 231 | * Paint used to draw the number indicator for all steps. 232 | */ 233 | private Paint stepTextNumberPaint; 234 | 235 | /** 236 | * List of {@link Paint} objects used to draw the number indicator for each step. 237 | */ 238 | private List stepsTextNumberPaintList; 239 | 240 | /** 241 | * Paint used to draw the indicator circle for the current and cleared steps 242 | */ 243 | private Paint indicatorPaint; 244 | 245 | /** 246 | * List of {@link Paint} objects used by each step indicating the current and cleared steps. 247 | *

If this is set, it will override the default.

248 | */ 249 | private List stepsIndicatorPaintList; 250 | 251 | /** 252 | * Paint used to draw the line between steps - as default. 253 | */ 254 | private Paint linePaint; 255 | 256 | /** 257 | * Paint used to draw the line between steps when done. 258 | */ 259 | private Paint lineDonePaint; 260 | 261 | /** 262 | * Paint used to draw the line between steps when animated. 263 | */ 264 | private Paint lineDoneAnimatedPaint; 265 | 266 | /** 267 | * List of {@link Path} for each line between steps 268 | */ 269 | private List linePathList = new ArrayList<>(); 270 | 271 | /** 272 | * The progress of the animation. 273 | * DO NOT DELETE OR RENAME: Will be used by animations logic. 274 | */ 275 | @SuppressWarnings("unused") 276 | private float animProgress; 277 | /** 278 | * The radius for the animated indicator. 279 | * DO NOT DELETE OR RENAME: Will be used by animations logic. 280 | */ 281 | private float animIndicatorRadius; 282 | /** 283 | * The radius for the animated check mark. 284 | * DO NOT DELETE OR RENAME: Will be used by animations logic. 285 | */ 286 | private float animCheckRadius; 287 | 288 | /** 289 | * Flag indicating if the indicator for the current step should be displayed at the bottom. 290 | *

291 | * This is useful if you want to use text number indicator for the steps as the bullet indicator will be 292 | * disabled for that flow. 293 | *

294 | */ 295 | private boolean useBottomIndicator; 296 | /** 297 | * The top margin of the bottom indicator. 298 | */ 299 | private float bottomIndicatorMarginTop = 0; 300 | /** 301 | * The width of the bottom indicator. 302 | */ 303 | private float bottomIndicatorWidth = 0; 304 | /** 305 | * The height of the bottom indicator. 306 | */ 307 | private float bottomIndicatorHeight = 0; 308 | /** 309 | * Flag indicating if the bottom indicator should use the same colors as the steps. 310 | */ 311 | private boolean useBottomIndicatorWithStepColors; 312 | 313 | /** 314 | * "Constant" size of the lines between steps 315 | */ 316 | private float lineLength; 317 | 318 | // Values retrieved from xml (or default values) 319 | private float checkRadius; 320 | private float indicatorRadius; 321 | private float lineMargin; 322 | private int animDuration; 323 | 324 | /** 325 | * Custom step click listener which will notify any component which sets an listener of any events (touch events) 326 | * that happen regarding the steps widget. 327 | */ 328 | private List onStepClickListeners = new ArrayList<>(0); 329 | /** 330 | * Click areas for each of the steps supported by the StepperIndicator widget. 331 | */ 332 | private List stepsClickAreas; 333 | 334 | /** 335 | * The gesture detector at which all the touch events will be propagated to. 336 | */ 337 | private GestureDetector gestureDetector; 338 | private int stepCount; 339 | private int currentStep; 340 | private int previousStep; 341 | 342 | // X position of each step indicator's center 343 | private float[] indicators; 344 | // Utils to avoid object instantiation during onDraw 345 | private Rect stepAreaRect = new Rect(); 346 | private RectF stepAreaRectF = new RectF(); 347 | 348 | private ViewPager pager; 349 | private Drawable doneIcon; 350 | private boolean showDoneIcon; 351 | 352 | // If viewpager is attached, viewpager's page titles are used when {@code showLabels} equals true 353 | private TextPaint labelPaint; 354 | private CharSequence[] labels; 355 | private boolean showLabels; 356 | private float labelMarginTop; 357 | private float labelSize; 358 | private StaticLayout[] labelLayouts; 359 | private float maxLabelHeight; 360 | 361 | // Running animations 362 | private AnimatorSet animatorSet; 363 | private ObjectAnimator lineAnimator, indicatorAnimator, checkAnimator; 364 | 365 | /** 366 | * Custom gesture listener though which all the touch events are propagated. 367 | *

368 | * The whole purpose of this listener is to correctly detect which step was touched by the user and notify 369 | * the component which registered to receive event updates through 370 | * {@link #addOnStepClickListener(OnStepClickListener)} 371 | *

372 | */ 373 | private GestureDetector.OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { 374 | @Override 375 | public boolean onSingleTapConfirmed(MotionEvent e) { 376 | int clickedStep = STEP_INVALID; 377 | if (isOnStepClickListenerAvailable()) { 378 | for (int i = 0; i < stepsClickAreas.size(); i++) { 379 | if (stepsClickAreas.get(i).contains(e.getX(), e.getY())) { 380 | clickedStep = i; 381 | // Stop as we found the step which was clicked 382 | break; 383 | } 384 | } 385 | } 386 | 387 | // If the clicked step is valid and an listener was setup - send the event 388 | if (clickedStep != STEP_INVALID) { 389 | for (OnStepClickListener listener : onStepClickListeners) { 390 | listener.onStepClicked(clickedStep); 391 | } 392 | } 393 | 394 | return super.onSingleTapConfirmed(e); 395 | } 396 | }; 397 | 398 | public StepperIndicator(Context context) { 399 | this(context, null); 400 | } 401 | 402 | public StepperIndicator(Context context, AttributeSet attrs) { 403 | this(context, attrs, 0); 404 | } 405 | 406 | public StepperIndicator(Context context, AttributeSet attrs, int defStyleAttr) { 407 | super(context, attrs, defStyleAttr); 408 | init(context, attrs, defStyleAttr); 409 | } 410 | 411 | @SuppressWarnings("unused") 412 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 413 | public StepperIndicator(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 414 | super(context, attrs, defStyleAttr, defStyleRes); 415 | init(context, attrs, defStyleAttr); 416 | } 417 | 418 | public static int getPrimaryColor(final Context context) { 419 | int color = context.getResources().getIdentifier("colorPrimary", "attr", context.getPackageName()); 420 | if (color != 0) { 421 | // If using support library v7 primaryColor 422 | TypedValue t = new TypedValue(); 423 | context.getTheme().resolveAttribute(color, t, true); 424 | color = t.data; 425 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 426 | // If using native primaryColor (SDK >21) 427 | TypedArray t = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary}); 428 | color = t.getColor(0, ContextCompat.getColor(context, R.color.stpi_default_primary_color)); 429 | t.recycle(); 430 | } else { 431 | TypedArray t = context.obtainStyledAttributes(new int[]{R.attr.colorPrimary}); 432 | color = t.getColor(0, ContextCompat.getColor(context, R.color.stpi_default_primary_color)); 433 | t.recycle(); 434 | } 435 | 436 | return color; 437 | } 438 | 439 | public static int getTextColorSecondary(final Context context) { 440 | TypedArray t = context.obtainStyledAttributes(new int[]{android.R.attr.textColorSecondary}); 441 | int color = t.getColor(0, ContextCompat.getColor(context, R.color.stpi_default_text_color)); 442 | t.recycle(); 443 | return color; 444 | } 445 | 446 | private static PathEffect createPathEffect(float pathLength, float phase, float offset) { 447 | // Create a PathEffect to set on a Paint to only draw some part of the line 448 | return new DashPathEffect(new float[]{pathLength, pathLength}, Math.max(phase * pathLength, offset)); 449 | } 450 | 451 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 452 | final Resources resources = getResources(); 453 | 454 | // Default values 455 | int defaultPrimaryColor = getPrimaryColor(context); 456 | 457 | int defaultCircleColor = ContextCompat.getColor(context, R.color.stpi_default_circle_color); 458 | float defaultCircleRadius = resources.getDimension(R.dimen.stpi_default_circle_radius); 459 | float defaultCircleStrokeWidth = resources.getDimension(R.dimen.stpi_default_circle_stroke_width); 460 | 461 | //noinspection UnnecessaryLocalVariable 462 | int defaultIndicatorColor = defaultPrimaryColor; 463 | float defaultIndicatorRadius = resources.getDimension(R.dimen.stpi_default_indicator_radius); 464 | 465 | float defaultLineStrokeWidth = resources.getDimension(R.dimen.stpi_default_line_stroke_width); 466 | float defaultLineMargin = resources.getDimension(R.dimen.stpi_default_line_margin); 467 | int defaultLineColor = ContextCompat.getColor(context, R.color.stpi_default_line_color); 468 | //noinspection UnnecessaryLocalVariable 469 | int defaultLineDoneColor = defaultPrimaryColor; 470 | 471 | /* Customize the widget based on the properties set on XML, or use default if not provided */ 472 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StepperIndicator, defStyleAttr, 0); 473 | 474 | circlePaint = new Paint(); 475 | circlePaint.setStrokeWidth( 476 | a.getDimension(R.styleable.StepperIndicator_stpi_circleStrokeWidth, defaultCircleStrokeWidth)); 477 | circlePaint.setStyle(Paint.Style.STROKE); 478 | circlePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_circleColor, defaultCircleColor)); 479 | circlePaint.setAntiAlias(true); 480 | 481 | // Call this as early as possible as other properties are configured based on the number of steps 482 | setStepCount(a.getInteger(R.styleable.StepperIndicator_stpi_stepCount, 2)); 483 | 484 | final int stepsCircleColorsResId = a.getResourceId(R.styleable.StepperIndicator_stpi_stepsCircleColors, 0); 485 | if (stepsCircleColorsResId != 0) { 486 | stepsCirclePaintList = new ArrayList<>(stepCount); 487 | 488 | for (int i = 0; i < stepCount; i++) { 489 | // Based on the main indicator paint object, we create the customized one 490 | Paint circlePaint = new Paint(this.circlePaint); 491 | if (isInEditMode()) { 492 | // Fallback for edit mode - to show something in the preview 493 | circlePaint.setColor(getRandomColor()); 494 | } else { 495 | // Get the array of attributes for the colors 496 | TypedArray colorResValues = context.getResources().obtainTypedArray(stepsCircleColorsResId); 497 | 498 | if (stepCount > colorResValues.length()) { 499 | throw new IllegalArgumentException( 500 | "Invalid number of colors for the circles. Please provide a list " + 501 | "of colors with as many items as the number of steps required!"); 502 | } 503 | 504 | circlePaint.setColor(colorResValues.getColor(i, 0)); // specific color 505 | // No need for the array anymore, recycle it 506 | colorResValues.recycle(); 507 | } 508 | 509 | stepsCirclePaintList.add(circlePaint); 510 | } 511 | } 512 | 513 | indicatorPaint = new Paint(circlePaint); 514 | indicatorPaint.setStyle(Paint.Style.FILL); 515 | indicatorPaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_indicatorColor, defaultIndicatorColor)); 516 | indicatorPaint.setAntiAlias(true); 517 | 518 | stepTextNumberPaint = new Paint(indicatorPaint); 519 | stepTextNumberPaint.setTextSize(getResources().getDimension(R.dimen.stpi_default_text_size)); 520 | 521 | showStepTextNumber = a.getBoolean(R.styleable.StepperIndicator_stpi_showStepNumberInstead, false); 522 | 523 | // Get the resource from the context style properties 524 | final int stepsIndicatorColorsResId = a 525 | .getResourceId(R.styleable.StepperIndicator_stpi_stepsIndicatorColors, 0); 526 | if (stepsIndicatorColorsResId != 0) { 527 | // init the list of colors with the same size as the number of steps 528 | stepsIndicatorPaintList = new ArrayList<>(stepCount); 529 | if (showStepTextNumber) { 530 | stepsTextNumberPaintList = new ArrayList<>(stepCount); 531 | } 532 | 533 | for (int i = 0; i < stepCount; i++) { 534 | Paint indicatorPaint = new Paint(this.indicatorPaint); 535 | 536 | Paint textNumberPaint = showStepTextNumber ? new Paint(stepTextNumberPaint) : null; 537 | if (isInEditMode()) { 538 | // Fallback for edit mode - to show something in the preview 539 | 540 | indicatorPaint.setColor(getRandomColor()); // random color 541 | if (null != textNumberPaint) { 542 | textNumberPaint.setColor(indicatorPaint.getColor()); 543 | } 544 | } else { 545 | // Get the array of attributes for the colors 546 | TypedArray colorResValues = context.getResources().obtainTypedArray(stepsIndicatorColorsResId); 547 | 548 | if (stepCount > colorResValues.length()) { 549 | throw new IllegalArgumentException( 550 | "Invalid number of colors for the indicators. Please provide a list " + 551 | "of colors with as many items as the number of steps required!"); 552 | } 553 | 554 | indicatorPaint.setColor(colorResValues.getColor(i, 0)); // specific color 555 | if (null != textNumberPaint) { 556 | textNumberPaint.setColor(indicatorPaint.getColor()); 557 | } 558 | // No need for the array anymore, recycle it 559 | colorResValues.recycle(); 560 | } 561 | 562 | stepsIndicatorPaintList.add(indicatorPaint); 563 | if (showStepTextNumber && null != textNumberPaint) { 564 | stepsTextNumberPaintList.add(textNumberPaint); 565 | } 566 | } 567 | } 568 | 569 | linePaint = new Paint(); 570 | linePaint.setStrokeWidth( 571 | a.getDimension(R.styleable.StepperIndicator_stpi_lineStrokeWidth, defaultLineStrokeWidth)); 572 | linePaint.setStrokeCap(Paint.Cap.ROUND); 573 | linePaint.setStyle(Paint.Style.STROKE); 574 | linePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_lineColor, defaultLineColor)); 575 | linePaint.setAntiAlias(true); 576 | 577 | lineDonePaint = new Paint(linePaint); 578 | lineDonePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_lineDoneColor, defaultLineDoneColor)); 579 | 580 | lineDoneAnimatedPaint = new Paint(lineDonePaint); 581 | 582 | // Check if we should use the bottom indicator instead of the bullet one 583 | useBottomIndicator = a.getBoolean(R.styleable.StepperIndicator_stpi_useBottomIndicator, false); 584 | if (useBottomIndicator) { 585 | // Get the default height(stroke width) for the bottom indicator 586 | float defaultHeight = resources.getDimension(R.dimen.stpi_default_bottom_indicator_height); 587 | 588 | bottomIndicatorHeight = a 589 | .getDimension(R.styleable.StepperIndicator_stpi_bottomIndicatorHeight, defaultHeight); 590 | 591 | if (bottomIndicatorHeight <= 0) { 592 | Log.d(TAG, "init: Invalid indicator height, disabling bottom indicator feature! Please provide " + 593 | "a value greater than 0."); 594 | useBottomIndicator = false; 595 | } 596 | 597 | // Get the default width for the bottom indicator 598 | float defaultWidth = resources.getDimension(R.dimen.stpi_default_bottom_indicator_width); 599 | bottomIndicatorWidth = a.getDimension(R.styleable.StepperIndicator_stpi_bottomIndicatorWidth, defaultWidth); 600 | 601 | // Get the default top margin for the bottom indicator 602 | float defaultTopMargin = resources.getDimension(R.dimen.stpi_default_bottom_indicator_margin_top); 603 | bottomIndicatorMarginTop = a 604 | .getDimension(R.styleable.StepperIndicator_stpi_bottomIndicatorMarginTop, defaultTopMargin); 605 | 606 | useBottomIndicatorWithStepColors = a 607 | .getBoolean(R.styleable.StepperIndicator_stpi_useBottomIndicatorWithStepColors, false); 608 | } 609 | 610 | circleRadius = a.getDimension(R.styleable.StepperIndicator_stpi_circleRadius, defaultCircleRadius); 611 | checkRadius = circleRadius + circlePaint.getStrokeWidth() / 2f; 612 | indicatorRadius = a.getDimension(R.styleable.StepperIndicator_stpi_indicatorRadius, defaultIndicatorRadius); 613 | animIndicatorRadius = indicatorRadius; 614 | animCheckRadius = checkRadius; 615 | lineMargin = a.getDimension(R.styleable.StepperIndicator_stpi_lineMargin, defaultLineMargin); 616 | 617 | animDuration = a.getInteger(R.styleable.StepperIndicator_stpi_animDuration, DEFAULT_ANIMATION_DURATION); 618 | showDoneIcon = a.getBoolean(R.styleable.StepperIndicator_stpi_showDoneIcon, true); 619 | doneIcon = a.getDrawable(R.styleable.StepperIndicator_stpi_doneIconDrawable); 620 | 621 | // Labels Configuration 622 | labelPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 623 | labelPaint.setTextAlign(Paint.Align.CENTER); 624 | 625 | float defaultLabelSize = resources.getDimension(R.dimen.stpi_default_label_size); 626 | labelSize = a.getDimension(R.styleable.StepperIndicator_stpi_labelSize, defaultLabelSize); 627 | labelPaint.setTextSize(labelSize); 628 | 629 | float defaultLabelMarginTop = resources.getDimension(R.dimen.stpi_default_label_margin_top); 630 | labelMarginTop = a.getDimension(R.styleable.StepperIndicator_stpi_labelMarginTop, defaultLabelMarginTop); 631 | 632 | showLabels(a.getBoolean(R.styleable.StepperIndicator_stpi_showLabels, false)); 633 | setLabels(a.getTextArray(R.styleable.StepperIndicator_stpi_labels)); 634 | 635 | if (a.hasValue(R.styleable.StepperIndicator_stpi_labelColor)) { 636 | setLabelColor(a.getColor(R.styleable.StepperIndicator_stpi_labelColor, 0)); 637 | } else { 638 | setLabelColor(getTextColorSecondary(getContext())); 639 | } 640 | 641 | if (isInEditMode() && showLabels && labels == null) { 642 | labels = new CharSequence[]{"First", "Second", "Third", "Fourth", "Fifth"}; 643 | } 644 | 645 | if (!a.hasValue(R.styleable.StepperIndicator_stpi_stepCount) && labels != null) { 646 | setStepCount(labels.length); 647 | } 648 | 649 | a.recycle(); 650 | 651 | if (showDoneIcon && doneIcon == null) { 652 | doneIcon = ContextCompat.getDrawable(context, R.drawable.ic_done_white_18dp); 653 | } 654 | if (doneIcon != null) { 655 | int size = getContext().getResources().getDimensionPixelSize(R.dimen.stpi_done_icon_size); 656 | doneIcon.setBounds(0, 0, size, size); 657 | } 658 | 659 | // Display at least 1 cleared step for preview in XML editor 660 | if (isInEditMode()) { 661 | currentStep = Math.max((int) Math.ceil(stepCount / 2f), 1); 662 | } 663 | 664 | // Initialize the gesture detector, setup with our custom gesture listener 665 | gestureDetector = new GestureDetector(getContext(), gestureListener); 666 | } 667 | 668 | /** 669 | * Get an random color {@link Paint} object. 670 | * 671 | * @return {@link Paint} object with the same attributes as {@link #circlePaint} and with an random color. 672 | * @see #circlePaint 673 | * @see #getRandomColor() 674 | */ 675 | private Paint getRandomPaint() { 676 | Paint paint = new Paint(indicatorPaint); 677 | paint.setColor(getRandomColor()); 678 | 679 | return paint; 680 | } 681 | 682 | /** 683 | * Get an random color value. 684 | * 685 | * @return The color value as AARRGGBB 686 | */ 687 | private int getRandomColor() { 688 | Random rnd = new Random(); 689 | return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)); 690 | } 691 | 692 | @Override 693 | public boolean onTouchEvent(MotionEvent event) { 694 | // Dispatch the touch events to our custom gesture detector. 695 | gestureDetector.onTouchEvent(event); 696 | return true; // we handle the event in the gesture detector 697 | } 698 | 699 | @Override 700 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 701 | compute(); // for setting up the indicator based on the new position 702 | } 703 | 704 | /** 705 | * Make calculations for establishing the exact positions of each step component, for the line dividers, for 706 | * bottom indicators, etc. 707 | *

708 | * Call this whenever there is an layout change for the widget. 709 | *

710 | */ 711 | private void compute() { 712 | if (null == circlePaint) { 713 | throw new IllegalArgumentException("circlePaint is invalid! Make sure you setup the field circlePaint " + 714 | "before calling compute() method!"); 715 | } 716 | 717 | indicators = new float[stepCount]; 718 | linePathList.clear(); 719 | 720 | float startX = circleRadius * EXPAND_MARK + circlePaint.getStrokeWidth() / 2f; 721 | if (useBottomIndicator) { 722 | startX = bottomIndicatorWidth / 2F; 723 | } 724 | if (showLabels) { 725 | // gridWidth is the width of the grid assigned for the step indicator 726 | int gridWidth = getMeasuredWidth() / stepCount; 727 | startX = gridWidth / 2F; 728 | } 729 | 730 | // Compute position of indicators and line length 731 | float divider = (getMeasuredWidth() - startX * 2f) / (stepCount - 1); 732 | lineLength = divider - (circleRadius * 2f + circlePaint.getStrokeWidth()) - (lineMargin * 2); 733 | 734 | // Compute position of circles and lines once 735 | for (int i = 0; i < indicators.length; i++) { 736 | indicators[i] = startX + divider * i; 737 | } 738 | for (int i = 0; i < indicators.length - 1; i++) { 739 | float position = ((indicators[i] + indicators[i + 1]) / 2) - lineLength / 2; 740 | final Path linePath = new Path(); 741 | float lineY = getStepCenterY(); 742 | linePath.moveTo(position, lineY); 743 | linePath.lineTo(position + lineLength, lineY); 744 | linePathList.add(linePath); 745 | } 746 | 747 | computeStepsClickAreas(); // update the position of the steps click area also 748 | } 749 | 750 | /** 751 | *

752 | * Calculate the area for each step. This ensure the correct step is detected when an click event is detected. 753 | *

754 | *

755 | * Whenever {@link #compute()} method is called, make sure to call this method also so that the steps click 756 | * area is updated. 757 | *

758 | */ 759 | public void computeStepsClickAreas() { 760 | if (stepCount == STEP_INVALID) { 761 | throw new IllegalArgumentException("stepCount wasn't setup yet. Make sure you call setStepCount() " + 762 | "before computing the steps click area!"); 763 | } 764 | 765 | if (null == indicators) { 766 | throw new IllegalArgumentException("indicators wasn't setup yet. Make sure the indicators are " + 767 | "initialized and setup correctly before trying to compute the click " + 768 | "area for each step!"); 769 | } 770 | 771 | // Initialize the list for the steps click area 772 | stepsClickAreas = new ArrayList<>(stepCount); 773 | 774 | // Compute the clicked area for each step 775 | for (float indicator : indicators) { 776 | // Get the indicator position 777 | // Calculate the bounds for the step 778 | float left = indicator - circleRadius * 2; 779 | float right = indicator + circleRadius * 2; 780 | float top = getStepCenterY() - circleRadius * 2; 781 | float bottom = getStepCenterY() + circleRadius + getBottomIndicatorHeight(); 782 | 783 | // Store the click area for the step 784 | RectF area = new RectF(left, top, right, bottom); 785 | stepsClickAreas.add(area); 786 | } 787 | } 788 | 789 | /** 790 | * Get the height of the bottom indicator. 791 | *

792 | * The height will include the height necessary for correctly drawing the bottom indicator plus the margin 793 | * set in XML (or the default one). 794 | *

795 | *

796 | * If the widget isn't set to display the bottom indicator this will method will always return {@code 0} 797 | *

798 | * 799 | * @return The height of the bottom indicator in pixels or {@code 0}. 800 | */ 801 | private int getBottomIndicatorHeight() { 802 | if (useBottomIndicator) { 803 | return (int) (bottomIndicatorHeight + bottomIndicatorMarginTop); 804 | } else { 805 | return 0; 806 | } 807 | } 808 | 809 | private float getMaxLabelHeight() { 810 | return showLabels ? maxLabelHeight + labelMarginTop : 0; 811 | } 812 | 813 | private void calculateMaxLabelHeight(final int measuredWidth) { 814 | if (!showLabels) return; 815 | 816 | // gridWidth is the width of the grid assigned for the step indicator 817 | int twoDp = getContext().getResources().getDimensionPixelSize(R.dimen.stpi_two_dp); 818 | int gridWidth = measuredWidth / stepCount - twoDp; 819 | 820 | if (gridWidth <= 0) return; 821 | 822 | // Compute StaticLayout for the labels 823 | labelLayouts = new StaticLayout[labels.length]; 824 | maxLabelHeight = 0F; 825 | float labelSingleLineHeight = labelPaint.descent() - labelPaint.ascent(); 826 | for (int i = 0; i < labels.length; i++) { 827 | if (labels[i] == null) continue; 828 | 829 | labelLayouts[i] = new StaticLayout(labels[i], labelPaint, gridWidth, 830 | Layout.Alignment.ALIGN_NORMAL, 1, 0, false); 831 | maxLabelHeight = Math.max(maxLabelHeight, labelLayouts[i].getLineCount() * labelSingleLineHeight); 832 | } 833 | } 834 | 835 | private float getStepCenterY() { 836 | return (getMeasuredHeight() - getBottomIndicatorHeight() - getMaxLabelHeight()) / 2f; 837 | } 838 | 839 | @SuppressWarnings("ConstantConditions") 840 | @Override 841 | protected void onDraw(Canvas canvas) { 842 | float centerY = getStepCenterY(); 843 | 844 | // Currently Drawing animation from step n-1 to n, or back from n+1 to n 845 | boolean inAnimation = animatorSet != null && animatorSet.isRunning(); 846 | boolean inLineAnimation = lineAnimator != null && lineAnimator.isRunning(); 847 | boolean inIndicatorAnimation = indicatorAnimator != null && indicatorAnimator.isRunning(); 848 | boolean inCheckAnimation = checkAnimator != null && checkAnimator.isRunning(); 849 | 850 | boolean drawToNext = previousStep == currentStep - 1; 851 | boolean drawFromNext = previousStep == currentStep + 1; 852 | 853 | for (int i = 0; i < indicators.length; i++) { 854 | final float indicator = indicators[i]; 855 | 856 | // We draw the "done" check if previous step, or if we are going back (if going back, animated value will reduce radius to 0) 857 | boolean drawCheck = i < currentStep || (drawFromNext && i == currentStep); 858 | 859 | // Draw back circle 860 | canvas.drawCircle(indicator, centerY, circleRadius, getStepCirclePaint(i)); 861 | 862 | // Draw the step number inside the back circle if the flag for this is set to true 863 | if (showStepTextNumber) { 864 | final String stepLabel = String.valueOf(i + 1); 865 | 866 | stepAreaRect.set((int) (indicator - circleRadius), (int) (centerY - circleRadius), 867 | (int) (indicator + circleRadius), (int) (centerY + circleRadius)); 868 | stepAreaRectF.set(stepAreaRect); 869 | 870 | Paint stepTextNumberPaint = getStepTextNumberPaint(i); 871 | 872 | // measure text width 873 | stepAreaRectF.right = stepTextNumberPaint.measureText(stepLabel, 0, stepLabel.length()); 874 | // measure text height 875 | stepAreaRectF.bottom = stepTextNumberPaint.descent() - stepTextNumberPaint.ascent(); 876 | 877 | stepAreaRectF.left += (stepAreaRect.width() - stepAreaRectF.right) / 2.0f; 878 | stepAreaRectF.top += (stepAreaRect.height() - stepAreaRectF.bottom) / 2.0f; 879 | 880 | canvas.drawText(stepLabel, stepAreaRectF.left, stepAreaRectF.top - stepTextNumberPaint.ascent(), 881 | stepTextNumberPaint); 882 | } 883 | 884 | if (showLabels && labelLayouts != null && 885 | i < labelLayouts.length && labelLayouts[i] != null) { 886 | drawLayout(labelLayouts[i], 887 | indicator, getHeight() - getBottomIndicatorHeight() - maxLabelHeight, 888 | canvas, labelPaint); 889 | } 890 | 891 | if (useBottomIndicator) { 892 | // Show the current step indicator as bottom line 893 | if (i == currentStep) { 894 | // Draw custom indicator for current step only 895 | canvas.drawRect(indicator - bottomIndicatorWidth / 2, getHeight() - bottomIndicatorHeight, 896 | indicator + bottomIndicatorWidth / 2, getHeight(), 897 | useBottomIndicatorWithStepColors ? getStepIndicatorPaint(i) : indicatorPaint); 898 | } 899 | } else { 900 | // Show the current step indicator as bullet 901 | // If current step, or coming back from next step and still animating 902 | if ((i == currentStep && !drawFromNext) || (i == previousStep && drawFromNext && inAnimation)) { 903 | // Draw animated indicator 904 | canvas.drawCircle(indicator, centerY, animIndicatorRadius, getStepIndicatorPaint(i)); 905 | } 906 | } 907 | 908 | // Draw check mark 909 | if (drawCheck) { 910 | float radius = checkRadius; 911 | // Use animated radius value? 912 | if ((i == previousStep && drawToNext) || (i == currentStep && drawFromNext)) radius = animCheckRadius; 913 | canvas.drawCircle(indicator, centerY, radius, getStepIndicatorPaint(i)); 914 | 915 | // Draw check bitmap 916 | if (!isInEditMode() && showDoneIcon) { 917 | if ((i != previousStep && i != currentStep) || 918 | (!inCheckAnimation && !(i == currentStep && !inAnimation))) { 919 | canvas.save(); 920 | canvas.translate(indicator - (doneIcon.getIntrinsicWidth() / 2), 921 | centerY - (doneIcon.getIntrinsicHeight() / 2)); 922 | doneIcon.draw(canvas); 923 | canvas.restore(); 924 | } 925 | } 926 | } 927 | 928 | // Draw lines 929 | if (i < linePathList.size()) { 930 | if (i >= currentStep) { 931 | canvas.drawPath(linePathList.get(i), linePaint); 932 | if (i == currentStep && drawFromNext && (inLineAnimation || inIndicatorAnimation)) { 933 | // Coming back from n+1 934 | canvas.drawPath(linePathList.get(i), lineDoneAnimatedPaint); 935 | } 936 | } else { 937 | if (i == currentStep - 1 && drawToNext && inLineAnimation) { 938 | // Going to n+1 939 | canvas.drawPath(linePathList.get(i), linePaint); 940 | canvas.drawPath(linePathList.get(i), lineDoneAnimatedPaint); 941 | } else { 942 | canvas.drawPath(linePathList.get(i), lineDonePaint); 943 | } 944 | } 945 | } 946 | } 947 | } 948 | 949 | /** 950 | * x and y anchored to top-middle point of StaticLayout 951 | */ 952 | public static void drawLayout(Layout layout, float x, float y, 953 | Canvas canvas, TextPaint paint) { 954 | canvas.save(); 955 | canvas.translate(x, y); 956 | layout.draw(canvas); 957 | canvas.restore(); 958 | } 959 | 960 | /** 961 | * Get the {@link Paint} object which should be used for displaying the current step indicator. 962 | * 963 | * @param stepPosition The step position for which to retrieve the {@link Paint} object 964 | * @return The {@link Paint} object for the specified step position 965 | */ 966 | private Paint getStepIndicatorPaint(final int stepPosition) { 967 | return getPaint(stepPosition, stepsIndicatorPaintList, indicatorPaint); 968 | } 969 | 970 | /** 971 | * Get the {@link Paint} object which should be used for drawing the text number the current step. 972 | * 973 | * @param stepPosition The step position for which to retrieve the {@link Paint} object 974 | * @return The {@link Paint} object for the specified step position 975 | */ 976 | private Paint getStepTextNumberPaint(final int stepPosition) { 977 | return getPaint(stepPosition, stepsTextNumberPaintList, stepTextNumberPaint); 978 | } 979 | 980 | /** 981 | * Get the {@link Paint} object which should be used for drawing the circle for the step. 982 | * 983 | * @param stepPosition The step position for which to retrieve the {@link Paint} object 984 | * @return The {@link Paint} object for the specified step position 985 | */ 986 | private Paint getStepCirclePaint(final int stepPosition) { 987 | return getPaint(stepPosition, stepsCirclePaintList, circlePaint); 988 | } 989 | 990 | /** 991 | * Get the {@link Paint} object based on the step position and the source list of {@link Paint} objects. 992 | *

993 | * If none found, will try to use the provided default. If not valid also, an random {@link Paint} object 994 | * will be returned instead. 995 | *

996 | * 997 | * @param stepPosition The step position for which the {@link Paint} object is needed 998 | * @param sourceList The source list of {@link Paint} object. 999 | * @param defaultPaint The default {@link Paint} object which will be returned if the source list does not 1000 | * contain the specified step. 1001 | * @return {@link Paint} object for the specified step position. 1002 | */ 1003 | private Paint getPaint(final int stepPosition, final List sourceList, final Paint defaultPaint) { 1004 | isStepValid(stepPosition); // it will throw an error if not valid 1005 | 1006 | Paint paint = null; 1007 | if (null != sourceList && !sourceList.isEmpty()) { 1008 | try { 1009 | paint = sourceList.get(stepPosition); 1010 | } catch (IndexOutOfBoundsException e) { 1011 | // We use an random color as this usually should not happen, maybe in edit mode 1012 | Log.d(TAG, "getPaint: could not find the specific step paint to use! Try to use default instead!"); 1013 | } 1014 | } 1015 | 1016 | if (null == paint && null != defaultPaint) { 1017 | // Try to use the default 1018 | paint = defaultPaint; 1019 | } 1020 | 1021 | if (null == paint) { 1022 | Log.d(TAG, "getPaint: could not use default paint for the specific step! Using random Paint instead!"); 1023 | // If we reached this point, not even the default is setup, rely on some random color 1024 | paint = getRandomPaint(); 1025 | } 1026 | 1027 | return paint; 1028 | } 1029 | 1030 | /** 1031 | * Check if the step position provided is an valid and supported step. 1032 | *

1033 | * This method ensured the widget doesn't try to use invalid steps. It will throw an exception whenever an 1034 | * invalid step is detected. Catch the exception if it is expected or it doesn't affect the flow. 1035 | *

1036 | * 1037 | * @param stepPos The step position to verify 1038 | * @return {@code true} if the step is valid, otherwise it will throw an exception. 1039 | */ 1040 | private boolean isStepValid(final int stepPos) { 1041 | if (stepPos < 0 || stepPos > stepCount - 1) { 1042 | throw new IllegalArgumentException("Invalid step position. " + stepPos + " is not a valid position! it " + 1043 | "should be between 0 and stepCount(" + stepCount + ")"); 1044 | } 1045 | 1046 | return true; 1047 | } 1048 | 1049 | @Override 1050 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1051 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 1052 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 1053 | 1054 | int width = widthMode == MeasureSpec.EXACTLY ? widthSize : getSuggestedMinimumWidth(); 1055 | 1056 | calculateMaxLabelHeight(width); 1057 | 1058 | // Compute the necessary height for the widget 1059 | int desiredHeight = (int) Math.ceil( 1060 | (circleRadius * EXPAND_MARK * 2) + 1061 | circlePaint.getStrokeWidth() + 1062 | getBottomIndicatorHeight() + 1063 | getMaxLabelHeight() 1064 | ); 1065 | 1066 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 1067 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 1068 | int height = heightMode == MeasureSpec.EXACTLY ? heightSize : desiredHeight; 1069 | 1070 | setMeasuredDimension(width, height); 1071 | } 1072 | 1073 | @SuppressWarnings("unused") 1074 | public int getStepCount() { 1075 | return stepCount; 1076 | } 1077 | 1078 | public void setStepCount(int stepCount) { 1079 | if (stepCount < 2) { 1080 | throw new IllegalArgumentException("stepCount must be >= 2"); 1081 | } 1082 | 1083 | this.stepCount = stepCount; 1084 | currentStep = 0; 1085 | compute(); 1086 | invalidate(); 1087 | } 1088 | 1089 | @SuppressWarnings("unused") 1090 | public int getCurrentStep() { 1091 | return currentStep; 1092 | } 1093 | 1094 | /** 1095 | * Sets the current step 1096 | * 1097 | * @param currentStep a value between 0 (inclusive) and stepCount (inclusive) 1098 | */ 1099 | @UiThread 1100 | public void setCurrentStep(int currentStep) { 1101 | if (currentStep < 0 || currentStep > stepCount) { 1102 | throw new IllegalArgumentException("Invalid step value " + currentStep); 1103 | } 1104 | 1105 | previousStep = this.currentStep; 1106 | this.currentStep = currentStep; 1107 | 1108 | // Cancel any running animations 1109 | if (animatorSet != null) { 1110 | animatorSet.cancel(); 1111 | } 1112 | 1113 | animatorSet = null; 1114 | lineAnimator = null; 1115 | indicatorAnimator = null; 1116 | 1117 | // TODO: 05/08/16 handle cases where steps are skipped - need to animate all of them 1118 | 1119 | if (currentStep == previousStep + 1) { 1120 | // Going to next step 1121 | animatorSet = new AnimatorSet(); 1122 | 1123 | // First, draw line to new 1124 | lineAnimator = ObjectAnimator.ofFloat(StepperIndicator.this, "animProgress", 1.0f, 0.0f); 1125 | 1126 | // Same time, pop check mark 1127 | checkAnimator = ObjectAnimator.ofFloat(StepperIndicator.this, "animCheckRadius", indicatorRadius, 1128 | checkRadius * EXPAND_MARK, checkRadius); 1129 | 1130 | // Finally, pop current step indicator 1131 | animIndicatorRadius = 0; 1132 | indicatorAnimator = ObjectAnimator.ofFloat(StepperIndicator.this, "animIndicatorRadius", 0f, 1133 | indicatorRadius * 1.4f, indicatorRadius); 1134 | 1135 | animatorSet.play(lineAnimator).with(checkAnimator).before(indicatorAnimator); 1136 | } else if (currentStep == previousStep - 1) { 1137 | // Going back to previous step 1138 | animatorSet = new AnimatorSet(); 1139 | 1140 | // First, pop out current step indicator 1141 | indicatorAnimator = ObjectAnimator 1142 | .ofFloat(StepperIndicator.this, "animIndicatorRadius", indicatorRadius, 0f); 1143 | 1144 | // Then delete line 1145 | animProgress = 1.0f; 1146 | lineDoneAnimatedPaint.setPathEffect(null); 1147 | lineAnimator = ObjectAnimator.ofFloat(StepperIndicator.this, "animProgress", 0.0f, 1.0f); 1148 | 1149 | // Finally, pop out check mark to display step indicator 1150 | animCheckRadius = checkRadius; 1151 | checkAnimator = ObjectAnimator 1152 | .ofFloat(StepperIndicator.this, "animCheckRadius", checkRadius, indicatorRadius); 1153 | 1154 | animatorSet.playSequentially(indicatorAnimator, lineAnimator, checkAnimator); 1155 | } 1156 | 1157 | if (animatorSet != null) { 1158 | // Max 500 ms for the animation 1159 | lineAnimator.setDuration(Math.min(500, animDuration)); 1160 | lineAnimator.setInterpolator(new DecelerateInterpolator()); 1161 | // Other animations will run 2 times faster that line animation 1162 | indicatorAnimator.setDuration(lineAnimator.getDuration() / 2); 1163 | checkAnimator.setDuration(lineAnimator.getDuration() / 2); 1164 | 1165 | animatorSet.start(); 1166 | } 1167 | 1168 | invalidate(); 1169 | } 1170 | 1171 | /** 1172 | *

1173 | * Setter method for the animation progress. 1174 | *

1175 | * DO NOT CALL, DELETE OR RENAME: Will be used by animation. 1176 | */ 1177 | @SuppressWarnings("unused") 1178 | public void setAnimProgress(float animProgress) { 1179 | this.animProgress = animProgress; 1180 | lineDoneAnimatedPaint.setPathEffect(createPathEffect(lineLength, animProgress, 0.0f)); 1181 | invalidate(); 1182 | } 1183 | 1184 | /** 1185 | *

1186 | * Setter method for the indicator radius animation. 1187 | *

1188 | * DO NOT CALL, DELETE OR RENAME: Will be used by animation. 1189 | */ 1190 | @SuppressWarnings("unused") 1191 | public void setAnimIndicatorRadius(float animIndicatorRadius) { 1192 | this.animIndicatorRadius = animIndicatorRadius; 1193 | invalidate(); 1194 | } 1195 | 1196 | /** 1197 | *

1198 | * Setter method for the checkmark radius animation. 1199 | *

1200 | * DO NOT CALL, DELETE OR RENAME: Will be used by animation. 1201 | */ 1202 | @SuppressWarnings("unused") 1203 | public void setAnimCheckRadius(float animCheckRadius) { 1204 | this.animCheckRadius = animCheckRadius; 1205 | invalidate(); 1206 | } 1207 | 1208 | /** 1209 | * Set the {@link ViewPager} associated with this widget indicator. 1210 | * 1211 | * @param pager {@link ViewPager} to attach 1212 | */ 1213 | @SuppressWarnings("unused") 1214 | public void setViewPager(ViewPager pager) { 1215 | if (pager.getAdapter() == null) { 1216 | throw new IllegalStateException("ViewPager does not have adapter instance."); 1217 | } 1218 | setViewPager(pager, pager.getAdapter().getCount()); 1219 | } 1220 | 1221 | /** 1222 | * Set the {@link ViewPager} associated with this widget indicator. 1223 | * 1224 | * @param pager {@link ViewPager} to attach 1225 | * @param keepLastPage {@code true} if the widget should not create an indicator for the last page, to use it as 1226 | * the 1227 | * final page 1228 | */ 1229 | public void setViewPager(ViewPager pager, boolean keepLastPage) { 1230 | if (pager.getAdapter() == null) { 1231 | throw new IllegalStateException("ViewPager does not have adapter instance."); 1232 | } 1233 | setViewPager(pager, pager.getAdapter().getCount() - (keepLastPage ? 1 : 0)); 1234 | } 1235 | 1236 | /** 1237 | * Set the {@link ViewPager} associated with this widget indicator. 1238 | * 1239 | * @param pager {@link ViewPager} to attach 1240 | * @param stepCount The real page count to display (use this if you are using looped viewpager to indicate the real 1241 | * number 1242 | * of pages) 1243 | */ 1244 | public void setViewPager(ViewPager pager, int stepCount) { 1245 | if (this.pager == pager) { 1246 | return; 1247 | } 1248 | if (this.pager != null) { 1249 | pager.removeOnPageChangeListener(this); 1250 | } 1251 | if (pager.getAdapter() == null) { 1252 | throw new IllegalStateException("ViewPager does not have adapter instance."); 1253 | } 1254 | 1255 | this.pager = pager; 1256 | this.stepCount = stepCount; 1257 | currentStep = 0; 1258 | pager.addOnPageChangeListener(this); 1259 | 1260 | if (showLabels && labels == null) { 1261 | setLabelsUsingPageTitles(); 1262 | } 1263 | 1264 | requestLayout(); 1265 | invalidate(); 1266 | } 1267 | 1268 | private void setLabelsUsingPageTitles() { 1269 | PagerAdapter pagerAdapter = pager.getAdapter(); 1270 | int pagerCount = pagerAdapter.getCount(); 1271 | labels = new CharSequence[pagerCount]; 1272 | for (int i = 0; i < pagerCount; i++) { 1273 | labels[i] = pagerAdapter.getPageTitle(i); 1274 | } 1275 | } 1276 | 1277 | /** 1278 | * Pass a labels array of Charsequence that is greater than or equal to the {@code stepCount}. 1279 | * Never pass {@code null} to this manually. Call {@code showLabels(false)} to hide labels. 1280 | * 1281 | * @param labelsArray Non-null array of CharSequence 1282 | */ 1283 | public void setLabels(CharSequence[] labelsArray) { 1284 | if (labelsArray == null) { 1285 | labels = null; 1286 | return; 1287 | } 1288 | if (stepCount > labelsArray.length) { 1289 | throw new IllegalArgumentException( 1290 | "Invalid number of labels for the indicators. Please provide a list " + 1291 | "of labels with at least as many items as the number of steps required!"); 1292 | } 1293 | labels = labelsArray; 1294 | showLabels(true); 1295 | } 1296 | 1297 | public void setLabelColor(int color) { 1298 | labelPaint.setColor(color); 1299 | requestLayout(); 1300 | invalidate(); 1301 | } 1302 | 1303 | /** 1304 | * Shows the labels if true is passed. Else hides them. 1305 | * 1306 | * @param show Boolean to show or hide the labels 1307 | */ 1308 | public void showLabels(boolean show) { 1309 | showLabels = show; 1310 | requestLayout(); 1311 | invalidate(); 1312 | } 1313 | 1314 | /** 1315 | * Add the {@link OnStepClickListener} to the list of listeners which will receive events when an step is clicked. 1316 | * 1317 | * @param listener The {@link OnStepClickListener} which will be added 1318 | */ 1319 | public void addOnStepClickListener(OnStepClickListener listener) { 1320 | onStepClickListeners.add(listener); 1321 | } 1322 | 1323 | /** 1324 | * Remove the specified {@link OnStepClickListener} from the list of listeners which will receive events when an 1325 | * step is clicked. 1326 | * 1327 | * @param listener The {@link OnStepClickListener} which will be removed 1328 | */ 1329 | @SuppressWarnings("unused") 1330 | public void removeOnStepClickListener(OnStepClickListener listener) { 1331 | onStepClickListeners.remove(listener); 1332 | } 1333 | 1334 | /** 1335 | * Remove all {@link OnStepClickListener} listeners from the StepperIndicator widget. 1336 | * No more events will be propagated. 1337 | */ 1338 | @SuppressWarnings("unused") 1339 | public void clearOnStepClickListeners() { 1340 | onStepClickListeners.clear(); 1341 | } 1342 | 1343 | /** 1344 | * Check if the widget has any valid {@link OnStepClickListener} listener set for receiving events from the steps. 1345 | * 1346 | * @return {@code true} if there are listeners registered, {@code false} otherwise. 1347 | */ 1348 | public boolean isOnStepClickListenerAvailable() { 1349 | return null != onStepClickListeners && !onStepClickListeners.isEmpty(); 1350 | } 1351 | 1352 | public void setDoneIcon(@Nullable Drawable doneIcon) { 1353 | this.doneIcon = doneIcon; 1354 | if (doneIcon != null) { 1355 | showDoneIcon = true; 1356 | int size = getContext().getResources().getDimensionPixelSize(R.dimen.stpi_done_icon_size); 1357 | doneIcon.setBounds(0, 0, size, size); 1358 | } 1359 | invalidate(); 1360 | } 1361 | 1362 | public void setShowDoneIcon(boolean showDoneIcon) { 1363 | this.showDoneIcon = showDoneIcon; 1364 | invalidate(); 1365 | } 1366 | 1367 | @Override 1368 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 1369 | /* no-op */ 1370 | } 1371 | 1372 | @Override 1373 | public void onPageSelected(int position) { 1374 | setCurrentStep(position); 1375 | } 1376 | 1377 | @Override 1378 | public void onPageScrollStateChanged(int state) { 1379 | /* no-op */ 1380 | } 1381 | 1382 | @Override 1383 | public void onRestoreInstanceState(Parcelable state) { 1384 | SavedState savedState = (SavedState) state; 1385 | super.onRestoreInstanceState(savedState.getSuperState()); 1386 | // Try to restore the current step 1387 | currentStep = savedState.mCurrentStep; 1388 | requestLayout(); 1389 | } 1390 | 1391 | @Override 1392 | public Parcelable onSaveInstanceState() { 1393 | Parcelable superState = super.onSaveInstanceState(); 1394 | SavedState savedState = new SavedState(superState); 1395 | // Store current stop so that it can be resumed when restored 1396 | savedState.mCurrentStep = currentStep; 1397 | return savedState; 1398 | } 1399 | 1400 | /** 1401 | * Contract used by the StepperIndicator widget to notify any listener of steps interaction events. 1402 | */ 1403 | public interface OnStepClickListener { 1404 | 1405 | /** 1406 | * Step was clicked 1407 | * 1408 | * @param step The step position which was clicked. (starts from 0, as the ViewPager bound to the widget) 1409 | */ 1410 | void onStepClicked(int step); 1411 | } 1412 | 1413 | /** 1414 | * Saved state in which information about the state of the widget is stored. 1415 | *

1416 | * Use this whenever you want to store or restore some information about the state of the widget. 1417 | *

1418 | */ 1419 | private static class SavedState extends BaseSavedState { 1420 | 1421 | @SuppressWarnings("UnusedDeclaration") 1422 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 1423 | @Override 1424 | public SavedState createFromParcel(Parcel in) { 1425 | return new SavedState(in); 1426 | } 1427 | 1428 | @Override 1429 | public SavedState[] newArray(int size) { 1430 | return new SavedState[size]; 1431 | } 1432 | }; 1433 | private int mCurrentStep; 1434 | 1435 | public SavedState(Parcelable superState) { 1436 | super(superState); 1437 | } 1438 | 1439 | private SavedState(Parcel in) { 1440 | super(in); 1441 | mCurrentStep = in.readInt(); 1442 | } 1443 | 1444 | @Override 1445 | public void writeToParcel(Parcel dest, int flags) { 1446 | super.writeToParcel(dest, flags); 1447 | dest.writeInt(mCurrentStep); 1448 | } 1449 | } 1450 | } 1451 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/ic_done_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/library/src/main/res/drawable-hdpi/ic_done_white_18dp.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/ic_done_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/library/src/main/res/drawable-mdpi/ic_done_white_18dp.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/ic_done_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/library/src/main/res/drawable-xhdpi/ic_done_white_18dp.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxhdpi/ic_done_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/library/src/main/res/drawable-xxhdpi/ic_done_white_18dp.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_done_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/library/src/main/res/drawable-xxxhdpi/ic_done_white_18dp.png -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #b3bdc2 4 | #00b47c 5 | #b3bdc2 6 | #000 7 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10dp 4 | 4dp 5 | 4dp 6 | 2dp 7 | 5dp 8 | 9 | 12sp 10 | 11 | 3dp 12 | 50dp 13 | 10dp 14 | 15 | 12sp 16 | 2dp 17 | 18 | 2dp 19 | 18dp 20 | 21 | -------------------------------------------------------------------------------- /library/src/test/java/com/badoualy/stepperindicator/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 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 | } -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def compileLocal = true 4 | 5 | android { 6 | compileSdkVersion 26 7 | buildToolsVersion "26.0.0" 8 | 9 | defaultConfig { 10 | applicationId "com.badoualy.stepperindicator.sample" 11 | minSdkVersion 14 12 | targetSdkVersion 26 13 | versionCode 105 14 | versionName "1.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:26.0.0' 28 | 29 | if (compileLocal) 30 | compile project(':library') 31 | else 32 | compile 'com.github.badoualy:stepper-indicator:1.0.4' 33 | } 34 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/badoualy/stepperindicator/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/badoualy/stepperindicator/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.ViewPager; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | import com.badoualy.stepperindicator.StepperIndicator; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(@Nullable Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | 17 | final ViewPager pager = findViewById(R.id.pager); 18 | assert pager != null; 19 | pager.setAdapter(new PagerAdapter(getSupportFragmentManager())); 20 | 21 | final StepperIndicator indicator = findViewById(R.id.stepper_indicator); 22 | // We keep last page for a "finishing" page 23 | indicator.setViewPager(pager, true); 24 | 25 | indicator.addOnStepClickListener(new StepperIndicator.OnStepClickListener() { 26 | @Override 27 | public void onStepClicked(int step) { 28 | pager.setCurrentItem(step, true); 29 | } 30 | }); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/badoualy/stepperindicator/sample/PageFragment.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | public class PageFragment extends Fragment { 12 | 13 | private TextView lblPage; 14 | 15 | public static PageFragment newInstance(int page, boolean isLast) { 16 | Bundle args = new Bundle(); 17 | args.putInt("page", page); 18 | if (isLast) 19 | args.putBoolean("isLast", true); 20 | final PageFragment fragment = new PageFragment(); 21 | fragment.setArguments(args); 22 | return fragment; 23 | } 24 | 25 | @Nullable 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 28 | final View view = inflater.inflate(R.layout.fragment_page, container, false); 29 | lblPage = (TextView) view.findViewById(R.id.lbl_page); 30 | return view; 31 | } 32 | 33 | @Override 34 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 35 | super.onActivityCreated(savedInstanceState); 36 | final int page = getArguments().getInt("page", 0); 37 | if (getArguments().containsKey("isLast")) 38 | lblPage.setText("You're done!"); 39 | else 40 | lblPage.setText(Integer.toString(page)); 41 | } 42 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/badoualy/stepperindicator/sample/PagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator.sample; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | 7 | class PagerAdapter extends FragmentPagerAdapter { 8 | 9 | public PagerAdapter(FragmentManager fm) { 10 | super(fm); 11 | } 12 | 13 | @Override 14 | public int getCount() { 15 | return 5; 16 | } 17 | 18 | @Override 19 | public Fragment getItem(int position) { 20 | return PageFragment.newInstance(position + 1, position == getCount() - 1); 21 | } 22 | 23 | @Override 24 | public CharSequence getPageTitle(int position) { 25 | return "Page " + position; 26 | } 27 | } -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoualy/stepper-indicator/5fbaaee013a4f6fc40dc00d1e0f7976cce562818/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00b47c 4 | #02a673 5 | #FFFFFF 6 | 7 | 8 | #FF4ECDC4 9 | #FFFFBD55 10 | #FFFB8A61 11 | #FF20A7C7 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Stepper Indicator 3 | 4 | 5 | Approval 6 | Processed 7 | Shipped 8 | Delivery 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/src/test/java/com/badoualy/stepperindicator/ExampleUnitTestSample.java: -------------------------------------------------------------------------------- 1 | package com.badoualy.stepperindicator; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTestSample { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library', ':sample' 2 | --------------------------------------------------------------------------------