├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── circle.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── onboarder ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── cuneytayyildiz │ │ └── onboarder │ │ ├── OnboarderActivity.kt │ │ ├── OnboarderPagerAdapter.kt │ │ ├── model │ │ └── OnboarderPage.kt │ │ ├── utils │ │ ├── Extensions.kt │ │ ├── OnboarderPageChangeListener.kt │ │ ├── TypefaceCacheManager.kt │ │ └── TypefaceManager.kt │ │ └── views │ │ └── CircleIndicatorView.kt │ └── res │ ├── drawable │ ├── ic_android_white_192dp.xml │ ├── ic_arrow_forward_white_24dp.xml │ ├── ic_done_white_24dp.xml │ └── ic_right_white_24dp.xml │ ├── layout-land │ └── item_onboarder.xml │ ├── layout │ ├── activity_onboarder.xml │ └── item_onboarder.xml │ ├── values-h720dp │ └── dimen.xml │ ├── values-tr │ └── strings.xml │ └── values │ ├── colors.xml │ ├── dimen.xml │ ├── strings.xml │ └── styles.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── cuneytayyildiz │ │ └── androidonboarder │ │ └── IntroActivity.kt │ └── res │ ├── drawable │ ├── donut_circle.png │ ├── eclair_circle.png │ ├── froyo_circle.png │ ├── gingerbread_circle.png │ ├── honeycomb_circle.png │ ├── icecream_circle.png │ ├── jellybean_circle.png │ ├── kitkat_circle.png │ ├── lollipop_circle.png │ ├── marshmallow_circle.png │ ├── nougat_circle.png │ ├── oreo_circle.png │ ├── pie_circle.png │ └── q_circle.png │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | build/ 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # Eclipse project files 21 | .classpath 22 | .project 23 | 24 | # Windows thumbnail db 25 | .DS_Store 26 | 27 | # IDEA/Android Studio project files, because 28 | # the project can be imported from settings.gradle 29 | .idea 30 | *.iml 31 | 32 | # Old-style IDEA project files 33 | *.ipr 34 | *.iws 35 | 36 | # Local IDEA workspace 37 | .idea/workspace.xml 38 | 39 | # Gradle cache 40 | .gradle 41 | 42 | # Sandbox stuff 43 | _sandbox 44 | /gradle.properties 45 | /upload_archives.gradle 46 | /maven_push.gradle 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cuneyt AYYILDIZ 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 | 2 | [![Android Arsenal]( https://img.shields.io/badge/Android%20Arsenal-Android%20Onboarder-green.svg?style=flat )]( https://android-arsenal.com/details/1/6598 ) 3 | 4 | # Android Onboarder 5 |

6 | Android Onboarder is a simple and lightweight library that helps you to create cool and beautiful introduction screens for your apps without writing dozens of lines of code. 7 |

