├── .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 [](https://android-arsenal.com/details/1/8059) [](https://jitpack.io/#shripal17/MaterialIntroView-v2) [  ](https://bintray.com/shripal17/codertainment/materialintroview-v2/2.2.0/link)
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 | |  |  |  |
337 |
338 | | Focus All | Focus Minimum | Focus Normal |
339 | |----------------|---------------------|-------------------|
340 | |  |  |  |
341 |
342 | | Toolbar Item with sequence and custom colors | Custom Info View using resource layout | Custom Info View at runtime |
343 | |----------------|---------------------|-------------------|
344 | |  |  |  |
345 |
346 | | Sequence with multiple fragments | Skip Button at Bottom Right Position with dotIconColor partially transparent | Skip Button at Top Right position |
347 | |----------------|----------------|----------------|
348 | | |  |  |
349 |
350 | # Full Demo GIF
351 | 
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 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_child1.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
29 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_child2.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
30 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_custom_info_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
31 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_focus.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
24 |
25 |
26 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_gravity.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
23 |
24 |
31 |
32 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
59 |
60 |
67 |
68 |
76 |
77 |
78 |
79 |
80 |
90 |
91 |
95 |
96 |
103 |
104 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_recyclerview.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_sequence_parent.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
26 |
27 |
31 |
32 |
36 |
37 |
38 |
46 |
47 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/info_custom_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
28 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/list_item_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
24 |
25 |
32 |
33 |
39 |
40 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/nav_header_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
19 |
20 |
26 |
27 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_help_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-hdpi/ic_help_outline.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-hdpi/ic_search.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_share_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-hdpi/ic_share_white.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shripal17/MaterialIntroView-v2/6ae490d1cd089262cd87221d0092892be301129f/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 200dp
5 |
6 | 16dp
7 | 16dp
8 | 16dp
9 |
10 | 160dp
11 | 220dp
12 | 28dp
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/drawables.xml:
--------------------------------------------------------------------------------
1 |
2 | - @android:drawable/ic_menu_camera
3 | - @android:drawable/ic_menu_gallery
4 | - @android:drawable/ic_menu_slideshow
5 | - @android:drawable/ic_menu_manage
6 | - @android:drawable/ic_menu_share
7 | - @android:drawable/ic_menu_send
8 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MIV Sample
3 |
4 | Open navigation drawer
5 | Close navigation drawer
6 |
7 | Settings
8 | ImageView
9 |
10 |
11 | Welcome. Let\'s setup your profile. Just tap on the circle.
12 |
13 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sample/src/test/java/com/codertainment/materialintro/sample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.codertainment.materialintro.sample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':materialintro', ':sample'
2 |
--------------------------------------------------------------------------------