├── .gitignore ├── LICENSE ├── README.md ├── art ├── custom_view.png ├── custom_view_res.png ├── focus_all.png ├── focus_min.png ├── focus_normal.png ├── gravity.png ├── home.png ├── materialintroviewgif.gif ├── recycler.png ├── sequence_multiple_fragments.png ├── skip_bottom_right.png ├── skip_top_right.png └── toolbar_item_custom_colors.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── materialintro ├── .gitignore ├── build.gradle ├── deploy.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── codertainment │ │ └── materialintro │ │ ├── MaterialIntroConfiguration.kt │ │ ├── animation │ │ ├── AnimationFactory.kt │ │ ├── AnimationListener.kt │ │ └── MaterialIntroListener.kt │ │ ├── prefs │ │ └── PreferencesManager.kt │ │ ├── sequence │ │ ├── MaterialIntroSequence.kt │ │ └── SkipLocation.kt │ │ ├── shape │ │ ├── Circle.kt │ │ ├── Focus.kt │ │ ├── FocusGravity.kt │ │ ├── Rect.kt │ │ ├── Shape.kt │ │ └── ShapeType.kt │ │ ├── target │ │ ├── Target.kt │ │ └── ViewTarget.kt │ │ ├── utils │ │ ├── Constants.kt │ │ ├── ExtensionHelpers.kt │ │ ├── SingletonHolder.kt │ │ └── Utils.kt │ │ └── view │ │ └── MaterialIntroView.kt │ └── res │ ├── drawable │ ├── dot.xml │ └── ic_help_outline_black.xml │ ├── layout │ ├── dot_view.xml │ └── material_intro_card.xml │ └── values │ ├── dimens.xml │ └── strings.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── codertainment │ │ └── materialintro │ │ └── sample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── codertainment │ │ │ └── materialintro │ │ │ └── sample │ │ │ ├── MainActivity.kt │ │ │ ├── ToolbarMenuItemActivity.kt │ │ │ ├── adapter │ │ │ └── RecyclerViewAdapter.kt │ │ │ ├── fragment │ │ │ ├── CustomInfoViewFragment.kt │ │ │ ├── FocusFragment.kt │ │ │ ├── GravityFragment.kt │ │ │ ├── MainFragment.kt │ │ │ ├── RecyclerViewFragment.kt │ │ │ └── sequence │ │ │ │ ├── Child1Fragment.kt │ │ │ │ ├── Child2Fragment.kt │ │ │ │ └── SequenceParentFragment.kt │ │ │ └── model │ │ │ └── Song.kt │ └── res │ │ ├── drawable-v21 │ │ ├── ic_menu_camera.xml │ │ ├── ic_menu_gallery.xml │ │ ├── ic_menu_manage.xml │ │ ├── ic_menu_send.xml │ │ ├── ic_menu_share.xml │ │ └── ic_menu_slideshow.xml │ │ ├── drawable │ │ ├── diamond.png │ │ ├── ic_skip.xml │ │ └── icon_miv.png │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_toolbar.xml │ │ ├── app_bar_main.xml │ │ ├── container.xml │ │ ├── content_main.xml │ │ ├── fragment_child1.xml │ │ ├── fragment_child2.xml │ │ ├── fragment_custom_info_view.xml │ │ ├── fragment_focus.xml │ │ ├── fragment_gravity.xml │ │ ├── fragment_recyclerview.xml │ │ ├── fragment_sequence_parent.xml │ │ ├── info_custom_view.xml │ │ ├── list_item_card.xml │ │ └── nav_header_main.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ └── main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_help_outline.png │ │ ├── ic_launcher.png │ │ ├── ic_search.png │ │ └── ic_share_white.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── drawables.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── codertainment │ └── materialintro │ └── sample │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle and Android Studio ignores 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | 8 | # IDEA/Android Studio project files, because 9 | # the project can be imported from settings.gradle 10 | .idea 11 | *.iml 12 | 13 | # Built application files 14 | *.apk 15 | *.ap_ 16 | 17 | # Files for the Dalvik VM 18 | *.dex 19 | 20 | # Java class files 21 | *.class 22 | 23 | # Generated files 24 | bin/ 25 | gen/ 26 | 27 | # Gradle files 28 | .gradle/ 29 | build/ 30 | 31 | # Local configuration file (sdk path, etc) 32 | local.properties 33 | 34 | # Proguard folder generated by Eclipse 35 | proguard/ 36 | 37 | # Log Files 38 | *.log 39 | 40 | .idea 41 | *.iml 42 | 43 | # Fabric files 44 | fabric.properties 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOW ARCHIVED [02 June 2022] 2 | I am sorry, but I no longer work on native Android, hence won't be able to maintain this repository. 3 | I now primarily work on Flutter 4 | 5 | # MaterialIntroView [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-MaterialIntroView%20v2-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/8059) [![](https://jitpack.io/v/shripal17/MaterialIntroView-v2.svg)](https://jitpack.io/#shripal17/MaterialIntroView-v2) [ ![Download](https://api.bintray.com/packages/shripal17/codertainment/materialintroview-v2/images/download.svg?version=2.2.0) ](https://bintray.com/shripal17/codertainment/materialintroview-v2/2.2.0/link)![](https://img.shields.io/badge/SDK-21+-blueviolet) 6 | 7 | Beautiful and highly customisable material-design based android library to help your users get started with your awesome app! 8 | Based originally on [iammert/MaterialIntroView](https://github.com/iammert/MaterialIntroView). 9 | 10 | Modifications/additions from the base lib: 11 | - [x] Migrate to AndroidX 12 | - [x] Migrated to Kotlin 13 | - [x] Add customisation options like help icon color, card background color, dot icon color 14 | - [x] Update Sample 15 | - [x] Custom align text in info card 16 | - [x] Custom help icon in info card 17 | - [x] Custom typeface for info text 18 | - [x] Custom Info View inside info card (using view or layout resource) 19 | - [x] Kotlin extension function for activity 20 | - [x] Full integration with MaterialIntroConfiguration 21 | - [x] Enhanced MaterialIntroListener, know when user has clicked or MIV was dismissed because it was set as saved 22 | - [x] Add option (userClickAsDisplayed) to set view intro as displayed only if user clicks on target view or outside too (if dismissOnTouch is enabled) 23 | - [x] More kotlin friendly 24 | - [x] Add Sequence (Added in v2.1.0) 25 | - [x] Singleton-based approach for unified experience across your app 26 | - [x] Bug fixes 27 | - [x] Add skip button for sequence with custom button attributes / button location using `SkipLocation` (BETA) (Added in v2.1.1) 28 | - [ ] CircularReveal animation for MIV show/hide 29 | 30 | 31 | # Screenshot 32 | 33 | 34 | Sample APK can be found in the [Releases Section](https://github.com/shripal17/MaterialIntroView-v2/releases) 35 | 36 | # BREAKING 37 | Upgrading to v2.2.0 will break your imports. This is because I have re-organized the extension methods. Please fix the imports by removing them from the `import` block and re-importing using Android Studio's `Alt+Enter` 38 | 39 | # Import 40 | ### Through bintray 41 | 1. Add to project-level build.gradle 42 | ```groovy 43 | buildscript { 44 | //... 45 | } 46 | allProjects { 47 | repositories { 48 | //... 49 | maven { url "https://dl.bintray.com/shripal17/codertainment" } 50 | } 51 | } 52 | ``` 53 | 2. Add to module-level build.gradle 54 | ```groovy 55 | dependencies { 56 | //... 57 | implementation 'com.codertainment.materialintro:materialintroview-v2:2.2.0' 58 | } 59 | ``` 60 | ### Through JitPack 61 | ```groovy 62 | buildscript { 63 | //... 64 | } 65 | allProjects { 66 | repositories { 67 | //... 68 | maven { url "https://jitpack.io" } 69 | } 70 | } 71 | ``` 72 | 2. Add to module-level build.gradle 73 | ```groovy 74 | dependencies { 75 | //... 76 | implementation 'com.github.shripal17:MaterialIntroView-v2:2.2.0' 77 | } 78 | ``` 79 | 80 | # Changelog 81 | Please check [Releases](https://github.com/shripal17/MaterialIntroView-v2/releases) 82 | 83 | # Single Usage in Activity/Fragment 84 | ```kotlin 85 | val miv = materialIntro(show = true /* if you want to show miv instantly */) { 86 | maskColor = Color.BLUE 87 | delayMillis = 300 88 | 89 | isFadeInAnimationEnabled = true 90 | isFadeOutAnimationEnabled = true 91 | fadeAnimationDurationMillis = 300 92 | 93 | focusType = Focus.ALL 94 | focusGravity = FocusGravity.CENTER 95 | 96 | padding = 24 // in px 97 | 98 | dismissOnTouch = false 99 | 100 | isInfoEnabled = true 101 | infoText = "Hello this is help message" 102 | infoTextColor = Color.BLACK 103 | infoTextSize = 18f 104 | infoTextAlignment = View.TEXT_ALIGNMENT_CENTER 105 | infoTextTypeface = Typeface.DEFAULT_BOLD 106 | infoCardBackgroundColor = Color.WHITE 107 | 108 | isHelpIconEnabled = true 109 | helpIconResource = R.drawable.your_icon 110 | helpIconDrawable = yourDrawable 111 | helpIconColor = Color.RED 112 | 113 | infoCustomView = yourViewHere 114 | infoCustomViewRes = R.layout.your_custom_view_here 115 | 116 | isDotViewEnabled = true 117 | isDotAnimationEnabled = true 118 | dotIconColor = Color.WHITE 119 | 120 | viewId = "unique_id" // or automatically picked from view's tag 121 | targetView = viewToBeFocusedHere 122 | 123 | isPerformClick = false 124 | 125 | showOnlyOnce = true 126 | userClickAsDisplayed = true 127 | 128 | shapeType = ShapeType.CIRCLE 129 | } 130 | // if you want to show it later 131 | miv.show(activity) 132 | ``` 133 | 134 | # Properties 135 | | Name | Description | Default Value | 136 | |-------------|--------------------------------|---------------| 137 | |maskColor | The background color | 46% Transparent | 138 | |delayMillis | Delay in ms for MIV (MaterialIntroView) to be shown | 500 | 139 | | isFadeInAnimationEnabled | Enable fade-in animation for MIV | true | 140 | | isFadeOutAnimationEnabled | Enable fade-out animation for MIV | true | 141 | |focusGravity | `FocusGravity.CENTER`, `FocusGravity.LEFT` or `FocusGravity.RIGHT` | `FocusGravity.CENTER` | 142 | | focusType | `Focus.ALL`, `Focus.MINIMUM` or `Focus.NORMAL` | `Focus.NORMAL` | 143 | | padding | Padding (in px) for focusing the target view | 10 | 144 | | dismissOnTouch | Dismiss intro when user touches anywhere | false | 145 | | isInfoEnabled | Whether to show info CardView | true | 146 | | infoText | Text (CharSequence) to be displayed in info CardView | Empty Text | 147 | | infoTextColor | Text Color for info text | `textColorPrimary` | 148 | | infoTextSize | Text size in sp for info text | 16sp | 149 | | infoTextAlignment | Text alignment for info text | `View.TEXT_ALIGNMENT_CENTER` | 150 | | infoTextTypeface | Custom typeface for info text | `Typeface.DEFAULT` | 151 | | infoCardBackgroundColor | Info CardView background color | Inherit from active theme | 152 | | isHelpIconEnabled | Whether to show the help icon in Info CardView | `true` | 153 | | helpIconColor | Tint help Icon | Black | 154 | | helpIconResource | Custom drawable Resource for help icon | NA | 155 | | helpIconDrawable | Custom drawable for help icon | NA | 156 | | infoCustomView | Custom view to be displayed inside info CardView | NA | 157 | | infoCustomViewRes | Custom layout resource id to be inflated inside CardView | NA | 158 | | isDotViewEnabled | Whether to show a dot at the centre of focus view | true | 159 | | isDotAnimationEnabled | Whether to zoom-in and zoom-out dot icon periodically | true | 160 | | dotIconColor | Tint Dot Icon | `textColorPrimaryInverse` | 161 | | viewId | Unique ID of View so that MIV doesn't show again 2nd time onwards (if `showOnlyOnce` is enabled) | Automatically picked from view's `tag` | 162 | | targetView | View to be focused on | NA | 163 | | isPerformClick | Click on the focused view when dismissing | false | 164 | | showOnlyOnce | MIV should be shown only once | true | 165 | | userClickAsDisplayed | MIV should be set as displayed only when user dismisses MIV manually, else MIV will be set as displayed as soon as it is rendered | true | 166 | | shapeType | `ShapeType.CIRCLE` or `ShapeType.RECTANGLE` | `ShapeType.CIRCLE` | 167 | | customShape | Use custom shape (Usage to be updated) | NA | 168 | | materialIntroListener | Callback when user dismisses a view or it is not shown because it was set as displayed | Current activity/fragment if it implements `MaterialIntroListener` | 169 | | skipLocation | Location of skip button on the screen `SkipLocation.BOTTOM_LEFT` or `SkipLocation.BOTTOM_RIGHT` or `SkipLocation.TOP_LEFT` or `SkipLocation.TOP_RIGHT` | `SkipLocation.BOTTOM_LEFT` | 170 | | skipText | Skip Button Text | `"Skip"` | 171 | | skipButtonStyling | Custom styling to be applied for the skip button (lambda function as member val) | NA | 172 | 173 | # Listener 174 | In your activity/fragment: 175 | ```kotlin 176 | class GravityFragment : Fragment(), MaterialIntroListener { 177 | /** 178 | * @param onUserClick is true when MIV has been dismissed through user click, false when MIV was previously displayed and was set as saved 179 | * @param viewId Unique ID of the target view 180 | */ 181 | override fun onIntroDone(onUserClick: Boolean, viewId: String) { 182 | // your action here 183 | } 184 | //... 185 | } 186 | ``` 187 | 188 | # Configuration Method 189 | ```kotlin 190 | //Create global config instance to not write same config again and again. 191 | val config = MaterialIntroConfiguration().apply { 192 | maskColor = Color.BLUE 193 | delayMillis = 300 194 | 195 | isFadeInAnimationEnabled = true 196 | isFadeOutAnimationEnabled = true 197 | fadeAnimationDurationMillis = 300 198 | 199 | focusType = Focus.ALL 200 | focusGravity = FocusGravity.CENTER 201 | 202 | padding = 24 // in px 203 | 204 | dismissOnTouch = false 205 | 206 | isInfoEnabled = true 207 | infoText = "Hello this is help message" 208 | infoTextColor = Color.BLACK 209 | infoTextSize = 18f 210 | infoTextAlignment = View.TEXT_ALIGNMENT_CENTER 211 | infoTextTypeface = Typeface.DEFAULT_BOLD 212 | infoCardBackgroundColor = Color.WHITE 213 | 214 | isHelpIconEnabled = true 215 | helpIconResource = R.drawable.your_icon 216 | helpIconDrawable = yourDrawable 217 | helpIconColor = Color.RED 218 | 219 | infoCustomView = yourViewHere 220 | infoCustomViewRes = R.layout.your_custom_view_here 221 | 222 | isDotViewEnabled = true 223 | isDotAnimationEnabled = true 224 | dotIconColor = Color.WHITE 225 | 226 | viewId = "unique_id" // or automatically picked from view's tag 227 | targetView = viewToBeFocusedHere 228 | 229 | isPerformClick = false 230 | 231 | showOnlyOnce = true 232 | userClickAsDisplayed = true 233 | 234 | shapeType = ShapeType.CIRCLE 235 | 236 | // skip customisations are only used when showSkip = true is set in MaterialIntroSequence 237 | skipLocation = SkipLocation.TOP_RIGHT 238 | skipText = "Skip" 239 | skipButtonStyling = { 240 | // apply custom styling for https://material.io/develop/android/components/buttons/ here 241 | // strokeWidth = 5 242 | // setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.colorAccent)) 243 | } 244 | } 245 | materialIntro(config = config) 246 | ``` 247 | # Sequence (Added in v2.1.0) 248 | Using MaterialIntroSequence, you can create a flow for intro view easily in your activity/fragments. Suppose your activity has multiple fragments and each fragment has some or the other view on which you want to show MIV but you want a specific sequence to be followed. In such cases, MaterialIntroSequence is your savior! 249 | 250 | The usage is quite simple, call the extension function from your activity/fragment and add MaterialIntroConfiguration objects to it: 251 | 252 | ```kotlin 253 | class YourFragment: Fragment(), MaterialIntroSequenceListener { 254 | 255 | //... 256 | 257 | override fun onResume() { 258 | super.onResume() 259 | /** 260 | * Create/get MaterialIntroSequence for the current fragment's activity 261 | * 262 | * If your Activity/Fragment implements MaterialIntroSequenceListener, it is automatically assigned as materialIntroSequenceListener for the current created instance 263 | * 264 | * @param initialDelay delay for the first MIV to be shown 265 | * 266 | * @param materialIntroSequenceListener listener for MaterialIntroSequence events 267 | * 268 | * @param showSkip Whether to show the skip button for MIVs 269 | * 270 | * @param persistSkip If enabled, once the user clicks on skip button, all new MIVs will be skipped too, else even after the user clicks on skip 271 | * button and new MIVs are added after that, for e.g. for another fragment, the new MIVs will be shown 272 | */ 273 | materialIntroSequence(initialDelay = 1000, showSkip = true, persistSkip = true) { 274 | addConfig { 275 | viewId = "viewId1" 276 | infoText = "Help for viewId1" 277 | infoCardBackgroundColor = Color.GREEN 278 | helpIconColor = Color.BLUE 279 | infoTextColor = Color.BLACK 280 | dotIconColor = Color.RED 281 | targetView = view1 282 | 283 | skipLocation = SkipLocation.TOP_RIGHT 284 | skipText = "Skip" 285 | skipButtonStyling = { 286 | // apply custom styling for https://material.io/develop/android/components/buttons/ here 287 | // strokeWidth = 5 288 | // setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.colorAccent)) 289 | } 290 | } 291 | addConfig { 292 | viewId = "viewId2" 293 | infoText = "Help for viewId2" 294 | infoCardBackgroundColor = Color.GREEN 295 | helpIconColor = Color.BLUE 296 | infoTextColor = Color.BLACK 297 | dotIconColor = Color.RED 298 | targetView = view2 299 | 300 | skipLocation = SkipLocation.TOP_RIGHT 301 | skipText = "Skip" 302 | skipButtonStyling = { 303 | // apply custom styling for https://material.io/develop/android/components/buttons/ here 304 | // strokeWidth = 5 305 | // setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.colorAccent)) 306 | } 307 | } 308 | } 309 | } 310 | 311 | /** 312 | * @param onUserClick if the MIV was dismissed by the user on click or it was auto-dismissed because it was set as displayed 313 | * @param viewId viewId for the dismissed MIV 314 | * @param current index of the dismissed MIV 315 | * @param total Total number of MIVs in the current MaterialIntroSequence 316 | */ 317 | override fun onProgress(onUserClick: Boolean, viewId: String, current: Int, total: Int) { 318 | toast("click: $onUserClick\nviewId: $viewId\ncurrent: $current\ntotal: $total") 319 | } 320 | 321 | /** 322 | * Called when all MIVs in the current MaterialIntroSequence have been dismissed 323 | */ 324 | override fun onCompleted() { 325 | toast("Tutorial Complete") 326 | } 327 | } 328 | ``` 329 | # Use Custom Shapes 330 | You can use your own highlight shapes if Circle and Rectangle do not work for you. See source for `Circle` and `Rect` for implementation example. 331 | > TODO update doc 332 | 333 | # More Screenshots 334 | | Default config | Right align gravity | RecyclerView item | 335 | |----------------|---------------------|-------------------| 336 | | ![Default config](/art/home.png?raw=true) | ![Right align gravity](/art/gravity.png?raw=true) | ![RecyclerView item](/art/recycler.png?raw=true) | 337 | 338 | | Focus All | Focus Minimum | Focus Normal | 339 | |----------------|---------------------|-------------------| 340 | | ![Focus All](/art/focus_all.png?raw=true) | ![Focus Minimum](/art/focus_min.png?raw=true) | ![Focus Normal](/art/focus_normal.png?raw=true) | 341 | 342 | | Toolbar Item with sequence and custom colors | Custom Info View using resource layout | Custom Info View at runtime | 343 | |----------------|---------------------|-------------------| 344 | | ![Toolbar Item with sequence and custom colors](/art/toolbar_item_custom_colors.png?raw=true) | ![Custom Info View using resource layout](/art/custom_view_res.png?raw=true) | ![Custom Info View at runtime](/art/custom_view.png?raw=true) | 345 | 346 | | Sequence with multiple fragments | Skip Button at Bottom Right Position with dotIconColor partially transparent | Skip Button at Top Right position | 347 | |----------------|----------------|----------------| 348 | |![Sequence with multiple fragments](/art/sequence_multiple_fragments.png?raw=true) | ![Skip Button at Bottom Right Position with dotIconColor partially transparent](/art/skip_bottom_right.png?raw=true) | ![Skip Button at Top Right position](/art/skip_top_right.png?raw=true) | 349 | 350 | # Full Demo GIF 351 | ![Whole Video](/art/materialintroviewgif.gif?raw=true) 352 | 353 | # Authors 354 | [Mert SIMSEK](https://github.com/iammert) 355 | 356 | [Murat Can BUR](https://github.com/muratcanbur) 357 | 358 | [Shripal Jain (Me)](https://github.com/shripal17) 359 | 360 | # Showcase 361 | Apps using this library 362 | 363 | Create a new issue to add your app here 364 | - [Portal Controller](https://play.google.com/store/apps/details?id=com.portalcomputainment.android.controller.client) 365 | 366 | # License 367 | -------- 368 | 369 | Copyright 2020 Shripal Jain 370 | 371 | Licensed under the Apache License, Version 2.0 (the "License"); 372 | you may not use this file except in compliance with the License. 373 | You may obtain a copy of the License at 374 | 375 | http://www.apache.org/licenses/LICENSE-2.0 376 | 377 | Unless required by applicable law or agreed to in writing, software 378 | distributed under the License is distributed on an "AS IS" BASIS, 379 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 380 | See the License for the specific language governing permissions and 381 | limitations under the License. 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | -------------------------------------------------------------------------------- /art/custom_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/custom_view.png -------------------------------------------------------------------------------- /art/custom_view_res.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/custom_view_res.png -------------------------------------------------------------------------------- /art/focus_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/focus_all.png -------------------------------------------------------------------------------- /art/focus_min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/focus_min.png -------------------------------------------------------------------------------- /art/focus_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/focus_normal.png -------------------------------------------------------------------------------- /art/gravity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/gravity.png -------------------------------------------------------------------------------- /art/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/home.png -------------------------------------------------------------------------------- /art/materialintroviewgif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/materialintroviewgif.gif -------------------------------------------------------------------------------- /art/recycler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/recycler.png -------------------------------------------------------------------------------- /art/sequence_multiple_fragments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/sequence_multiple_fragments.png -------------------------------------------------------------------------------- /art/skip_bottom_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/skip_bottom_right.png -------------------------------------------------------------------------------- /art/skip_top_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/skip_top_right.png -------------------------------------------------------------------------------- /art/toolbar_item_custom_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/art/toolbar_item_custom_colors.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.72' 5 | repositories { 6 | jcenter() 7 | google() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.6.3' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { project -> 19 | repositories { 20 | jcenter() 21 | maven { 22 | url "https://jitpack.io" 23 | } 24 | maven { 25 | url "https://maven.google.com" 26 | } 27 | google() 28 | group = 'com.codertainment.materialintro' 29 | version = '2.2.0' 30 | } 31 | } 32 | 33 | task clean(type: Delete) { 34 | delete rootProject.buildDir 35 | } 36 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.enableJetifier=true 20 | android.useAndroidX=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 17 20:55:44 CEST 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-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # 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 | -------------------------------------------------------------------------------- /materialintro/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /materialintro/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.github.dcendents.android-maven' version '2.1' 3 | } 4 | apply plugin: 'com.android.library' 5 | apply plugin: 'kotlin-android-extensions' 6 | apply plugin: 'kotlin-android' 7 | install { 8 | repositories.mavenInstaller { 9 | pom.project { 10 | groupId project.group 11 | artifactId 'materialintroview-v2' 12 | packaging 'aar' 13 | } 14 | } 15 | } 16 | 17 | apply from: 'deploy.gradle' 18 | android { 19 | compileSdkVersion 29 20 | 21 | defaultConfig { 22 | minSdkVersion 21 23 | targetSdkVersion 29 24 | versionCode 1 25 | versionName "1.0" 26 | } 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation fileTree(dir: 'libs', include: ['*.jar']) 37 | testImplementation 'junit:junit:4.13' 38 | implementation 'androidx.appcompat:appcompat:1.1.0' 39 | implementation 'androidx.cardview:cardview:1.0.0' 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 41 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 42 | implementation 'com.google.android.material:material:1.2.0-alpha06' 43 | } 44 | repositories { 45 | mavenCentral() 46 | } 47 | -------------------------------------------------------------------------------- /materialintro/deploy.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | bintray { 3 | user = getBintrayUser() 4 | key = getBintrayKey() 5 | configurations = ['archives'] 6 | pkg { 7 | repo = 'codertainment' 8 | name = 'materialintroview-v2' 9 | userOrg = 'shripal17' 10 | vcsUrl = 'https://github.com/shripal17/MaterialIntroView-v2.git' 11 | licenses = ['Apache-2.0'] 12 | desc = 'Beautiful and highly customisable Android Library to help your users get started with your awesome app!' 13 | version { 14 | name = project.version 15 | released = new Date() 16 | } 17 | } 18 | } 19 | 20 | task cleanForDeployment { 21 | doLast { 22 | logger.lifecycle('MaterialIntroView: Deployment: Cleaning...') 23 | logger.lifecycle('Deleting: ' + project.buildDir) 24 | delete project.buildDir 25 | } 26 | } 27 | 28 | task buildForDeployment { 29 | logger.lifecycle('MaterialIntroView: Deployment: Building...') 30 | shouldRunAfter(cleanForDeployment) 31 | finalizedBy assemble 32 | 33 | doFirst { 34 | android.variantFilter { variant -> 35 | if (variant.buildType.name == 'debug') { 36 | variant.setIgnore(true) 37 | } 38 | } 39 | } 40 | } 41 | 42 | def getBintrayUser() { 43 | if (System.getenv('BINTRAY_USER')) { 44 | logger.lifecycle(System.getenv('BINTRAY_USER')) 45 | return System.getenv('BINTRAY_USER') 46 | } else if (rootProject.file('local.properties').exists()) { 47 | Properties properties = new Properties() 48 | properties.load(rootProject.file('local.properties').newDataInputStream()) 49 | return properties.getProperty('bintray.user') 50 | } else { 51 | logger.lifecycle("Warning: Could not find BINTRAY_USER in environment or local.properties") 52 | } 53 | } 54 | 55 | def getBintrayKey() { 56 | if (System.getenv('BINTRAY_KEY')) { 57 | logger.lifecycle(System.getenv('BINTRAY_KEY')) 58 | return System.getenv('BINTRAY_KEY') 59 | } else if (rootProject.file('local.properties').exists()) { 60 | Properties properties = new Properties() 61 | properties.load(rootProject.file('local.properties').newDataInputStream()) 62 | return properties.getProperty('bintray.key') 63 | } else { 64 | logger.lifecycle("Warning: Could not find BINTRAY_KEY in environment or local.properties") 65 | } 66 | } 67 | 68 | task deployRelease { 69 | logger.lifecycle('MaterialIntroView - Starting Release Deployment') 70 | shouldRunAfter(buildForDeployment) 71 | 72 | dependsOn cleanForDeployment 73 | dependsOn buildForDeployment 74 | finalizedBy bintrayUpload 75 | 76 | doLast { 77 | logger.lifecycle(bintrayUpload.getVersionName()) 78 | bintrayUpload.setVersionName(bintrayUpload.getVersionName()) 79 | bintrayUpload.setUserOrg('shripal17') 80 | bintrayUpload.setRepoName('codertainment') 81 | 82 | logger.lifecycle('Deploying version ' + bintrayUpload.getVersionName() + ' in ' + bintrayUpload.getRepoName()) 83 | } 84 | } -------------------------------------------------------------------------------- /materialintro/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/mertsimsek/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 | -------------------------------------------------------------------------------- /materialintro/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/MaterialIntroConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro 2 | 3 | import android.graphics.Typeface 4 | import android.graphics.drawable.Drawable 5 | import android.view.View 6 | import androidx.annotation.ColorInt 7 | import androidx.annotation.DrawableRes 8 | import androidx.annotation.LayoutRes 9 | import com.codertainment.materialintro.animation.MaterialIntroListener 10 | import com.codertainment.materialintro.sequence.SkipLocation 11 | import com.codertainment.materialintro.shape.Focus 12 | import com.codertainment.materialintro.shape.FocusGravity 13 | import com.codertainment.materialintro.shape.Shape 14 | import com.codertainment.materialintro.shape.ShapeType 15 | import com.codertainment.materialintro.utils.Constants 16 | import com.google.android.material.button.MaterialButton 17 | 18 | data class MaterialIntroConfiguration( 19 | var maskColor: Int = Constants.DEFAULT_MASK_COLOR, 20 | var delayMillis: Long = Constants.DEFAULT_DELAY_MILLIS, 21 | 22 | var isFadeInAnimationEnabled: Boolean = true, 23 | var isFadeOutAnimationEnabled: Boolean = true, 24 | var fadeAnimationDurationMillis: Long = Constants.DEFAULT_FADE_DURATION, 25 | 26 | var focusType: Focus = Focus.ALL, 27 | var focusGravity: FocusGravity = FocusGravity.CENTER, 28 | 29 | var padding: Int = Constants.DEFAULT_TARGET_PADDING, 30 | 31 | var dismissOnTouch: Boolean = false, 32 | 33 | var isInfoEnabled: Boolean = true, 34 | var infoText: CharSequence = "", 35 | var infoTextColor: Int? = null, 36 | var infoTextSize: Float? = null, 37 | var infoTextAlignment: Int = View.TEXT_ALIGNMENT_CENTER, 38 | var infoTextTypeface: Typeface? = null, 39 | @ColorInt 40 | var infoCardBackgroundColor: Int? = null, 41 | 42 | var isHelpIconEnabled: Boolean = true, 43 | @DrawableRes 44 | var helpIconResource: Int? = null, 45 | var helpIconDrawable: Drawable? = null, 46 | @ColorInt 47 | var helpIconColor: Int? = null, 48 | 49 | var infoCustomView: View? = null, 50 | @LayoutRes 51 | var infoCustomViewRes: Int? = null, 52 | 53 | var isDotViewEnabled: Boolean = true, 54 | var isDotAnimationEnabled: Boolean = true, 55 | @ColorInt 56 | var dotIconColor: Int? = null, 57 | 58 | var viewId: String? = null, 59 | var targetView: View? = null, 60 | 61 | var isPerformClick: Boolean = false, 62 | 63 | var showOnlyOnce: Boolean = true, 64 | var userClickAsDisplayed: Boolean = true, 65 | 66 | var shapeType: ShapeType = ShapeType.CIRCLE, 67 | var customShape: Shape? = null, 68 | var materialIntroListener: MaterialIntroListener? = null, 69 | 70 | var skipLocation: SkipLocation = SkipLocation.BOTTOM_LEFT, 71 | var skipText: CharSequence = "Skip", 72 | var skipButtonStyling: MaterialButton.() -> Unit = {} 73 | ) -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/animation/AnimationFactory.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.animation 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorSet 5 | import android.animation.ObjectAnimator 6 | import android.animation.ValueAnimator 7 | import android.view.View 8 | import com.codertainment.materialintro.animation.AnimationListener.OnAnimationEndListener 9 | import com.codertainment.materialintro.animation.AnimationListener.OnAnimationStartListener 10 | 11 | object AnimationFactory { 12 | 13 | /** 14 | * MaterialIntroView will appear on screen with 15 | * fade in animation. Notifies onAnimationStartListener 16 | * when fade in animation is about to start. 17 | * 18 | * @param view 19 | * @param duration 20 | * @param onAnimationStartListener 21 | */ 22 | fun animateFadeIn(view: View?, duration: Long, onAnimationStartListener: OnAnimationStartListener?) { 23 | val objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f) 24 | objectAnimator.duration = duration 25 | objectAnimator.addListener(object : Animator.AnimatorListener { 26 | override fun onAnimationStart(animation: Animator) { 27 | onAnimationStartListener?.onAnimationStart() 28 | } 29 | 30 | override fun onAnimationEnd(animation: Animator) {} 31 | override fun onAnimationCancel(animation: Animator) {} 32 | override fun onAnimationRepeat(animation: Animator) {} 33 | }) 34 | objectAnimator.start() 35 | } 36 | 37 | /** 38 | * MaterialIntroView will disappear from screen with 39 | * fade out animation. Notifies onAnimationEndListener 40 | * when fade out animation is ended. 41 | * 42 | * @param view 43 | * @param duration 44 | * @param onAnimationEndListener 45 | */ 46 | fun animateFadeOut(view: View?, duration: Long, onAnimationEndListener: OnAnimationEndListener?) { 47 | val objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f) 48 | objectAnimator.duration = duration 49 | objectAnimator.addListener(object : Animator.AnimatorListener { 50 | override fun onAnimationStart(animation: Animator) {} 51 | override fun onAnimationEnd(animation: Animator) { 52 | onAnimationEndListener?.onAnimationEnd() 53 | } 54 | 55 | override fun onAnimationCancel(animation: Animator) {} 56 | override fun onAnimationRepeat(animation: Animator) {} 57 | }) 58 | objectAnimator.start() 59 | } 60 | 61 | fun performAnimation(view: View) { 62 | val animatorSet = AnimatorSet() 63 | val scaleX: ValueAnimator = ObjectAnimator.ofFloat(view, View.SCALE_X, 0.6f) 64 | scaleX.repeatCount = ValueAnimator.INFINITE 65 | scaleX.repeatMode = ValueAnimator.REVERSE 66 | scaleX.duration = 1000 67 | val scaleY: ValueAnimator = ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.6f) 68 | scaleY.repeatCount = ValueAnimator.INFINITE 69 | scaleY.repeatMode = ValueAnimator.REVERSE 70 | scaleY.duration = 1000 71 | animatorSet.playTogether(scaleX, scaleY) 72 | animatorSet.start() 73 | } 74 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/animation/AnimationListener.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.animation 2 | 3 | interface AnimationListener { 4 | 5 | /** 6 | * We need to make MaterialIntroView visible 7 | * before fade in animation starts 8 | */ 9 | interface OnAnimationStartListener { 10 | 11 | fun onAnimationStart() 12 | } 13 | 14 | /** 15 | * We need to make MaterialIntroView invisible 16 | * after fade out animation ends. 17 | */ 18 | interface OnAnimationEndListener { 19 | 20 | fun onAnimationEnd() 21 | } 22 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/animation/MaterialIntroListener.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.animation 2 | 3 | interface MaterialIntroListener { 4 | /** 5 | * @param onUserClick is true when MIV has been dismissed through user click, false when MIV was previously displayed and was set as saved 6 | * @param viewId Unique ID of the target view 7 | */ 8 | fun onIntroDone(onUserClick: Boolean, viewId: String) 9 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/prefs/PreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.prefs 2 | 3 | import android.content.Context 4 | import com.codertainment.materialintro.utils.SingletonHolder 5 | 6 | internal class PreferencesManager private constructor(private val context: Context) { 7 | private val sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) 8 | 9 | fun isDisplayed(id: String?): Boolean { 10 | return sharedPreferences.getBoolean(id, false) 11 | } 12 | 13 | fun setDisplayed(id: String?) { 14 | sharedPreferences.edit().putBoolean(id, true).apply() 15 | } 16 | 17 | fun reset(id: String?) { 18 | sharedPreferences.edit().putBoolean(id, false).apply() 19 | } 20 | 21 | fun resetAll() { 22 | sharedPreferences.edit().clear().apply() 23 | } 24 | 25 | companion object : SingletonHolder(::PreferencesManager) { 26 | private const val PREFERENCES_NAME = "material_intro_preferences" 27 | } 28 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/sequence/MaterialIntroSequence.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sequence 2 | 3 | import android.app.Activity 4 | import android.os.Handler 5 | import com.codertainment.materialintro.MaterialIntroConfiguration 6 | import com.codertainment.materialintro.animation.MaterialIntroListener 7 | import com.codertainment.materialintro.utils.materialIntro 8 | import com.codertainment.materialintro.utils.preferencesManager 9 | import com.codertainment.materialintro.view.MaterialIntroView 10 | 11 | /* 12 | * Created by Shripal Jain 13 | * on 11/02/2020 14 | */ 15 | 16 | class MaterialIntroSequence private constructor(private val activity: Activity) { 17 | 18 | companion object { 19 | private val sequences = ArrayList() 20 | 21 | fun getInstance(activity: Activity): MaterialIntroSequence { 22 | val found = sequences.filter { it.activity == activity } 23 | return if (found.isEmpty()) { 24 | val mis = MaterialIntroSequence(activity) 25 | sequences.add(mis) 26 | mis 27 | } else { 28 | found[0] 29 | } 30 | } 31 | } 32 | 33 | private var mivs = ArrayList() 34 | 35 | private var counter = 0 36 | private var isMivShowing = false 37 | private val handler by lazy { 38 | Handler() 39 | } 40 | 41 | /** 42 | * Whether to show the skip button 43 | */ 44 | var showSkip = false 45 | 46 | /** 47 | * If enabled, once the user clicks on skip button, all new MIVs will be skipped too 48 | * If disabled, even after the user clicks on skip button and new MIVs are added after that, for e.g. for another fragment, the new MIVs will be shown 49 | */ 50 | var persistSkip = false 51 | private var isSkipped = false 52 | 53 | private var materialIntroListener = object : MaterialIntroListener { 54 | override fun onIntroDone(onUserClick: Boolean, viewId: String) { 55 | materialIntroSequenceListener?.onProgress(onUserClick, viewId, counter, mivs.size) 56 | isMivShowing = false 57 | if (counter == mivs.size) { 58 | materialIntroSequenceListener?.onCompleted() 59 | } else { 60 | nextIntro() 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * Delay (in ms) for first MIV to be shown 67 | */ 68 | var initialDelay: Long = 500 69 | var materialIntroSequenceListener: MaterialIntroSequenceListener? = null 70 | 71 | fun add(config: MaterialIntroConfiguration) { 72 | val found = mivs.find { it.viewId == config.viewId || it.viewId == config.targetView?.tag?.toString() } 73 | if (found != null && activity.preferencesManager.isDisplayed(config.viewId ?: config.targetView?.tag?.toString())) return 74 | mivs.add(activity.materialIntro(config = config) { 75 | showSkip = this@MaterialIntroSequence.showSkip 76 | delayMillis = if (mivs.isEmpty()) initialDelay else 0 77 | materialIntroListener = this@MaterialIntroSequence.materialIntroListener 78 | skipButton.setOnClickListener { 79 | skip() 80 | } 81 | }) 82 | } 83 | 84 | fun addConfig(func: MaterialIntroConfiguration.() -> Unit) { 85 | add(MaterialIntroConfiguration().apply { 86 | func() 87 | }) 88 | } 89 | 90 | private fun skip() { 91 | isSkipped = true 92 | mivs[counter - 1].dismiss() 93 | for (i in 0 until mivs.size) { 94 | if (mivs[i].showOnlyOnce) { 95 | activity.preferencesManager.setDisplayed(mivs[i].viewId) 96 | } 97 | } 98 | counter = mivs.size 99 | materialIntroSequenceListener?.onCompleted() 100 | } 101 | 102 | fun start() { 103 | if (isSkipped && persistSkip) { 104 | skip() 105 | } else { 106 | if (!isMivShowing) { 107 | nextIntro() 108 | } 109 | } 110 | } 111 | 112 | private fun nextIntro() { 113 | if (isSkipped && persistSkip) { 114 | skip() 115 | } else if (counter < mivs.size) { 116 | isMivShowing = true 117 | handler.post { 118 | mivs[counter++].show(activity) 119 | } 120 | } 121 | } 122 | } 123 | 124 | interface MaterialIntroSequenceListener { 125 | /** 126 | * @param onUserClick if the MIV was dismissed by the user on click or it was auto-dismissed because it was set as displayed 127 | * @param viewId viewId for the dismissed MIV 128 | * @param current index of the dismissed MIV 129 | * @param total Total number of MIVs in the current MaterialIntroSequence 130 | */ 131 | fun onProgress(onUserClick: Boolean, viewId: String, current: Int, total: Int) 132 | 133 | /** 134 | * Called when all MIVs in the current MaterialIntroSequence have been dismissed 135 | */ 136 | fun onCompleted() 137 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/sequence/SkipLocation.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sequence 2 | 3 | enum class SkipLocation { 4 | TOP_RIGHT, 5 | TOP_LEFT, 6 | BOTTOM_LEFT, 7 | BOTTOM_RIGHT 8 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/shape/Circle.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.shape 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Paint 5 | import android.graphics.Point 6 | import com.codertainment.materialintro.target.Target 7 | import kotlin.math.max 8 | import kotlin.math.min 9 | import kotlin.math.pow 10 | 11 | class Circle(target: Target, focus: Focus, focusGravity: FocusGravity, padding: Int) : 12 | Shape(target, focus, focusGravity, padding) { 13 | 14 | private var radius = 0 15 | private var circlePoint: Point 16 | override fun draw(canvas: Canvas, eraser: Paint, padding: Int) { 17 | calculateRadius(padding) 18 | circlePoint = focusPoint 19 | canvas.drawCircle(circlePoint.x.toFloat(), circlePoint.y.toFloat(), radius.toFloat(), eraser) 20 | } 21 | 22 | override fun reCalculateAll() { 23 | calculateRadius(padding) 24 | circlePoint = focusPoint 25 | } 26 | 27 | private fun calculateRadius(padding: Int) { 28 | val side: Int 29 | side = when (focus) { 30 | Focus.MINIMUM -> min(target.rect.width() / 2, target.rect.height() / 2) 31 | Focus.ALL -> max( 32 | target.rect.width() / 2, 33 | target.rect.height() / 2 34 | ) 35 | else -> { 36 | val minSide = min(target.rect.width() / 2, target.rect.height() / 2) 37 | val maxSide = max(target.rect.width() / 2, target.rect.height() / 2) 38 | (minSide + maxSide) / 2 39 | } 40 | } 41 | radius = side + padding 42 | } 43 | 44 | override val point: Point 45 | get() = circlePoint 46 | 47 | override val height: Int 48 | get() = 2 * radius 49 | 50 | override fun isTouchOnFocus(x: Double, y: Double): Boolean { 51 | val xV = point.x 52 | val yV = point.y 53 | val dx = (x - xV).pow(2.0) 54 | val dy = (y - yV).pow(2.0) 55 | return dx + dy <= radius.toDouble().pow(2.0) 56 | } 57 | 58 | init { 59 | circlePoint = focusPoint 60 | calculateRadius(padding) 61 | } 62 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/shape/Focus.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.shape 2 | 3 | /** 4 | * Focus on target with circle 5 | * These enum decides circle radius. 6 | */ 7 | enum class Focus { 8 | 9 | MINIMUM, 10 | NORMAL, ALL 11 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/shape/FocusGravity.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.shape 2 | 3 | /** 4 | * Gravity for focus circle ie. We may want to focus on left of recyclerview item 5 | */ 6 | enum class FocusGravity { 7 | 8 | LEFT, 9 | CENTER, RIGHT 10 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/shape/Rect.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.shape 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Paint 5 | import android.graphics.Point 6 | import android.graphics.RectF 7 | import com.codertainment.materialintro.target.Target 8 | 9 | class Rect : Shape { 10 | private var adjustedRect: RectF? = null 11 | 12 | constructor(target: Target) : super(target) { 13 | calculateAdjustedRect() 14 | } 15 | 16 | constructor(target: Target, focus: Focus) : super(target, focus) { 17 | calculateAdjustedRect() 18 | } 19 | 20 | constructor(target: Target, focus: Focus, focusGravity: FocusGravity, padding: Int) : super(target, focus, focusGravity, padding) { 21 | calculateAdjustedRect() 22 | } 23 | 24 | override fun draw(canvas: Canvas, eraser: Paint, padding: Int) { 25 | adjustedRect?.let { 26 | canvas.drawRoundRect(it, padding.toFloat(), padding.toFloat(), eraser) 27 | } 28 | } 29 | 30 | private fun calculateAdjustedRect() { 31 | val rect = RectF() 32 | rect.set(target.rect) 33 | rect.left -= padding.toFloat() 34 | rect.top -= padding.toFloat() 35 | rect.right += padding.toFloat() 36 | rect.bottom += padding.toFloat() 37 | adjustedRect = rect 38 | } 39 | 40 | override fun reCalculateAll() { 41 | calculateAdjustedRect() 42 | } 43 | 44 | override val point: Point 45 | get() = target.point 46 | 47 | override val height: Int 48 | get() = adjustedRect!!.height().toInt() 49 | 50 | override fun isTouchOnFocus(x: Double, y: Double): Boolean { 51 | return adjustedRect!!.contains(x.toFloat(), y.toFloat()) 52 | } 53 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/shape/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.shape 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Paint 5 | import android.graphics.Point 6 | import com.codertainment.materialintro.target.Target 7 | import com.codertainment.materialintro.utils.Constants 8 | 9 | abstract class Shape @JvmOverloads constructor( 10 | protected var target: Target, 11 | protected var focus: Focus = Focus.MINIMUM, 12 | private val focusGravity: FocusGravity = FocusGravity.CENTER, 13 | protected var padding: Int = Constants.DEFAULT_TARGET_PADDING 14 | ) { 15 | 16 | abstract fun draw(canvas: Canvas, eraser: Paint, padding: Int) 17 | protected val focusPoint get() = when { 18 | focusGravity === FocusGravity.LEFT -> { 19 | val xLeft = target.rect.left + (target.point.x - target.rect.left) / 2 20 | Point(xLeft, target.point.y) 21 | } 22 | focusGravity === FocusGravity.RIGHT -> { 23 | val xRight = target.point.x + (target.rect.right - target.point.x) / 2 24 | Point(xRight, target.point.y) 25 | } 26 | else -> target.point 27 | } 28 | 29 | abstract fun reCalculateAll() 30 | abstract val point: Point 31 | abstract val height: Int 32 | 33 | /** 34 | * Determines if a click is on the shape 35 | * @param x x-axis location of click 36 | * @param y y-axis location of click 37 | * @return true if click is inside shape 38 | */ 39 | abstract fun isTouchOnFocus(x: Double, y: Double): Boolean 40 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/shape/ShapeType.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.shape 2 | 3 | /** 4 | * Allows the target area to be highlighted by either a circle or rectangle 5 | */ 6 | enum class ShapeType { 7 | 8 | CIRCLE, 9 | RECTANGLE 10 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/target/Target.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.target 2 | 3 | import android.graphics.Point 4 | import android.graphics.Rect 5 | import android.view.View 6 | 7 | interface Target { 8 | /** 9 | * Returns center point of target. We can get x and y coordinates using point object 10 | */ 11 | val point: Point 12 | 13 | /** 14 | * Returns Rectangle points of target view 15 | */ 16 | val rect: Rect 17 | 18 | /** 19 | * return target view 20 | */ 21 | val view: View 22 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/target/ViewTarget.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.target 2 | 3 | import android.graphics.Point 4 | import android.graphics.Rect 5 | import android.view.View 6 | 7 | class ViewTarget(override val view: View) : Target { 8 | override val point: Point 9 | get() { 10 | val location = IntArray(2) 11 | view.getLocationInWindow(location) 12 | return Point(location[0] + view.width / 2, location[1] + view.height / 2) 13 | } 14 | 15 | override val rect: Rect 16 | get() { 17 | val location = IntArray(2) 18 | view.getLocationInWindow(location) 19 | return Rect( 20 | location[0], 21 | location[1], 22 | location[0] + view.width, 23 | location[1] + view.height 24 | ) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.utils 2 | 3 | import android.graphics.Color 4 | 5 | object Constants { 6 | const val DEFAULT_MASK_COLOR = 0x70000000 7 | const val DEFAULT_DELAY_MILLIS: Long = 0 8 | const val DEFAULT_FADE_DURATION: Long = 500 9 | const val DEFAULT_TARGET_PADDING = 10 10 | const val DEFAULT_COLOR_TEXTVIEW_INFO = -0x1000000 11 | const val DEFAULT_DOT_SIZE = 55 12 | const val DEFAULT_DOT_ICON_COLOR = Color.WHITE 13 | const val DEFAULT_HELP_ICON_COLOR = Color.BLACK 14 | const val DEFAULT_CARD_BACKGROUND_COLOR = Color.WHITE 15 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/utils/ExtensionHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.utils 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import androidx.fragment.app.Fragment 6 | import com.codertainment.materialintro.MaterialIntroConfiguration 7 | import com.codertainment.materialintro.animation.MaterialIntroListener 8 | import com.codertainment.materialintro.prefs.PreferencesManager 9 | import com.codertainment.materialintro.sequence.MaterialIntroSequence 10 | import com.codertainment.materialintro.sequence.MaterialIntroSequenceListener 11 | import com.codertainment.materialintro.view.MaterialIntroView 12 | 13 | /* 14 | * Created by Shripal Jain 15 | * on 30/04/2020 16 | */ 17 | 18 | /** 19 | * Create an instance of MaterialIntroView with the passed config or properties applied in the lambda 20 | * 21 | * If your Activity implements MaterialIntroListener, it is automatically assigned as materialIntroListener for the current created instance 22 | * 23 | * @param config (Optional) The MaterialIntroConfiguration to be used. For e.g. you can set a global config for your app and override the targetView and/or 24 | * other properties in the lambda function 25 | * 26 | * @param show Indicates whether this instance of MaterialIntroView should show instantly after instantiation and initialisation 27 | */ 28 | fun Activity.materialIntro(show: Boolean = false, config: MaterialIntroConfiguration? = null, func: MaterialIntroView.() -> Unit): MaterialIntroView = 29 | MaterialIntroView(this).apply { 30 | if (this@materialIntro is MaterialIntroListener) { 31 | materialIntroListener = this@materialIntro 32 | } 33 | withConfig(config) 34 | func() 35 | if (show) { 36 | show(this@materialIntro) 37 | } 38 | } 39 | 40 | /** 41 | * Create an instance of MaterialIntroView for a Fragment's activity with the passed config or properties applied in the lambda 42 | * 43 | * If your Activity/Fragment implements MaterialIntroListener, it is automatically assigned as materialIntroListener for the current created instance 44 | 45 | * @param config (Optional) The MaterialIntroConfiguration to be used. For e.g. you can set a global config for your app and override the targetView and/or 46 | * other properties in the lambda function 47 | * 48 | * @param show Indicates whether this instance of MaterialIntroView should show instantly after instantiation and initialisation 49 | */ 50 | fun Fragment.materialIntro(show: Boolean = false, config: MaterialIntroConfiguration? = null, func: MaterialIntroView.() -> Unit): MaterialIntroView? { 51 | return if (activity == null) null 52 | else MaterialIntroView(activity!!).apply { 53 | if (activity is MaterialIntroListener) { 54 | materialIntroListener = activity as MaterialIntroListener 55 | } 56 | if (this@materialIntro is MaterialIntroListener) { 57 | materialIntroListener = this@materialIntro 58 | } 59 | withConfig(config) 60 | func() 61 | if (show) { 62 | show(activity!!) 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * Reset supplied viewId's displayed state 69 | * 70 | * @param viewId the viewId who's displayed state needs to be reset 71 | */ 72 | fun Context.resetMivDisplayed(viewId: String?) { 73 | preferencesManager.reset(viewId) 74 | } 75 | 76 | /** 77 | * Reset all saved displayed states 78 | */ 79 | fun Context.resetAllMivs() { 80 | preferencesManager.resetAll() 81 | } 82 | 83 | internal val Context.preferencesManager 84 | get() = PreferencesManager.getInstance(this) 85 | 86 | 87 | val Activity.materialIntroSequence 88 | get() = MaterialIntroSequence.getInstance(this) 89 | 90 | /** 91 | * Create/get MaterialIntroSequence for the current activity 92 | * 93 | * If your Activity implements MaterialIntroSequenceListener, it is automatically assigned as materialIntroSequenceListener for the current created instance 94 | * 95 | * @param initialDelay delay for the first MIV to be shown 96 | * 97 | * @param materialIntroSequenceListener listener for MaterialIntroSequence events 98 | * 99 | * @param showSkip Whether to show the skip button for MIVs 100 | * 101 | * @param persistSkip If enabled, once the user clicks on skip button, all new MIVs will be skipped too, else even after the user clicks on skip 102 | * button and new MIVs are added after that, for e.g. for another fragment, the new MIVs will be shown 103 | */ 104 | fun Activity.materialIntroSequence( 105 | initialDelay: Long? = null, materialIntroSequenceListener: MaterialIntroSequenceListener? = null, showSkip: Boolean? = null, persistSkip: Boolean? = null, 106 | func: MaterialIntroSequence.() -> Unit 107 | ): MaterialIntroSequence { 108 | return materialIntroSequence.apply { 109 | if (this@materialIntroSequence is MaterialIntroSequenceListener) { 110 | this.materialIntroSequenceListener = this@materialIntroSequence 111 | } 112 | showSkip?.let { 113 | this.showSkip = it 114 | } 115 | persistSkip?.let { 116 | this.persistSkip = it 117 | } 118 | initialDelay?.let { 119 | this.initialDelay = it 120 | } 121 | materialIntroSequenceListener?.let { 122 | this.materialIntroSequenceListener = it 123 | } 124 | func() 125 | start() 126 | } 127 | } 128 | 129 | val Fragment.materialIntroSequence 130 | get() = if (activity != null) MaterialIntroSequence.getInstance(activity!!) else null 131 | 132 | /** 133 | * Create/get MaterialIntroSequence for the current fragment's activity 134 | * 135 | * If your Activity/Fragment implements MaterialIntroSequenceListener, it is automatically assigned as materialIntroSequenceListener for the current created instance 136 | * 137 | * @param initialDelay delay for the first MIV to be shown 138 | * 139 | * @param materialIntroSequenceListener listener for MaterialIntroSequence events 140 | * 141 | * @param showSkip Whether to show the skip button for MIVs 142 | * 143 | * @param persistSkip If enabled, once the user clicks on skip button, all new MIVs will be skipped too, else even after the user clicks on skip 144 | * button and new MIVs are added after that, for e.g. for another fragment, the new MIVs will be shown 145 | */ 146 | fun Fragment.materialIntroSequence( 147 | initialDelay: Long? = null, materialIntroSequenceListener: MaterialIntroSequenceListener? = null, showSkip: Boolean? = null, persistSkip: Boolean? = null, 148 | func: MaterialIntroSequence.() -> Unit 149 | ): MaterialIntroSequence? { 150 | return if (activity == null) null 151 | else materialIntroSequence.apply { 152 | if (activity is MaterialIntroSequenceListener) { 153 | this?.materialIntroSequenceListener = activity as MaterialIntroSequenceListener 154 | } 155 | if (this@materialIntroSequence is MaterialIntroSequenceListener) { 156 | this?.materialIntroSequenceListener = this@materialIntroSequence 157 | } 158 | showSkip?.let { 159 | this?.showSkip = it 160 | } 161 | persistSkip?.let { 162 | this?.persistSkip = it 163 | } 164 | initialDelay?.let { 165 | this?.initialDelay = it 166 | } 167 | materialIntroSequenceListener?.let { 168 | this?.materialIntroSequenceListener = it 169 | } 170 | this?.func() 171 | this?.start() 172 | } 173 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/utils/SingletonHolder.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.utils 2 | 3 | /* 4 | * Created by shrip 5 | * on 15-05-2019 6 | */ 7 | 8 | open class SingletonHolder(creator: (A) -> T) { 9 | private var creator: ((A) -> T)? = creator 10 | 11 | @Volatile 12 | private var instance: T? = null 13 | 14 | fun getInstance(arg: A): T { 15 | val i = instance 16 | if (i != null) { 17 | return i 18 | } 19 | 20 | return synchronized(this) { 21 | val i2 = instance 22 | if (i2 != null) { 23 | i2 24 | } else { 25 | val created = creator!!(arg) 26 | instance = created 27 | creator = null 28 | created 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.utils 2 | 3 | import android.content.res.Resources 4 | 5 | internal object Utils { 6 | fun pxToDp(px: Int): Int { 7 | return (px / Resources.getSystem().displayMetrics.density).toInt() 8 | } 9 | 10 | @JvmStatic 11 | fun dpToPx(dp: Int): Int { 12 | return (dp * Resources.getSystem().displayMetrics.density).toInt() 13 | } 14 | } -------------------------------------------------------------------------------- /materialintro/src/main/java/com/codertainment/materialintro/view/MaterialIntroView.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.view 2 | 3 | import android.annotation.TargetApi 4 | import android.app.Activity 5 | import android.content.Context 6 | import android.graphics.* 7 | import android.graphics.drawable.Drawable 8 | import android.os.Build 9 | import android.os.Handler 10 | import android.util.AttributeSet 11 | import android.util.TypedValue 12 | import android.view.* 13 | import android.widget.ImageView 14 | import android.widget.RelativeLayout 15 | import android.widget.TextView 16 | import androidx.annotation.ColorInt 17 | import androidx.annotation.DrawableRes 18 | import androidx.annotation.LayoutRes 19 | import androidx.cardview.widget.CardView 20 | import com.codertainment.materialintro.MaterialIntroConfiguration 21 | import com.codertainment.materialintro.R 22 | import com.codertainment.materialintro.animation.AnimationFactory 23 | import com.codertainment.materialintro.animation.AnimationListener 24 | import com.codertainment.materialintro.animation.MaterialIntroListener 25 | import com.codertainment.materialintro.sequence.SkipLocation 26 | import com.codertainment.materialintro.shape.* 27 | import com.codertainment.materialintro.shape.Circle 28 | import com.codertainment.materialintro.shape.Rect 29 | import com.codertainment.materialintro.target.Target 30 | import com.codertainment.materialintro.target.ViewTarget 31 | import com.codertainment.materialintro.utils.Constants 32 | import com.codertainment.materialintro.utils.Utils 33 | import com.codertainment.materialintro.utils.preferencesManager 34 | import com.google.android.material.button.MaterialButton 35 | 36 | class MaterialIntroView : RelativeLayout { 37 | 38 | /** 39 | * Mask color 40 | */ 41 | var maskColor = Constants.DEFAULT_MASK_COLOR 42 | 43 | /** 44 | * MaterialIntroView will start 45 | * showing after delayMillis seconds 46 | * passed 47 | */ 48 | var delayMillis = Constants.DEFAULT_DELAY_MILLIS 49 | 50 | /** 51 | * We don't draw MaterialIntroView 52 | * until isReady field set to true 53 | */ 54 | private var isReady = false 55 | 56 | /** 57 | * Show MaterialIntroView 58 | * with fade in animation if 59 | * this is enabled. 60 | */ 61 | var isFadeInAnimationEnabled = true 62 | 63 | /** 64 | * Dismiss MaterialIntroView 65 | * with fade out animation if 66 | * this is enabled. 67 | */ 68 | var isFadeOutAnimationEnabled = true 69 | 70 | /** 71 | * Animation duration 72 | */ 73 | var fadeAnimationDurationMillis = Constants.DEFAULT_FADE_DURATION 74 | 75 | /** 76 | * targetShape focus on target 77 | * and clear circle to focus 78 | */ 79 | private lateinit var targetShape: Shape 80 | 81 | /** 82 | * Focus Type 83 | */ 84 | var focusType = Focus.ALL 85 | 86 | /** 87 | * FocusGravity type 88 | */ 89 | var focusGravity = FocusGravity.CENTER 90 | 91 | /** 92 | * Target View 93 | */ 94 | private lateinit var myTargetView: Target 95 | 96 | /** 97 | * Eraser 98 | */ 99 | private lateinit var eraser: Paint 100 | 101 | /** 102 | * Handler will be used to 103 | * delay MaterialIntroView 104 | */ 105 | private lateinit var myHandler: Handler 106 | 107 | /** 108 | * All views will be drawn to 109 | * this bitmap and canvas then 110 | * bitmap will be drawn to canvas 111 | */ 112 | private var bitmap: Bitmap? = null 113 | private var canvas: Canvas? = null 114 | 115 | /** 116 | * Circle padding 117 | */ 118 | var padding = Constants.DEFAULT_TARGET_PADDING 119 | 120 | /** 121 | * Layout myWidth/myHeight 122 | */ 123 | private var myWidth = 0 124 | private var myHeight = 0 125 | 126 | /** 127 | * Dismiss on touch any where 128 | */ 129 | var dismissOnTouch = false 130 | 131 | /** 132 | * Info card view container 133 | */ 134 | private lateinit var infoView: RelativeLayout 135 | 136 | /** 137 | * Info CardView 138 | */ 139 | private lateinit var infoCardView: CardView 140 | 141 | /** 142 | * Info TextView 143 | */ 144 | private lateinit var infoTextView: TextView 145 | 146 | /** 147 | * Info dialog will be shown 148 | * If this value true 149 | */ 150 | var isInfoEnabled = true 151 | 152 | /** 153 | * Info Text 154 | */ 155 | var infoText: CharSequence = "" 156 | 157 | /** 158 | * Info Text Color 159 | */ 160 | @ColorInt 161 | var infoTextColor: Int? = null 162 | 163 | /** 164 | * Info Text Size in sp 165 | */ 166 | var infoTextSize: Float? = null 167 | 168 | /** 169 | * Info Text Alignment, Use View.TEXT_ALIGNMENT_ 170 | */ 171 | var infoTextAlignment: Int = View.TEXT_ALIGNMENT_CENTER 172 | 173 | /** 174 | * Info Text Custom Typeface 175 | */ 176 | var infoTextTypeface: Typeface? = null 177 | 178 | /** 179 | * Card View Background Color 180 | */ 181 | @ColorInt 182 | var infoCardBackgroundColor: Int? = null 183 | 184 | /** 185 | * Help Dialog Icon 186 | */ 187 | private lateinit var helpIconView: ImageView 188 | 189 | /** 190 | * Help Icon will be shown if this is true 191 | */ 192 | var isHelpIconEnabled = true 193 | 194 | /** 195 | * Drawable resource to set as help icon 196 | */ 197 | @DrawableRes 198 | var helpIconResource: Int? = null 199 | 200 | /** 201 | * Drawable to set as help icon 202 | */ 203 | var helpIconDrawable: Drawable? = null 204 | 205 | /** 206 | * Tint Help Icon 207 | */ 208 | @ColorInt 209 | var helpIconColor: Int? = null 210 | 211 | /** 212 | * Custom View for info card 213 | */ 214 | var infoCustomView: View? = null 215 | 216 | /** 217 | * Layout Resource for custom view 218 | */ 219 | @LayoutRes 220 | var infoCustomViewRes: Int? = null 221 | 222 | /** 223 | * Dot view will appear center of 224 | * cleared target area 225 | */ 226 | private lateinit var dotView: ImageView 227 | 228 | /** 229 | * Dot View will be shown if 230 | * this is true 231 | */ 232 | var isDotViewEnabled = true 233 | 234 | /** 235 | * Dot View animated with zoom in & zoom out animation if this is true 236 | */ 237 | var isDotAnimationEnabled = true 238 | 239 | /** 240 | * Tint Dot Icon 241 | */ 242 | @ColorInt 243 | var dotIconColor: Int? = null 244 | 245 | /** 246 | * Check using this Id whether user learned 247 | * or not. 248 | */ 249 | var viewId: String = "" 250 | 251 | /** 252 | * When layout completed, we set this true 253 | * Otherwise onGlobalLayoutListener stuck on loop. 254 | */ 255 | private var isLayoutCompleted = false 256 | 257 | /** 258 | * Notify user when MaterialIntroView is dismissed 259 | */ 260 | var materialIntroListener: MaterialIntroListener? = null 261 | 262 | /** 263 | * Perform click operation to target 264 | * if this is true 265 | */ 266 | var isPerformClick = false 267 | 268 | /** 269 | * Show MIV only once 270 | */ 271 | var showOnlyOnce = true 272 | 273 | /** 274 | * Mark view as displayed only when user clicks 275 | */ 276 | var userClickAsDisplayed = true 277 | 278 | /** 279 | * Shape of target 280 | */ 281 | var shapeType = ShapeType.CIRCLE 282 | 283 | /** 284 | * Use custom shape 285 | */ 286 | var customShape: Shape? = null 287 | 288 | internal var showSkip = false 289 | 290 | /** 291 | * Location of the skip button 292 | */ 293 | var skipLocation: SkipLocation = SkipLocation.BOTTOM_LEFT 294 | 295 | /** 296 | * Text for skip button 297 | */ 298 | private var skipText: CharSequence = "Skip" 299 | 300 | /** 301 | * Apply custom styling to the skip button 302 | */ 303 | private var skipButtonStyling: MaterialButton.() -> Unit = {} 304 | 305 | lateinit var skipButton: MaterialButton 306 | 307 | private var statusBarHeight = 0 308 | 309 | var skipButtonMargin = Utils.dpToPx(16) 310 | 311 | private var dismissed = false 312 | 313 | constructor(context: Context) : super(context) { 314 | init() 315 | } 316 | 317 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 318 | init() 319 | } 320 | 321 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 322 | init() 323 | } 324 | 325 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 326 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { 327 | init() 328 | } 329 | 330 | private fun init() { 331 | setWillNotDraw(false) 332 | visibility = INVISIBLE 333 | /** 334 | * initialize objects 335 | */ 336 | skipButton = MaterialButton(context) 337 | myHandler = Handler() 338 | eraser = Paint().apply { 339 | color = -0x1 340 | xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) 341 | flags = Paint.ANTI_ALIAS_FLAG 342 | } 343 | fitsSystemWindows = true 344 | } 345 | 346 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 347 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 348 | myWidth = measuredWidth 349 | myHeight = measuredHeight 350 | } 351 | 352 | override fun onDraw(canvas: Canvas) { 353 | super.onDraw(canvas) 354 | if (!isReady) return 355 | if (bitmap == null) { 356 | bitmap?.recycle() 357 | bitmap = Bitmap.createBitmap(myWidth, myHeight, Bitmap.Config.ARGB_8888) 358 | this.canvas = Canvas(bitmap!!) 359 | } 360 | /** 361 | * Draw mask 362 | */ 363 | this.canvas?.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) 364 | this.canvas?.drawColor(maskColor) 365 | /** 366 | * Clear focus area 367 | */ 368 | targetShape.draw(this.canvas!!, eraser, padding) 369 | canvas.drawBitmap(bitmap!!, 0f, 0f, null) 370 | } 371 | 372 | /** 373 | * Perform click operation when user 374 | * touches on target circle. 375 | * 376 | * @param event 377 | * @return 378 | */ 379 | override fun onTouchEvent(event: MotionEvent): Boolean { 380 | val xT = event.x 381 | val yT = event.y 382 | val isTouchOnFocus = targetShape.isTouchOnFocus(xT.toDouble(), yT.toDouble()) 383 | when (event.action) { 384 | MotionEvent.ACTION_DOWN -> { 385 | if (isTouchOnFocus && isPerformClick) { 386 | myTargetView.view.apply { 387 | isPressed = true 388 | invalidate() 389 | } 390 | } 391 | return true 392 | } 393 | MotionEvent.ACTION_UP -> { 394 | if (isTouchOnFocus || dismissOnTouch) { 395 | dismiss() 396 | } 397 | if (isTouchOnFocus && isPerformClick) { 398 | myTargetView.view.apply { 399 | performClick() 400 | isPressed = true 401 | invalidate() 402 | isPressed = false 403 | invalidate() 404 | } 405 | } 406 | return true 407 | } 408 | } 409 | return super.onTouchEvent(event) 410 | } 411 | 412 | var targetView 413 | get() = myTargetView.view 414 | set(value) { 415 | if (value.tag is String) { 416 | value.tag?.toString()?.let { 417 | viewId = it 418 | } 419 | } 420 | myTargetView = ViewTarget(value) 421 | } 422 | 423 | /** 424 | * Shows material view with fade in 425 | * animation 426 | * 427 | * @param activity 428 | */ 429 | fun show(activity: Activity) { 430 | if (context.preferencesManager.isDisplayed(viewId)) { 431 | materialIntroListener?.onIntroDone(false, viewId) 432 | return 433 | } 434 | if (!::targetShape.isInitialized) { 435 | targetShape = when { 436 | customShape != null -> { 437 | customShape!! 438 | } 439 | shapeType == ShapeType.CIRCLE -> { 440 | Circle(myTargetView, focusType, focusGravity, padding) 441 | } 442 | else -> { 443 | Rect(myTargetView, focusType, focusGravity, padding) 444 | } 445 | } 446 | } 447 | 448 | if (isInfoEnabled) { 449 | infoView = LayoutInflater.from(context).inflate(R.layout.material_intro_card, null) as RelativeLayout 450 | infoCardView = infoView.findViewById(R.id.info_card_view) 451 | infoTextView = infoView.findViewById(R.id.info_text) 452 | helpIconView = infoView.findViewById(R.id.info_icon) 453 | if (infoCustomViewRes != null || infoCustomView != null) { 454 | infoCustomViewRes?.let { 455 | infoCustomView = LayoutInflater.from(context).inflate(it, infoCardView, false) 456 | } 457 | infoCardView.removeAllViews() 458 | infoCardView.addView(infoCustomView) 459 | } else { 460 | infoCardBackgroundColor?.let { 461 | infoCardView.setCardBackgroundColor(it) 462 | } 463 | infoTextView.text = infoText 464 | infoTextView.textAlignment = infoTextAlignment 465 | infoTextView.typeface = infoTextTypeface 466 | 467 | infoTextSize?.let { 468 | infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, it) 469 | } 470 | 471 | infoTextColor?.let { 472 | infoTextView.setTextColor(it) 473 | } 474 | if (isHelpIconEnabled) { 475 | helpIconResource?.let { 476 | helpIconView.setImageResource(it) 477 | } 478 | helpIconDrawable?.let { 479 | helpIconView.setImageDrawable(it) 480 | } 481 | helpIconColor?.let { 482 | helpIconView.setColorFilter(it) 483 | } 484 | } 485 | } 486 | } 487 | 488 | if (isDotViewEnabled) { 489 | dotView = LayoutInflater.from(context).inflate(R.layout.dot_view, null) as ImageView 490 | dotView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) 491 | dotIconColor?.let { 492 | dotView.setColorFilter(it, PorterDuff.Mode.SRC_IN) 493 | } 494 | } 495 | 496 | if (showSkip) { 497 | val rect = android.graphics.Rect() 498 | activity.window.decorView.getWindowVisibleDisplayFrame(rect) 499 | statusBarHeight = rect.top 500 | } 501 | 502 | viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 503 | override fun onGlobalLayout() { 504 | targetShape.reCalculateAll() 505 | if (targetShape.point.y != 0 && !isLayoutCompleted) { 506 | if (isInfoEnabled) { 507 | setInfoLayout() 508 | } 509 | if (isDotViewEnabled) { 510 | setDotViewLayout() 511 | } 512 | if (showSkip) { 513 | setSkipButtonLayout() 514 | } 515 | removeOnGlobalLayoutListener(this@MaterialIntroView, this) 516 | } 517 | } 518 | }) 519 | 520 | (activity.window.decorView as ViewGroup).addView(this) 521 | isReady = true 522 | myHandler.postDelayed( 523 | { 524 | if (isFadeInAnimationEnabled) 525 | AnimationFactory.animateFadeIn( 526 | this@MaterialIntroView, 527 | fadeAnimationDurationMillis, 528 | object : AnimationListener.OnAnimationStartListener { 529 | override fun onAnimationStart() { 530 | visibility = VISIBLE 531 | } 532 | }) 533 | else 534 | visibility = VISIBLE 535 | }, delayMillis 536 | ) 537 | if (showOnlyOnce && !userClickAsDisplayed) { 538 | context.preferencesManager.setDisplayed(viewId) 539 | } 540 | } 541 | 542 | /** 543 | * Dismiss Material Intro View 544 | */ 545 | fun dismiss() { 546 | //prevent from firing dismiss() method multiple times when quickly clicking the layer 547 | if (dismissed) { 548 | return 549 | } 550 | dismissed = true 551 | if (showOnlyOnce && userClickAsDisplayed) { 552 | context.preferencesManager.setDisplayed(viewId) 553 | } 554 | if (isFadeOutAnimationEnabled) { 555 | AnimationFactory.animateFadeOut(this, fadeAnimationDurationMillis, object : AnimationListener.OnAnimationEndListener { 556 | override fun onAnimationEnd() { 557 | removeSelf() 558 | } 559 | }) 560 | } else { 561 | removeSelf() 562 | } 563 | } 564 | 565 | private fun removeSelf() { 566 | visibility = GONE 567 | removeMaterialView() 568 | materialIntroListener?.onIntroDone(true, viewId) 569 | } 570 | 571 | private fun removeMaterialView() { 572 | if (parent != null) 573 | (parent as ViewGroup).removeView(this) 574 | } 575 | 576 | /** 577 | * locate info card view above/below the 578 | * circle. If circle's Y coordinate is bigger than 579 | * Y coordinate of root view, then locate cardview 580 | * above the circle. Otherwise locate below. 581 | */ 582 | private fun setInfoLayout() { 583 | myHandler.post { 584 | isLayoutCompleted = true 585 | infoParent?.removeView(infoView) 586 | val infoDialogParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.FILL_PARENT) 587 | if (targetShape.point.y < myHeight / 2) { 588 | infoView.gravity = Gravity.TOP 589 | infoDialogParams.setMargins( 590 | 0, 591 | targetShape.point.y + targetShape.height / 2, 592 | 0, 593 | 0 594 | ) 595 | } else { 596 | infoView.gravity = Gravity.BOTTOM 597 | infoDialogParams.setMargins( 598 | 0, 599 | 0, 600 | 0, 601 | myHeight - (targetShape.point.y + targetShape.height / 2) + 2 * targetShape.height / 2 602 | ) 603 | } 604 | infoView.layoutParams = infoDialogParams 605 | infoView.postInvalidate() 606 | addView(infoView) 607 | if (!isHelpIconEnabled) { 608 | helpIconView.visibility = GONE 609 | } 610 | infoView.visibility = VISIBLE 611 | } 612 | } 613 | 614 | private fun setDotViewLayout() { 615 | myHandler.post { 616 | dotParent?.removeView(dotView) 617 | val dotViewLayoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) 618 | dotViewLayoutParams.height = Utils.dpToPx(Constants.DEFAULT_DOT_SIZE) 619 | dotViewLayoutParams.width = Utils.dpToPx(Constants.DEFAULT_DOT_SIZE) 620 | dotViewLayoutParams.setMargins( 621 | targetShape.point.x - (dotViewLayoutParams.width / 2), 622 | targetShape.point.y - (dotViewLayoutParams.height / 2), 623 | 0, 624 | 0 625 | ) 626 | dotView.layoutParams = dotViewLayoutParams 627 | dotView.postInvalidate() 628 | addView(dotView) 629 | dotView.visibility = VISIBLE 630 | if (isDotAnimationEnabled) { 631 | AnimationFactory.performAnimation(dotView) 632 | } 633 | } 634 | } 635 | 636 | private fun setSkipButtonLayout() { 637 | myHandler.post { 638 | val s = Point() 639 | skipButton.text = skipText 640 | skipButton.apply { 641 | skipButtonStyling() 642 | } 643 | display.getSize(s) 644 | skipButton.measure(s.x, s.y) 645 | val skipButtonLayoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) 646 | var topMargin = 0 647 | var leftMargin = 0 648 | 649 | val defaultMargin = skipButtonMargin 650 | 651 | when (skipLocation) { 652 | SkipLocation.BOTTOM_LEFT -> { 653 | leftMargin = defaultMargin 654 | topMargin = s.y - skipButton.measuredHeight - defaultMargin 655 | } 656 | SkipLocation.BOTTOM_RIGHT -> { 657 | leftMargin = s.x - skipButton.measuredWidth - defaultMargin 658 | topMargin = s.y - skipButton.measuredHeight - defaultMargin 659 | } 660 | SkipLocation.TOP_LEFT -> { 661 | leftMargin = defaultMargin 662 | topMargin = defaultMargin + statusBarHeight 663 | } 664 | SkipLocation.TOP_RIGHT -> { 665 | leftMargin = s.x - skipButton.measuredWidth - defaultMargin 666 | topMargin = defaultMargin + statusBarHeight 667 | } 668 | } 669 | skipButtonLayoutParams.setMargins(leftMargin, topMargin, 0, 0) 670 | skipButton.layoutParams = skipButtonLayoutParams 671 | skipButton.postInvalidate() 672 | addView(skipButton) 673 | } 674 | } 675 | 676 | fun withConfig(config: MaterialIntroConfiguration?) { 677 | if (config == null) return 678 | this.maskColor = config.maskColor 679 | 680 | this.delayMillis = config.delayMillis 681 | 682 | this.isFadeInAnimationEnabled = config.isFadeInAnimationEnabled 683 | this.isFadeOutAnimationEnabled = config.isFadeOutAnimationEnabled 684 | this.fadeAnimationDurationMillis = config.fadeAnimationDurationMillis 685 | 686 | this.focusType = config.focusType 687 | this.focusGravity = config.focusGravity 688 | 689 | this.padding = config.padding 690 | 691 | this.dismissOnTouch = config.dismissOnTouch 692 | 693 | this.isInfoEnabled = config.isInfoEnabled 694 | this.infoText = config.infoText 695 | this.infoTextColor = config.infoTextColor 696 | this.infoTextSize = config.infoTextSize 697 | this.infoTextAlignment = config.infoTextAlignment 698 | this.infoTextTypeface = config.infoTextTypeface 699 | this.infoCardBackgroundColor = config.infoCardBackgroundColor 700 | 701 | this.isHelpIconEnabled = config.isHelpIconEnabled 702 | this.helpIconResource = config.helpIconResource 703 | this.helpIconDrawable = config.helpIconDrawable 704 | this.helpIconColor = config.helpIconColor 705 | 706 | this.infoCustomView = config.infoCustomView 707 | this.infoCustomViewRes = config.infoCustomViewRes 708 | 709 | this.isDotViewEnabled = config.isDotViewEnabled 710 | this.isDotAnimationEnabled = config.isDotAnimationEnabled 711 | this.dotIconColor = config.dotIconColor 712 | 713 | config.viewId?.let { 714 | this.viewId = it 715 | } 716 | config.targetView?.let { 717 | this.targetView = it 718 | } 719 | 720 | this.isPerformClick = config.isPerformClick 721 | 722 | this.showOnlyOnce = config.showOnlyOnce 723 | this.userClickAsDisplayed = config.userClickAsDisplayed 724 | 725 | this.shapeType = config.shapeType 726 | this.customShape = config.customShape 727 | 728 | this.materialIntroListener = config.materialIntroListener 729 | 730 | this.skipLocation = config.skipLocation 731 | this.skipText = config.skipText 732 | this.skipButtonStyling = config.skipButtonStyling 733 | } 734 | 735 | private val infoParent 736 | get() = infoView.parent as ViewGroup? 737 | 738 | private val dotParent 739 | get() = dotView.parent as ViewGroup? 740 | 741 | companion object { 742 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 743 | fun removeOnGlobalLayoutListener(v: View, listener: ViewTreeObserver.OnGlobalLayoutListener) { 744 | v.viewTreeObserver.removeOnGlobalLayoutListener(listener) 745 | } 746 | } 747 | } 748 | -------------------------------------------------------------------------------- /materialintro/src/main/res/drawable/dot.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /materialintro/src/main/res/drawable/ic_help_outline_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /materialintro/src/main/res/layout/dot_view.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /materialintro/src/main/res/layout/material_intro_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 22 | 23 | 32 | 33 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /materialintro/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24dp 4 | 16sp 5 | -------------------------------------------------------------------------------- /materialintro/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | materialintro 3 | 4 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | 8 | defaultConfig { 9 | applicationId "com.codertainment.materialintro.sample" 10 | minSdkVersion 21 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 26 | testImplementation 'junit:junit:4.13' 27 | implementation 'androidx.appcompat:appcompat:1.1.0' 28 | implementation 'com.google.android.material:material:1.2.0-alpha06' 29 | implementation project(':materialintro') 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 32 | implementation "org.jetbrains.anko:anko:0.10.8" 33 | } 34 | repositories { 35 | mavenCentral() 36 | } 37 | -------------------------------------------------------------------------------- /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 /Users/mertsimsek/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 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/codertainment/materialintro/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample; 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 | 2 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.Menu 6 | import android.view.MenuItem 7 | import android.view.View 8 | import androidx.appcompat.app.ActionBarDrawerToggle 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.appcompat.widget.Toolbar 11 | import androidx.core.view.GravityCompat 12 | import androidx.drawerlayout.widget.DrawerLayout 13 | import com.codertainment.materialintro.sample.fragment.* 14 | import com.codertainment.materialintro.sample.fragment.sequence.SequenceParentFragment 15 | import com.codertainment.materialintro.sequence.MaterialIntroSequence 16 | import com.google.android.material.navigation.NavigationView 17 | import kotlinx.android.synthetic.main.activity_main.* 18 | 19 | class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_main) 24 | val toolbar = findViewById(R.id.toolbar) as Toolbar 25 | setSupportActionBar(toolbar) 26 | if (savedInstanceState == null) supportFragmentManager 27 | .beginTransaction() 28 | .add(R.id.container, MainFragment()) 29 | .commit() 30 | val drawer = findViewById(R.id.drawer_layout) as DrawerLayout 31 | val toggle = ActionBarDrawerToggle( 32 | this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close 33 | ) 34 | drawer.setDrawerListener(toggle) 35 | toggle.syncState() 36 | val navigationView = findViewById(R.id.nav_view) as NavigationView 37 | navigationView.setNavigationItemSelectedListener(this) 38 | } 39 | 40 | override fun onBackPressed() { 41 | val drawer = findViewById(R.id.drawer_layout) as DrawerLayout 42 | if (drawer.isDrawerOpen(GravityCompat.START)) { 43 | drawer.closeDrawer(GravityCompat.START) 44 | } else { 45 | super.onBackPressed() 46 | } 47 | } 48 | 49 | override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate the menu; this adds items to the action bar if it is present. 50 | menuInflater.inflate(R.menu.main, menu) 51 | return true 52 | } 53 | 54 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 55 | val id = item.itemId 56 | return if (id == R.id.action_settings) { 57 | true 58 | } else super.onOptionsItemSelected(item) 59 | } 60 | 61 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 62 | val fragment = when (item.itemId) { 63 | R.id.nav_demo -> MainFragment() 64 | R.id.nav_gravity -> GravityFragment() 65 | R.id.nav_focus -> FocusFragment() 66 | R.id.nav_recyclerview -> RecyclerViewFragment() 67 | R.id.nav_custom_view -> CustomInfoViewFragment() 68 | R.id.nav_sequence -> SequenceParentFragment() 69 | else -> null 70 | } 71 | if (item.itemId == R.id.nav_toolbar) { 72 | startActivity(Intent(applicationContext, ToolbarMenuItemActivity::class.java)) 73 | } else if (fragment != null) { 74 | supportFragmentManager.beginTransaction().replace(R.id.container, fragment).commit() 75 | } 76 | drawer_layout.closeDrawer(GravityCompat.START) 77 | return true 78 | } 79 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/ToolbarMenuItemActivity.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample 2 | 3 | import android.content.Intent 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import android.os.Handler 7 | import android.view.Menu 8 | import android.view.MenuItem 9 | import android.view.View 10 | import androidx.appcompat.app.ActionBarDrawerToggle 11 | import androidx.appcompat.app.AppCompatActivity 12 | import androidx.core.view.GravityCompat 13 | import com.codertainment.materialintro.sample.fragment.* 14 | import com.codertainment.materialintro.sequence.MaterialIntroSequenceListener 15 | import com.codertainment.materialintro.utils.materialIntroSequence 16 | import com.google.android.material.navigation.NavigationView 17 | import kotlinx.android.synthetic.main.activity_toolbar.* 18 | import org.jetbrains.anko.toast 19 | 20 | /** 21 | * This activity demonstrates how to implement Material introView on ToolBar MenuItems with custom colors and sequence 22 | * 23 | */ 24 | class ToolbarMenuItemActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, MaterialIntroSequenceListener { 25 | 26 | private lateinit var shareAction: View 27 | private lateinit var helpAction: View 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_toolbar) 32 | setSupportActionBar(toolbar) 33 | 34 | val toggle = ActionBarDrawerToggle(this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) 35 | drawer_layout.setDrawerListener(toggle) 36 | toggle.syncState() 37 | nav_view.setNavigationItemSelectedListener(this) 38 | } 39 | 40 | override fun onBackPressed() { 41 | if (drawer_layout.isDrawerOpen(GravityCompat.START)) { 42 | drawer_layout.closeDrawer(GravityCompat.START) 43 | } else { 44 | super.onBackPressed() 45 | } 46 | } 47 | 48 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 49 | menuInflater.inflate(R.menu.main, menu) 50 | Handler().post { 51 | helpAction = findViewById(R.id.help) 52 | shareAction = findViewById(R.id.share) 53 | materialIntroSequence(500) { 54 | addConfig { 55 | viewId = MENU_SEARCH_ID_TAG 56 | infoText = getString(R.string.guide_setup_profile) 57 | infoCardBackgroundColor = Color.GREEN 58 | helpIconColor = Color.BLUE 59 | infoTextColor = Color.BLACK 60 | dotIconColor = Color.RED 61 | targetView = findViewById(R.id.search) 62 | } 63 | addConfig { 64 | viewId = MENU_SHARED_ID_TAG 65 | infoText = getString(R.string.guide_setup_profile) 66 | infoCardBackgroundColor = Color.GREEN 67 | helpIconColor = Color.BLUE 68 | infoTextColor = Color.BLACK 69 | dotIconColor = Color.RED 70 | targetView = shareAction 71 | } 72 | addConfig { 73 | viewId = MENU_ABOUT_ID_TAG 74 | infoText = getString(R.string.guide_setup_profile) 75 | infoCardBackgroundColor = Color.GREEN 76 | helpIconColor = Color.BLUE 77 | infoTextColor = Color.BLACK 78 | dotIconColor = Color.RED 79 | targetView = helpAction 80 | } 81 | } 82 | } 83 | return true 84 | } 85 | 86 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 87 | val id = item.itemId 88 | return if (id == R.id.action_settings) { 89 | true 90 | } else super.onOptionsItemSelected(item) 91 | } 92 | 93 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 94 | val fragment = when (item.itemId) { 95 | R.id.nav_demo -> MainFragment() 96 | R.id.nav_gravity -> GravityFragment() 97 | R.id.nav_focus -> FocusFragment() 98 | R.id.nav_recyclerview -> RecyclerViewFragment() 99 | R.id.nav_custom_view -> CustomInfoViewFragment() 100 | else -> null 101 | } 102 | if (item.itemId == R.id.nav_toolbar) { 103 | startActivity(Intent(applicationContext, ToolbarMenuItemActivity::class.java)) 104 | } else if (fragment != null) { 105 | supportFragmentManager.beginTransaction().replace(R.id.container, fragment).commit() 106 | } 107 | drawer_layout.closeDrawer(GravityCompat.START) 108 | return true 109 | } 110 | 111 | companion object { 112 | private const val MENU_SHARED_ID_TAG = "menuSharedIdTag" 113 | private const val MENU_ABOUT_ID_TAG = "menuAboutIdTag" 114 | private const val MENU_SEARCH_ID_TAG = "menuSearchIdTag" 115 | } 116 | 117 | override fun onProgress(onUserClick: Boolean, viewId: String, current: Int, total: Int) { 118 | when (viewId) { 119 | MENU_SHARED_ID_TAG -> toast("Share done") 120 | MENU_ABOUT_ID_TAG -> toast("About done") 121 | MENU_SEARCH_ID_TAG -> toast("Search done") 122 | } 123 | } 124 | 125 | override fun onCompleted() { 126 | toast("All done") 127 | } 128 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/adapter/RecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.codertainment.materialintro.sample.R 10 | import com.codertainment.materialintro.sample.adapter.RecyclerViewAdapter.ExampleViewHolder 11 | import com.codertainment.materialintro.sample.model.Song 12 | 13 | class RecyclerViewAdapter(private val data: ArrayList) : RecyclerView.Adapter() { 14 | 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder = 16 | ExampleViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_card, parent, false)) 17 | 18 | override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) = holder.bind(data[position]) 19 | 20 | override fun getItemCount(): Int = data.size 21 | 22 | inner class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 23 | var coverImage: ImageView = itemView.findViewById(R.id.cover_photo) 24 | var coverName: TextView = itemView.findViewById(R.id.cover_name) 25 | var singerName: TextView = itemView.findViewById(R.id.singer_name) 26 | 27 | fun bind(item: Song) { 28 | coverImage.setImageResource(item.songArt) 29 | coverName.text = item.songName 30 | singerName.text = item.singerName 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/CustomInfoViewFragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.ImageView 9 | import androidx.fragment.app.Fragment 10 | import com.codertainment.materialintro.sample.R 11 | import com.codertainment.materialintro.sequence.SkipLocation 12 | import com.codertainment.materialintro.shape.ShapeType 13 | import com.codertainment.materialintro.utils.materialIntroSequence 14 | import com.google.android.material.button.MaterialButton 15 | import kotlinx.android.synthetic.main.fragment_custom_info_view.* 16 | 17 | class CustomInfoViewFragment : Fragment() { 18 | 19 | private val mSkipButtonStyling: MaterialButton.() -> Unit = { 20 | setBackgroundColor(Color.parseColor("#009688")) 21 | setIconResource(R.drawable.ic_skip) 22 | } 23 | 24 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 25 | // Inflate the layout for this fragment 26 | return inflater.inflate(R.layout.fragment_custom_info_view, container, false) 27 | } 28 | 29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 30 | super.onViewCreated(view, savedInstanceState) 31 | 32 | materialIntroSequence(500, showSkip = true) { 33 | // remove listener 34 | materialIntroSequenceListener = null 35 | addConfig { 36 | infoCustomViewRes = R.layout.info_custom_view 37 | showOnlyOnce = false 38 | targetView = custom_view_res_button 39 | skipLocation = SkipLocation.BOTTOM_RIGHT 40 | dotIconColor = Color.argb(200, 255, 0, 0) 41 | skipButtonStyling = mSkipButtonStyling 42 | } 43 | addConfig { 44 | isDotViewEnabled = false 45 | isDotAnimationEnabled = false 46 | infoCustomView = ImageView(requireActivity()).apply { setImageResource(R.drawable.diamond) } 47 | targetView = custom_view_button 48 | showOnlyOnce = false 49 | shapeType = ShapeType.RECTANGLE 50 | skipLocation = SkipLocation.TOP_RIGHT 51 | skipButtonStyling = mSkipButtonStyling 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/FocusFragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.codertainment.materialintro.MaterialIntroConfiguration 9 | import com.codertainment.materialintro.sample.R 10 | import com.codertainment.materialintro.sequence.MaterialIntroSequenceListener 11 | import com.codertainment.materialintro.shape.Focus 12 | import com.codertainment.materialintro.utils.materialIntroSequence 13 | import kotlinx.android.synthetic.main.fragment_focus.* 14 | import org.jetbrains.anko.support.v4.toast 15 | 16 | class FocusFragment : Fragment(), MaterialIntroSequenceListener { 17 | 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 19 | return inflater.inflate(R.layout.fragment_focus, container, false) 20 | } 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | materialIntroSequence(1000) { 25 | add(materialIntroConfig.apply { 26 | focusType = Focus.ALL 27 | infoText = "This intro view focuses on whole of target view" 28 | targetView = button_focus_1 29 | }) 30 | add(materialIntroConfig.apply { 31 | focusType = Focus.MINIMUM 32 | infoText = "This intro view focuses with minimum size" 33 | targetView = button_focus_2 34 | }) 35 | add(materialIntroConfig.apply { 36 | focusType = Focus.NORMAL 37 | infoText = "This intro view focuses with normal size (average of MIN and ALL)" 38 | targetView = button_focus_3 39 | }) 40 | } 41 | } 42 | 43 | private val materialIntroConfig 44 | get() = MaterialIntroConfiguration().apply { 45 | isPerformClick = true 46 | } 47 | 48 | override fun onProgress(onUserClick: Boolean, viewId: String, current: Int, total: Int) { 49 | if (onUserClick) toast(viewId) 50 | } 51 | 52 | override fun onCompleted() { 53 | toast("Focus tutorial done") 54 | } 55 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/GravityFragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.codertainment.materialintro.MaterialIntroConfiguration 9 | import com.codertainment.materialintro.sample.R 10 | import com.codertainment.materialintro.sequence.MaterialIntroSequenceListener 11 | import com.codertainment.materialintro.shape.Focus 12 | import com.codertainment.materialintro.shape.FocusGravity 13 | import com.codertainment.materialintro.utils.materialIntroSequence 14 | import kotlinx.android.synthetic.main.fragment_gravity.* 15 | import org.jetbrains.anko.support.v4.toast 16 | 17 | class GravityFragment : Fragment(), MaterialIntroSequenceListener { 18 | 19 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 20 | return inflater.inflate(R.layout.fragment_gravity, container, false) 21 | } 22 | 23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 24 | super.onViewCreated(view, savedInstanceState) 25 | 26 | materialIntroSequence(500) { 27 | add(materialIntroConfig.apply { 28 | targetView = my_card 29 | infoText = "This intro focuses on RIGHT of target with text alignment" 30 | focusGravity = FocusGravity.RIGHT 31 | infoTextAlignment = View.TEXT_ALIGNMENT_VIEW_END 32 | }) 33 | add(materialIntroConfig.apply { 34 | targetView = my_card2 35 | infoText = "This intro focuses on CENTER of target" 36 | focusGravity = FocusGravity.CENTER 37 | infoTextAlignment = View.TEXT_ALIGNMENT_CENTER 38 | }) 39 | add(materialIntroConfig.apply { 40 | targetView = my_card3 41 | infoText = "This intro focuses on LEFT of target with text alignment" 42 | focusGravity = FocusGravity.LEFT 43 | infoTextAlignment = View.TEXT_ALIGNMENT_VIEW_START 44 | }) 45 | } 46 | } 47 | 48 | private val materialIntroConfig 49 | get() = MaterialIntroConfiguration().apply { 50 | focusType = Focus.MINIMUM 51 | isPerformClick = true 52 | } 53 | 54 | override fun onProgress(onUserClick: Boolean, viewId: String, current: Int, total: Int) { 55 | if (onUserClick) toast(viewId) 56 | } 57 | 58 | override fun onCompleted() { 59 | toast("Gravity tutorial done") 60 | } 61 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.codertainment.materialintro.sample.R 9 | import com.codertainment.materialintro.shape.Focus 10 | import com.codertainment.materialintro.shape.ShapeType 11 | import com.codertainment.materialintro.utils.materialIntro 12 | import com.codertainment.materialintro.utils.resetAllMivs 13 | import kotlinx.android.synthetic.main.content_main.* 14 | 15 | class MainFragment : Fragment() { 16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 17 | return inflater.inflate(R.layout.content_main, container, false) 18 | } 19 | 20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 21 | super.onViewCreated(view, savedInstanceState) 22 | button_reset_all.setOnClickListener { 23 | if (activity == null) return@setOnClickListener 24 | requireContext().resetAllMivs() 25 | } 26 | showIntro(my_card, INTRO_CARD, "This is the info card! Hello There. You can set this text!") 27 | } 28 | 29 | 30 | private fun showIntro(view: View, usageId: String, text: String) { 31 | materialIntro(true) { 32 | focusType = Focus.MINIMUM 33 | isPerformClick = true 34 | infoText = text 35 | targetView = view 36 | shapeType = ShapeType.RECTANGLE 37 | viewId = usageId 38 | } 39 | } 40 | 41 | companion object { 42 | private const val INTRO_CARD = "material_intro" 43 | } 44 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/RecyclerViewFragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.Toast 9 | import androidx.fragment.app.Fragment 10 | import androidx.recyclerview.widget.GridLayoutManager 11 | import com.codertainment.materialintro.animation.MaterialIntroListener 12 | import com.codertainment.materialintro.sample.R 13 | import com.codertainment.materialintro.sample.adapter.RecyclerViewAdapter 14 | import com.codertainment.materialintro.sample.model.Song 15 | import com.codertainment.materialintro.shape.Focus 16 | import com.codertainment.materialintro.utils.materialIntro 17 | import kotlinx.android.synthetic.main.fragment_recyclerview.* 18 | 19 | class RecyclerViewFragment : Fragment(), MaterialIntroListener { 20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 21 | return inflater.inflate(R.layout.fragment_recyclerview, container, false) 22 | } 23 | 24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 25 | super.onViewCreated(view, savedInstanceState) 26 | initializeRecyclerView() 27 | Handler().postDelayed({ showMaterialIntro() }, 2000) 28 | } 29 | 30 | private fun showMaterialIntro() { 31 | materialIntro(true) { 32 | focusType = Focus.MINIMUM 33 | infoText = "This intro focuses on RecyclerView item" 34 | targetView = recycler_view.getChildAt(2) 35 | viewId = INTRO_CARD 36 | } 37 | } 38 | 39 | private fun getSongs(): ArrayList { 40 | val list = ArrayList() 41 | val song = Song("Diamonds", R.drawable.diamond, "Rihanna") 42 | for (i in 0..9) { 43 | list.add(song) 44 | } 45 | return list 46 | } 47 | 48 | private fun initializeRecyclerView() { 49 | if (context == null) return 50 | recycler_view.apply { 51 | layoutManager = GridLayoutManager(activity, 2) 52 | adapter = RecyclerViewAdapter(getSongs()) 53 | } 54 | } 55 | 56 | override fun onIntroDone(onUserClick: Boolean, viewId: String) { 57 | if (viewId == INTRO_CARD && onUserClick) { 58 | Toast.makeText(activity, "User Clicked", Toast.LENGTH_SHORT).show() 59 | } 60 | } 61 | 62 | companion object { 63 | private const val INTRO_CARD = "recyclerView_material_intro" 64 | } 65 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/sequence/Child1Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment.sequence 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import com.codertainment.materialintro.sample.R 10 | import com.codertainment.materialintro.shape.ShapeType 11 | import com.codertainment.materialintro.utils.materialIntroSequence 12 | import kotlinx.android.synthetic.main.fragment_child1.* 13 | 14 | class Child1Fragment : Fragment() { 15 | 16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 17 | // Inflate the layout for this fragment 18 | return inflater.inflate(R.layout.fragment_child1, container, false) 19 | } 20 | 21 | override fun onResume() { 22 | super.onResume() 23 | Log.d("child1", "onResume") 24 | materialIntroSequence { 25 | addConfig { 26 | shapeType = ShapeType.RECTANGLE 27 | viewId = "c1_b1" 28 | targetView = child1_button1 29 | infoText = "This is intro for Child1's Button1" 30 | } 31 | addConfig { 32 | shapeType = ShapeType.RECTANGLE 33 | viewId = "c1_b2" 34 | targetView = child1_button2 35 | infoText = "This is intro for Child1's Button2" 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/sequence/Child2Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment.sequence 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.util.Log 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.fragment.app.Fragment 10 | import com.codertainment.materialintro.sample.R 11 | import com.codertainment.materialintro.shape.ShapeType 12 | import com.codertainment.materialintro.utils.materialIntroSequence 13 | import kotlinx.android.synthetic.main.fragment_child2.* 14 | 15 | class Child2Fragment : Fragment() { 16 | 17 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 18 | // Inflate the layout for this fragment 19 | return inflater.inflate(R.layout.fragment_child2, container, false) 20 | } 21 | 22 | override fun onResume() { 23 | super.onResume() 24 | Log.d("child2", "onResume") 25 | //delay to let viewpager switch to this fragment completely 26 | Handler().postDelayed( 27 | { 28 | materialIntroSequence { 29 | addConfig { 30 | shapeType = ShapeType.RECTANGLE 31 | targetView = child2_button1 32 | infoText = "This is intro for Child2's Button1" 33 | } 34 | addConfig { 35 | shapeType = ShapeType.RECTANGLE 36 | viewId = "c2_b2" 37 | targetView = child2_button2 38 | infoText = "This is intro for Child2's Button2" 39 | } 40 | } 41 | }, 500 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/fragment/sequence/SequenceParentFragment.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.fragment.sequence 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import androidx.fragment.app.FragmentStatePagerAdapter 10 | import androidx.viewpager.widget.ViewPager 11 | import com.codertainment.materialintro.sample.R 12 | import com.codertainment.materialintro.sequence.MaterialIntroSequenceListener 13 | import com.codertainment.materialintro.shape.ShapeType 14 | import com.codertainment.materialintro.utils.materialIntroSequence 15 | import com.google.android.material.tabs.TabLayout 16 | import kotlinx.android.synthetic.main.fragment_sequence_parent.* 17 | import org.jetbrains.anko.support.v4.toast 18 | 19 | class SequenceParentFragment : Fragment(), MaterialIntroSequenceListener { 20 | 21 | private val onTabSelectedListener = object : TabLayout.OnTabSelectedListener { 22 | override fun onTabReselected(tab: TabLayout.Tab?) { 23 | 24 | } 25 | 26 | override fun onTabUnselected(tab: TabLayout.Tab?) { 27 | 28 | } 29 | 30 | override fun onTabSelected(tab: TabLayout.Tab?) { 31 | if (tab == null) return 32 | sequence_parent_view_pager.setCurrentItem(tab.position, true) 33 | } 34 | } 35 | 36 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 37 | // Inflate the layout for this fragment 38 | return inflater.inflate(R.layout.fragment_sequence_parent, container, false) 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | 44 | Log.d("parent", "onViewCreated") 45 | sequence_parent_view_pager.adapter = TabsAdapter() 46 | sequence_parent_view_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { 47 | override fun onPageScrollStateChanged(state: Int) { 48 | 49 | } 50 | 51 | override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { 52 | 53 | } 54 | 55 | override fun onPageSelected(position: Int) { 56 | sequence_parent_tabs.selectTab(sequence_parent_tabs.getTabAt(position)) 57 | } 58 | }) 59 | sequence_parent_tabs.addOnTabSelectedListener(onTabSelectedListener) 60 | 61 | materialIntroSequence(1000, showSkip = true, persistSkip = true) { 62 | addConfig { 63 | infoText = "Parent Fragment" 64 | shapeType = ShapeType.RECTANGLE 65 | targetView = sequence_parent_fragment_button 66 | viewId = "parent" 67 | } 68 | } 69 | } 70 | 71 | inner class TabsAdapter : FragmentStatePagerAdapter(childFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { 72 | override fun getItem(position: Int): Fragment = 73 | if (position == 0) { 74 | Child1Fragment() 75 | } else { 76 | Child2Fragment() 77 | } 78 | 79 | override fun getCount(): Int = sequence_parent_tabs.tabCount 80 | } 81 | 82 | override fun onProgress(onUserClick: Boolean, viewId: String, current: Int, total: Int) { 83 | toast("click: $onUserClick\nviewId: $viewId\ncurrent: $current\ntotal: $total") 84 | } 85 | 86 | override fun onCompleted() { 87 | toast("Tutorial Complete") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /sample/src/main/java/com/codertainment/materialintro/sample/model/Song.kt: -------------------------------------------------------------------------------- 1 | package com.codertainment.materialintro.sample.model 2 | 3 | import androidx.annotation.DrawableRes 4 | 5 | data class Song(var songName: String, @DrawableRes var songArt: Int, var singerName: String) -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/ic_menu_send.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/ic_menu_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/drawable/diamond.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_skip.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/icon_miv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/drawable/icon_miv.png -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/container.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 24 | 25 | 29 | 30 | 37 | 38 | 46 | 47 | 48 | 49 | 50 |