8 | 9 | * [Features](#features) 10 | * [Getting Started](#getting-started) 11 | * [Adding dependency](#adding-dependency) 12 | * [Basic usage](#basic-usage) 13 | * [Activity functions](#activity-functions) 14 | * [Page Properties](#page-properties) 15 | * [Style Modifications](#style-modifications) 16 | * [Localization](#localization) 17 | * [License](#license) 18 | 19 | ![Sample image](https://media.giphy.com/media/WplHKZTuKyX7cKZGw7/giphy.gif) 20 | 21 | ## Features 22 | 23 | * **API >= 14** compatible. 24 | * 100% Kotlin ❤️ 25 | * **AndroidX** compatible. 26 | * Support for **runtime permissions**. 27 | * Dependent only on AndroidX AppCompat/Annotations, ConstraintLayout and Kotlin JDK. 28 | 29 | 30 | ## Getting Started 31 | 32 | #### Adding dependency 33 | 34 | Add dependency in your build.gradle (app/build.gradle) 35 | 36 | ```groovy 37 | implementation 'com.cuneytayyildiz:onboarder:2.0.0' 38 | ``` 39 | 40 | #### Basic usage 41 | To use Onboarder, create an activity that extends from OnboarderActivity like the following: 42 | ```kotlin 43 | 44 | class IntroActivity : OnboarderActivity(), OnboarderPageChangeListener { 45 | override fun onCreate(savedInstanceState: Bundle?) { 46 | super.onCreate(savedInstanceState) 47 | setOnboarderPageChangeListener(this) 48 | 49 | val pages: MutableList = createOnboarderPages() 50 | 51 | initOnboardingPages(pages) 52 | } 53 | 54 | public override fun onSkipButtonPressed() { 55 | super.onSkipButtonPressed() 56 | Toast.makeText(this, "Skip button was pressed!", Toast.LENGTH_SHORT).show() 57 | } 58 | 59 | override fun onFinishButtonPressed() { 60 | // implement your logic, save induction has done to sharedPrefs 61 | Toast.makeText(this, "Finish button was pressed", Toast.LENGTH_SHORT).show() 62 | } 63 | 64 | override fun onPageChanged(position: Int) { 65 | Log.d(javaClass.simpleName, "onPageChanged: $position") 66 | } 67 | 68 | private fun createOnboarderPages(): MutableList { 69 | return mutableListOf( 70 | onboarderPage { 71 | backgroundColor = color(R.color.color_donut) 72 | 73 | image { 74 | imageResId = R.drawable.donut_circle 75 | } 76 | 77 | title { 78 | text = "Donut" 79 | textColor = color(R.color.primary_text) 80 | } 81 | 82 | description { 83 | text = "Version 1.6 Donut was given the name Donut. " 84 | textColor = color(R.color.secondary_text) 85 | multilineCentered = true 86 | } 87 | }, 88 | onboarderPage { 89 | backgroundColor = color(R.color.color_android_green) 90 | 91 | image { 92 | imageResId = R.drawable.q_circle 93 | } 94 | 95 | title { 96 | text = "Q" 97 | textColor = color(R.color.primary_text) 98 | } 99 | 100 | description { 101 | text = "Android 10 was officially released on September 3, 2019 for supported Google Pixel devices.\n" + 102 | "\nGoogle announced that a new Android Version will be officially known as Android 10." 103 | textColor = color(R.color.secondary_text) 104 | multilineCentered = true 105 | } 106 | 107 | miscellaneousButton { 108 | visibility = View.VISIBLE 109 | text = "What's Next?" 110 | backgroundColor = Color.WHITE 111 | textColor = color(R.color.color_android_green) 112 | clickListener = View.OnClickListener { 113 | Toast.makeText(this@IntroActivity, "Hello World", Toast.LENGTH_SHORT).show() 114 | } 115 | } 116 | } 117 | ) 118 | } 119 | } 120 | ``` 121 | 122 | #### Activity functions 123 | 124 | ```kotlin 125 | /*********** Activity methods ***********/ 126 | fun initOnboardingPages(pages: MutableList) // Set onboarding pages into adapter 127 | fun setPageTransformer(pageTransformer: ViewPager.PageTransformer) // Animate your page transitions 128 | fun setOnboarderPageChangeListener(onboarderPageChangeListener: OnboarderPageChangeListener?) // Get current position of the page 129 | 130 | fun setInactiveIndicatorColor(color: Int) // Change dot's color for inactive status 131 | fun setActiveIndicatorColor(color: Int) // Change dot's color for active status 132 | 133 | fun setDividerColor(@ColorInt color: Int) // Set divider color 134 | fun setDividerHeight(heightInDp: Int) // Set divider height 135 | fun setDividerVisibility(dividerVisibility: Int) // Hide divider 136 | 137 | fun shouldUseFloatingActionButton(shouldUseFloatingActionButton: Boolean) // Change skip and finish button as FloatingActionButton aka FAB 138 | 139 | fun shouldDarkenButtonsLayout(shouldDarkenButtonsLayout: Boolean) // Make buttons layout darker on bottom 140 | 141 | fun setSkipButtonHidden() // Hide skip button 142 | fun setSkipButtonTitle(title: CharSequence?) // Set custom text for skip button 143 | fun setSkipButtonTitle(@StringRes titleResId: Int) 144 | fun setSkipButtonTextColor(@ColorRes colorResId: Int) 145 | fun setSkipButtonBackgroundColor(@ColorRes colorResId: Int) 146 | 147 | fun setFinishButtonTitle(title: CharSequence?) // Set custom text for finish button 148 | fun setFinishButtonTitle(@StringRes titleResId: Int) 149 | fun setFinishButtonTextColor(@ColorRes colorResId: Int) 150 | fun setFinishButtonBackgroundColor(@ColorRes colorResId: Int) 151 | 152 | fun setNextButtonTitle(title: CharSequence?) // Set custom text for next button 153 | fun setNextButtonTitle(@StringRes titleResId: Int) 154 | fun setNextButtonTextColor(@ColorRes colorResId: Int) 155 | fun setNextButtonBackgroundColor(@ColorRes colorResId: Int) 156 | fun setNextButtonIcon(@DrawableRes drawableResId: Int) 157 | 158 | fun NextButton() // Get Next button to change it's properties 159 | fun SkipButton() // Get Skip button to change it's properties 160 | fun FinishButton() // Get Finish button to change it's properties 161 | fun FabButton() // Get Fab button to change it's properties 162 | ``` 163 | 164 | #### Page Properties 165 | You can change most properties of the views on page such as background color, text size, text color, showing a button, setting a click listener to view etc. 166 | ```kotlin 167 | data class OnboarderPage( 168 | backgroundColor // Background color of the page 169 | image: OnboarderImage // Thumbnail image at top 170 | title: OnboarderText // Title of the page 171 | description: OnboarderText // Description text 172 | miscellaneousButton: OnboarderMiscellaneousButton // A button where you can add any functionality that you want. Request a permission, show recent changes of your app, start your privacy policy url etc. 173 | ) 174 | 175 | data class OnboarderImage( 176 | imageResId // R.drawable.first_page_thumbnail 177 | drawable // ContextCompat.getDrawable(context, R.drawable.first_page_thumbnail) 178 | imageWidthPx // ViewGroup.LayoutParams.WRAP_CONTENT, 100.dp, 100.px 179 | imageHeightPx // ViewGroup.LayoutParams.WRAP_CONTENT, 100.dp, 100.px 180 | imageBias // .5f (0 is top, 1 is bottom) 181 | ) 182 | 183 | data class OnboarderText( 184 | text // "First Page Title" 185 | textResId // R.string.first_page_title 186 | textColor // color(R.color.first_page_title_color) 187 | backgroundColor // color(R.color.first_page_title_background_color) 188 | widthPx // ViewGroup.LayoutParams.WRAP_CONTENT, 100.dp, 100.px 189 | heightPx // ViewGroup.LayoutParams.WRAP_CONTENT, 100.dp, 100.px 190 | typefacePath // fonts/myfont.ttf 191 | typefaceFontResId // R.font.my_font 192 | textSize // 20f 193 | textPaddingBottomPx // 20 194 | multilineCentered // true, false 195 | clickListener // View.OnClickListener{ textView -> } 196 | ) 197 | 198 | data class OnboarderMiscellaneousButton( 199 | visibility // default View.GONE, 200 | text // Button text 201 | textResId // R.string.button_text 202 | textColor // color(R.color.button_text_color) 203 | backgroundColor // color(R.color.button_background_color) 204 | widthPx // ViewGroup.LayoutParams.WRAP_CONTENT, 100.dp, 100.px 205 | heightPx // ViewGroup.LayoutParams.WRAP_CONTENT, 100.dp, 100.px 206 | typefacePath // fonts/myfont.ttf 207 | typefaceFontResId // R.font.my_font 208 | textSize // 20f 209 | leftPadding // 16, 210 | rightPadding // 16, 211 | topPadding // 8, 212 | bottomPadding // 8 213 | clickListener // View.OnClickListener{ button -> } 214 | ) 215 | ``` 216 | 217 | #### Style modifications 218 | If you would like to change style on the OnboarderPage, you can simple add these styles in your app/res/values/styles.xml and change the attributes. 219 | 220 | ```xml 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | ``` 229 | 230 | #### Localization 231 | To localize buttons from the library to your language, add these strings into corresponding app/res/values-XX/strings.xml 232 | ```xml 233 | 234 | 235 | ... 236 | ... 237 | ... 238 | 239 | ``` 240 | or 241 | ```kotlin 242 | SkipButton().setText(R.string.onboarder_button_skip) 243 | NextButton().setText(R.string.onboarder_button_next) 244 | FinishButton().setText(R.string.onboarder_button_finish) 245 | ``` 246 | 247 | 248 | ## License 249 | 250 | ``` 251 | MIT License 252 | 253 | Copyright (c) 2017 Cuneyt AYYILDIZ 254 | 255 | Permission is hereby granted, free of charge, to any person obtaining a copy 256 | of this software and associated documentation files (the "Software"), to deal 257 | in the Software without restriction, including without limitation the rights 258 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 259 | copies of the Software, and to permit persons to whom the Software is 260 | furnished to do so, subject to the following conditions: 261 | 262 | The above copyright notice and this permission notice shall be included in all 263 | copies or substantial portions of the Software. 264 | 265 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 266 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 267 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 268 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 269 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 270 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 271 | SOFTWARE. 272 | 273 | ``` 274 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.72' 3 | 4 | repositories { 5 | google() 6 | mavenCentral() 7 | jcenter() 8 | maven { 9 | url "https://maven.google.com" 10 | } 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:4.0.0-rc01' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | } 16 | } 17 | 18 | allprojects { 19 | tasks.withType(Javadoc).all { enabled = false } 20 | 21 | repositories { 22 | google() 23 | jcenter() 24 | maven { 25 | url "https://maven.google.com" 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | environment: 3 | GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' 4 | JAVA_OPTS: "-Xms512m -Xmx2048m" 5 | ANDROID_HOME: /usr/local/android-sdk-linux 6 | TERM: "dumb" 7 | java: 8 | version: oraclejdk8 9 | dependencies: 10 | pre: 11 | - echo y | android update sdk --no-ui --all --filter tools,platform-tools,build-tools-26.0.1,android-26,extra-google-m2repository,extra-google-google_play_services,extra-android-support,extra-android-m2repository 12 | cache_directories: 13 | - /usr/local/android-sdk-linux/platforms/android-26 14 | - /usr/local/android-sdk-linux/build-tools/26.0.1 15 | - /usr/local/android-sdk-linux/extras/android/m2repository 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swisyn/Android-Onboarder/8ac8870cc121316c9b40e42fd7a45b4bb31408a7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 15 23:12:44 CEST 2020 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-6.1.1-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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /onboarder/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /onboarder/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | vectorDrawables.useSupportLibrary = true 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | debug { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | lintOptions { 26 | abortOnError false 27 | } 28 | sourceSets { 29 | main.java.srcDirs += 'src/main/kotlin' 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation "androidx.core:core-ktx:1.2.0" 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | 37 | implementation 'androidx.appcompat:appcompat:1.1.0' 38 | implementation 'com.google.android.material:material:1.2.0-alpha06' 39 | implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6' 40 | 41 | } 42 | apply from: '../maven_push.gradle' 43 | -------------------------------------------------------------------------------- /onboarder/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=AndroidOnboarder 2 | POM_ARTIFACT_ID=onboarder 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /onboarder/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Blackmagic/Library/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 | -------------------------------------------------------------------------------- /onboarder/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /onboarder/src/main/kotlin/com/cuneytayyildiz/onboarder/OnboarderActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cuneytayyildiz.onboarder 2 | 3 | import android.animation.ArgbEvaluator 4 | import android.graphics.Color 5 | import android.os.Build 6 | import android.os.Bundle 7 | import android.util.TypedValue 8 | import android.view.View 9 | import android.widget.Button 10 | import android.widget.FrameLayout 11 | import androidx.annotation.ColorInt 12 | import androidx.annotation.ColorRes 13 | import androidx.annotation.DrawableRes 14 | import androidx.annotation.StringRes 15 | import androidx.appcompat.app.AppCompatActivity 16 | import androidx.core.content.ContextCompat 17 | import androidx.viewpager.widget.ViewPager 18 | import androidx.viewpager.widget.ViewPager.OnPageChangeListener 19 | import com.cuneytayyildiz.onboarder.model.OnboarderPage 20 | import com.cuneytayyildiz.onboarder.utils.* 21 | import com.cuneytayyildiz.onboarder.views.CircleIndicatorView 22 | import com.google.android.material.floatingactionbutton.FloatingActionButton 23 | 24 | abstract class OnboarderActivity : AppCompatActivity(R.layout.activity_onboarder), View.OnClickListener, OnPageChangeListener { 25 | private lateinit var circleIndicatorView: CircleIndicatorView 26 | private lateinit var viewPagerOnboarder: ViewPager 27 | private lateinit var btnSkip: Button 28 | private lateinit var btnFinish: Button 29 | private lateinit var btnNext: Button 30 | private lateinit var buttonsLayout: FrameLayout 31 | private lateinit var fab: FloatingActionButton 32 | private lateinit var divider: View 33 | 34 | private lateinit var onboarderAdapter: OnboarderPagerAdapter 35 | 36 | private var backgroundColors: Array? = null 37 | 38 | private var evaluator: ArgbEvaluator = ArgbEvaluator() 39 | private var shouldDarkenButtonsLayout = false 40 | private var shouldUseFloatingActionButton = false 41 | private var onboarderPageChangeListener: OnboarderPageChangeListener? = null 42 | 43 | override fun onCreate(savedInstanceState: Bundle?) { 44 | super.onCreate(savedInstanceState) 45 | 46 | initViews() 47 | } 48 | 49 | private fun initViews() { 50 | supportActionBar?.hide() 51 | 52 | setStatusBackgroundColor() 53 | 54 | circleIndicatorView = findViewById(R.id.indicator_circle) 55 | btnNext = findViewById(R.id.button_next) 56 | btnSkip = findViewById(R.id.button_skip) 57 | btnFinish = findViewById(R.id.button_finish) 58 | buttonsLayout = findViewById(R.id.layout_buttons) 59 | fab = findViewById(R.id.fab) 60 | divider = findViewById(R.id.divider) 61 | viewPagerOnboarder = findViewById(R.id.viewpager_onboarder) 62 | 63 | viewPagerOnboarder.addOnPageChangeListener(this) 64 | btnNext.setOnClickListener(this) 65 | btnSkip.setOnClickListener(this) 66 | btnFinish.setOnClickListener(this) 67 | fab.setOnClickListener(this) 68 | } 69 | 70 | fun initOnboardingPages(pages: MutableList) { 71 | backgroundColors = getPageBackgroundColors(pages) 72 | 73 | onboarderAdapter = OnboarderPagerAdapter(this, pages) 74 | viewPagerOnboarder.adapter = onboarderAdapter 75 | circleIndicatorView.setPageIndicatorCount(pages.size) 76 | } 77 | 78 | private fun darkenColor(@ColorInt color: Int): Int { 79 | val hsv = FloatArray(3) 80 | Color.colorToHSV(color, hsv) 81 | hsv[2] *= 0.9f 82 | return Color.HSVToColor(hsv) 83 | } 84 | 85 | private fun setStatusBackgroundColor() { 86 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 87 | window.decorView.systemUiVisibility = ( 88 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 89 | or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) 90 | window.statusBarColor = ContextCompat.getColor(this, R.color.black_transparent) 91 | } 92 | } 93 | 94 | override fun onClick(view: View) { 95 | val viewId = view.id 96 | 97 | if (::onboarderAdapter.isInitialized) { 98 | val isInLastPage = viewPagerOnboarder.currentItem == onboarderAdapter.lastPosition 99 | 100 | when { 101 | viewId == R.id.button_next || viewId == R.id.fab && !isInLastPage -> { 102 | viewPagerOnboarder.currentItem = viewPagerOnboarder.currentItem + 1 103 | } 104 | viewId == R.id.button_skip -> { 105 | onSkipButtonPressed() 106 | } 107 | viewId == R.id.button_finish || viewId == R.id.fab && isInLastPage -> { 108 | onFinishButtonPressed() 109 | } 110 | } 111 | } 112 | } 113 | 114 | override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { 115 | if (::onboarderAdapter.isInitialized && backgroundColors != null) { 116 | val colors = backgroundColors!! 117 | 118 | if (position < onboarderAdapter.lastPosition && position < colors.size - 1) { 119 | viewPagerOnboarder.setBackgroundColor((evaluator.evaluate(positionOffset, colors[position], colors[position + 1]) as Int)) 120 | 121 | if (shouldDarkenButtonsLayout) { 122 | buttonsLayout.setBackgroundColor(darkenColor(evaluator.evaluate(positionOffset, colors[position], colors[position + 1]) as Int)) 123 | divider.gone() 124 | } 125 | } else { 126 | viewPagerOnboarder.setBackgroundColor(colors[colors.size - 1]) 127 | 128 | if (shouldDarkenButtonsLayout) { 129 | buttonsLayout.setBackgroundColor(darkenColor(colors[colors.size - 1])) 130 | divider.gone() 131 | } 132 | } 133 | } 134 | } 135 | 136 | override fun onPageSelected(position: Int) { 137 | if (::onboarderAdapter.isInitialized) { 138 | circleIndicatorView.setCurrentPage(position) 139 | 140 | val isLastPage = (position == onboarderAdapter.lastPosition) 141 | 142 | if (shouldUseFloatingActionButton) { 143 | fab.setImageResource(if (isLastPage) R.drawable.ic_done_white_24dp else R.drawable.ic_arrow_forward_white_24dp) 144 | } else { 145 | if (isLastPage) { 146 | btnNext.gone() 147 | btnFinish.visible() 148 | } else { 149 | btnFinish.gone() 150 | btnNext.visible() 151 | } 152 | } 153 | 154 | onboarderPageChangeListener?.onPageChanged(position) 155 | } 156 | } 157 | 158 | override fun onPageScrollStateChanged(state: Int) {} 159 | 160 | protected open fun onSkipButtonPressed() { 161 | if (::onboarderAdapter.isInitialized) { 162 | viewPagerOnboarder.currentItem = onboarderAdapter.count 163 | } 164 | } 165 | 166 | abstract fun onFinishButtonPressed() 167 | 168 | protected fun setPage(position: Int) { 169 | viewPagerOnboarder.currentItem = position 170 | } 171 | 172 | fun setInactiveIndicatorColor(color: Int) { 173 | circleIndicatorView.setInactiveIndicatorColor(color) 174 | } 175 | 176 | fun setActiveIndicatorColor(color: Int) { 177 | circleIndicatorView.setActiveIndicatorColor(color) 178 | } 179 | 180 | fun shouldDarkenButtonsLayout(shouldDarkenButtonsLayout: Boolean) { 181 | this.shouldDarkenButtonsLayout = shouldDarkenButtonsLayout 182 | } 183 | 184 | fun setDividerColor(@ColorInt color: Int) { 185 | if (!shouldDarkenButtonsLayout) divider.setBackgroundColor(color) 186 | } 187 | 188 | fun setDividerHeight(heightInDp: Int) { 189 | if (!shouldDarkenButtonsLayout) divider.layoutParams.height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, heightInDp.toFloat(), 190 | resources.displayMetrics).toInt() 191 | } 192 | 193 | fun setDividerVisibility(dividerVisibility: Int) { 194 | divider.visibility = dividerVisibility 195 | } 196 | 197 | fun setOnboarderPageChangeListener(onboarderPageChangeListener: OnboarderPageChangeListener?) { 198 | this.onboarderPageChangeListener = onboarderPageChangeListener 199 | } 200 | 201 | fun shouldUseFloatingActionButton(shouldUseFloatingActionButton: Boolean) { 202 | this.shouldUseFloatingActionButton = shouldUseFloatingActionButton 203 | 204 | if (shouldUseFloatingActionButton) { 205 | fab.visible() 206 | setDividerVisibility(View.GONE) 207 | shouldDarkenButtonsLayout(false) 208 | btnFinish.gone() 209 | btnSkip.gone() 210 | btnNext.gone() 211 | btnNext.isFocusable = false 212 | buttonsLayout.layoutParams.height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 96f, 213 | resources.displayMetrics).toInt() 214 | } 215 | } 216 | 217 | fun setPageTransformer(pageTransformer: ViewPager.PageTransformer) { 218 | viewPagerOnboarder.setPageTransformer(true, pageTransformer) 219 | } 220 | 221 | 222 | fun setSkipButtonTitle(title: CharSequence?) { 223 | btnSkip.text = title 224 | } 225 | 226 | fun setSkipButtonHidden() { 227 | btnSkip.gone() 228 | } 229 | 230 | fun setSkipButtonTitle(@StringRes titleResId: Int) { 231 | btnSkip.setText(titleResId) 232 | } 233 | 234 | fun setFinishButtonTitle(title: CharSequence?) { 235 | btnFinish.text = title 236 | } 237 | 238 | fun setFinishButtonTitle(@StringRes titleResId: Int) { 239 | btnFinish.setText(titleResId) 240 | } 241 | 242 | fun setFinishButtonTextColor(@ColorRes colorResId: Int) { 243 | btnFinish.setTextColor(color(colorResId)) 244 | } 245 | 246 | fun setNextButtonTextColor(@ColorRes colorResId: Int) { 247 | btnNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) 248 | btnNext.setTextColor(color(colorResId)) 249 | } 250 | 251 | fun setSkipButtonTextColor(@ColorRes colorResId: Int) { 252 | btnSkip.setTextColor(color(colorResId)) 253 | } 254 | 255 | fun setFinishButtonBackgroundColor(@ColorRes colorResId: Int) { 256 | btnFinish.setBackgroundColor(color(colorResId)) 257 | } 258 | 259 | fun setSkipButtonBackgroundColor(@ColorRes colorResId: Int) { 260 | btnSkip.setBackgroundColor(color(colorResId)) 261 | } 262 | 263 | fun setNextButtonBackgroundColor(@ColorRes colorResId: Int) { 264 | btnNext.setBackgroundColor(color(colorResId)) 265 | } 266 | 267 | fun setNextButtonTitle(title: CharSequence?) { 268 | btnNext.text = title 269 | btnNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) 270 | } 271 | 272 | fun setNextButtonTitle(@StringRes titleResId: Int) { 273 | btnNext.setText(titleResId) 274 | btnNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) 275 | } 276 | 277 | fun setNextButtonIcon(@DrawableRes drawableResId: Int) { 278 | btnNext.text = null 279 | btnNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, drawableResId) 280 | } 281 | 282 | fun NextButton() = btnNext 283 | 284 | fun SkipButton() = btnSkip 285 | 286 | fun FinishButton() = btnFinish 287 | 288 | fun FabButton() = fab 289 | 290 | private fun getPageBackgroundColors(pages: MutableList): Array { 291 | return pages.map { page -> page.backgroundColor }.toTypedArray() 292 | } 293 | } -------------------------------------------------------------------------------- /onboarder/src/main/kotlin/com/cuneytayyildiz/onboarder/OnboarderPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.cuneytayyildiz.onboarder 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.Button 9 | import android.widget.FrameLayout 10 | import android.widget.ImageView 11 | import android.widget.TextView 12 | import androidx.constraintlayout.widget.ConstraintLayout 13 | import androidx.viewpager.widget.PagerAdapter 14 | import com.cuneytayyildiz.onboarder.model.OnboarderImage 15 | import com.cuneytayyildiz.onboarder.model.OnboarderMiscellaneousButton 16 | import com.cuneytayyildiz.onboarder.model.OnboarderPage 17 | import com.cuneytayyildiz.onboarder.model.OnboarderText 18 | import com.cuneytayyildiz.onboarder.utils.TypefaceManager 19 | import com.cuneytayyildiz.onboarder.utils.px 20 | 21 | class OnboarderPagerAdapter(private val context: Context, private val onboarderPages: MutableList) : PagerAdapter() { 22 | 23 | override fun getCount(): Int = onboarderPages.size 24 | 25 | override fun isViewFromObject(view: View, obj: Any): Boolean = view == obj 26 | 27 | fun getItem(position: Int) = onboarderPages[position] 28 | 29 | override fun instantiateItem(container: ViewGroup, position: Int): Any { 30 | val itemView = LayoutInflater.from(context).inflate(R.layout.item_onboarder, container, false) 31 | 32 | val onboarderImageView = itemView.findViewById(R.id.image_top) 33 | val onboarderTitleTextView = itemView.findViewById(R.id.textview_title) 34 | val onboarderDescriptionTextView = itemView.findViewById(R.id.textview_description) 35 | val onboarderMiscellaneousButton = itemView.findViewById