├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── layout
│ │ │ │ └── activity_main.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── dizcoding
│ │ │ └── sample
│ │ │ └── MainActivity.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── dizcoding
│ │ │ └── sample
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── dizcoding
│ │ └── sample
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── showcase
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── dizcoding
│ │ │ │ └── showcase
│ │ │ │ ├── ShowCaseListener.java
│ │ │ │ ├── ShowCaseContentPosition.java
│ │ │ │ ├── ShowCasePreference.java
│ │ │ │ ├── ShowCaseObject.java
│ │ │ │ ├── ViewHelper.java
│ │ │ │ ├── ShowCaseBuilder.java
│ │ │ │ ├── ShowCaseDialog.java
│ │ │ │ └── ShowCaseLayout.java
│ │ └── res
│ │ │ ├── values
│ │ │ ├── public.xml
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ └── styles.xml
│ │ │ ├── drawable
│ │ │ ├── rectangle_rounded.xml
│ │ │ ├── background_round_white_10.xml
│ │ │ ├── selector_white.xml
│ │ │ ├── selector_circle_blue.xml
│ │ │ ├── selector_circle_green.xml
│ │ │ ├── ic_button_prev.xml
│ │ │ ├── ic_button_finish.xml
│ │ │ ├── ic_button_next.xml
│ │ │ └── ic_button_skip.xml
│ │ │ ├── layout
│ │ │ ├── circle_green_view.xml
│ │ │ └── tutorial_view.xml
│ │ │ └── drawable-v21
│ │ │ └── selector_white.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── dizcoding
│ │ │ └── showcase
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── dizcoding
│ │ └── showcase
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .gitignore
├── compiler.xml
├── runConfigurations.xml
├── misc.xml
├── gradle.xml
└── jarRepositories.xml
├── Showcase.gif
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/showcase/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/showcase/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/Showcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/Showcase.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "sample"
2 | include ':app'
3 | include ':showcase'
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | sample
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/showcase/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/showcase/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseListener.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | public interface ShowCaseListener {
4 | void onPrevious();
5 | void onNext();
6 |
7 | void onComplete();
8 | }
9 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/rectangle_rounded.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 07 00:02:43 WIB 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | tutors
3 |
4 | <
5 | >
6 | OK
7 | Skip
8 |
9 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/background_round_white_10.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/showcase/src/main/res/layout/circle_green_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #B0000000
4 | #42b549
5 | #1B4DB3
6 | #FFFFFF
7 | #DDDDDD
8 | #434655
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/dizcoding/sample/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.sample
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/showcase/src/test/java/com/dizcoding/showcase/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseContentPosition.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | public enum ShowCaseContentPosition {
4 | UNDEFINED (0),
5 | TOP (1),
6 | RIGHT (2),
7 | BOTTOM (3),
8 | LEFT (4);
9 |
10 | private final int position;
11 |
12 | ShowCaseContentPosition(int position) {
13 | this.position = position;
14 | }
15 |
16 | public int getPosition() {
17 | return position;
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 18sp
5 | 14sp
6 | 1dp
7 |
8 | 10dp
9 | 16dp
10 | 2dp
11 |
12 | 85dp
13 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable-v21/selector_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/selector_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/selector_circle_blue.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
7 |
8 |
9 |
10 | -
11 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/selector_circle_green.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
7 |
8 |
9 |
10 | -
11 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dizcoding/sample/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.sample
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.dizcoding.sample", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/showcase/src/androidTest/java/com/dizcoding/showcase/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.dizcoding.showcase.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/showcase/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCasePreference.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | public class ShowCasePreference {
7 | private static final String SHOWCASE_PREFERENCES = "show_case_pref";
8 |
9 | public static boolean hasShown(Context context, String tag){
10 | SharedPreferences sharedPreferences = context.getSharedPreferences(SHOWCASE_PREFERENCES,
11 | Context.MODE_PRIVATE);
12 | return sharedPreferences.getBoolean(tag, false);
13 | }
14 |
15 | public static void setShown(Context context, String tag, boolean hasShown){
16 | SharedPreferences.Editor sharedPreferencesEditor = context.getSharedPreferences(SHOWCASE_PREFERENCES,
17 | Context.MODE_PRIVATE).edit();
18 | sharedPreferencesEditor.putBoolean (tag, hasShown);
19 | sharedPreferencesEditor.apply();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_prev.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_finish.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/showcase/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 31
8 | buildToolsVersion "30.0.3"
9 |
10 | defaultConfig {
11 | minSdkVersion 16
12 | targetSdkVersion 31
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | consumerProguardFiles "consumer-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 |
37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
38 | implementation 'androidx.core:core-ktx:1.7.0'
39 | implementation 'androidx.appcompat:appcompat:1.4.0'
40 | implementation 'com.google.android.material:material:1.4.0'
41 | testImplementation 'junit:junit:4.+'
42 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
43 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
44 | }
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_next.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 31
8 | buildToolsVersion "30.0.3"
9 |
10 | defaultConfig {
11 | applicationId "com.dizcoding.sample"
12 | minSdkVersion 16
13 | targetSdkVersion 31
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | debug {
22 | debuggable true
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 |
39 |
40 | }
41 |
42 | dependencies {
43 |
44 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
45 | implementation 'androidx.core:core-ktx:1.7.0'
46 | implementation 'androidx.appcompat:appcompat:1.4.0'
47 | implementation 'com.google.android.material:material:1.4.0'
48 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
49 |
50 | implementation project(path: ':showcase')
51 |
52 | testImplementation 'junit:junit:4.+'
53 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
54 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
55 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
29 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dizcoding/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.sample
2 |
3 | import android.os.Bundle
4 | import android.widget.TextView
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.dizcoding.showcase.ShowCaseBuilder
7 | import com.dizcoding.showcase.ShowCaseContentPosition
8 | import com.dizcoding.showcase.ShowCaseObject
9 |
10 | class MainActivity : AppCompatActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContentView(R.layout.activity_main)
14 |
15 | val showCaseDialog = ShowCaseBuilder()
16 | .titleTextColorRes(R.color.blue)
17 | .titleTextSizeRes(R.dimen.text_title)
18 | .shadowColorRes(R.color.shadow)
19 | .textColorRes(R.color.dark_gray)
20 | .textSizeRes(R.dimen.text_normal)
21 | .spacingRes(R.dimen.spacing_normal)
22 | .backgroundContentColorRes(R.color.white)
23 | .circleIndicatorBackgroundDrawableRes(R.drawable.selector_circle_blue)
24 | .useSkipWord(true)
25 | .setLineColorRes(R.color.blue)
26 | .useCircleIndicator(true)
27 | .clickable(false)
28 | .build()
29 | val textView = findViewById(R.id.tv)
30 | val tvOne = findViewById(R.id.tvOne)
31 | val tvTwo = findViewById(R.id.tvTwo)
32 | val showCaseList = arrayListOf(
33 | ShowCaseObject(
34 | textView,
35 | "Akun Anda",
36 | "Lengkapi profil anda dan lihat nomor VA dan e-sertifikat kepesertaan disini.",
37 | ShowCaseContentPosition.TOP
38 | ),
39 | ShowCaseObject(
40 | tvOne,
41 | "Akun Anda",
42 | "Lengkapi profil anda dan lihat nomor VA dan e-sertifikat kepesertaan disini.",
43 | ShowCaseContentPosition.BOTTOM
44 | ),ShowCaseObject(
45 | tvTwo,
46 | "Akun Anda",
47 | "Lengkapi profil anda dan lihat nomor VA dan e-sertifikat kepesertaan disini.",
48 | ShowCaseContentPosition.TOP
49 | )
50 | )
51 |
52 | showCaseDialog.show(this, "", showCaseList)
53 |
54 | }
55 | }
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseObject.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.view.View;
4 | import android.view.ViewGroup;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | public class ShowCaseObject {
9 |
10 | private View view;
11 | protected String title;
12 | protected String text;
13 | protected ShowCaseContentPosition showCaseContentPosition;
14 | protected int tintBackgroundColor;
15 | private ViewGroup scrollView;
16 |
17 | public ShowCaseObject(@Nullable View view, @Nullable String title, String text ) {
18 | this(view, title, text, ShowCaseContentPosition.UNDEFINED);
19 | }
20 | public ShowCaseObject(@Nullable View view, @Nullable String title,
21 | String text, ShowCaseContentPosition showCaseContentPosition) {
22 | this(view, title, text, showCaseContentPosition, 0);
23 | }
24 |
25 | public ShowCaseObject(@Nullable View view, @Nullable String title,
26 | String text, ShowCaseContentPosition showCaseContentPosition,
27 | int tintBackgroundColor) {
28 | this(view, title, text, showCaseContentPosition, tintBackgroundColor, null);
29 | }
30 | public ShowCaseObject(@Nullable View view, @Nullable String title,
31 | String text, ShowCaseContentPosition showCaseContentPosition,
32 | int tintBackgroundColor, ViewGroup scrollView) {
33 | this.view = view;
34 | this.title = title;
35 | this.text = text;
36 | this.showCaseContentPosition = showCaseContentPosition;
37 | this.tintBackgroundColor = tintBackgroundColor;
38 | this.scrollView = scrollView;
39 | }
40 |
41 | public String getTitle() {
42 | return title;
43 | }
44 |
45 | public String getText() {
46 | return text;
47 | }
48 |
49 | public int getTintBackgroundColor() {
50 | return tintBackgroundColor;
51 | }
52 |
53 | public ShowCaseContentPosition getShowCaseContentPosition() {
54 | return showCaseContentPosition;
55 | }
56 |
57 | private int[] location;
58 | private int radius;
59 | public ShowCaseObject withCustomTarget(int[] location, int radius){
60 | if (location.length != 2) {
61 | return this;
62 | }
63 | this.location = location;
64 | this.radius = radius;
65 | return this;
66 | }
67 |
68 | public ShowCaseObject withCustomTarget(int[] location){
69 | if (location.length!= 4) {
70 | return this;
71 | }
72 | this.location = location;
73 | return this;
74 | }
75 |
76 | public int[] getLocation() {
77 | return location;
78 | }
79 |
80 | public int getRadius() {
81 | return radius;
82 | }
83 |
84 | public View getView() {
85 | return view;
86 | }
87 | public ViewGroup getScrollView() {
88 | return scrollView;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/showcase/src/main/res/layout/tutorial_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
24 |
25 |
31 |
32 |
37 |
38 |
45 |
46 |
49 |
50 |
57 |
58 |
65 |
66 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_skip.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ViewHelper.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Paint;
7 | import android.graphics.PorterDuff;
8 | import android.graphics.PorterDuffXfermode;
9 | import android.graphics.Rect;
10 | import android.graphics.drawable.Drawable;
11 | import android.graphics.drawable.GradientDrawable;
12 | import android.graphics.drawable.ShapeDrawable;
13 | import android.view.View;
14 | import android.view.ViewParent;
15 |
16 | public class ViewHelper {
17 | static public void getRelativePositionRec(View myView, ViewParent root, int[] location) {
18 | if (myView.getParent() == root) {
19 | location[0] += myView.getLeft();
20 | location[1] += myView.getTop();
21 | } else {
22 | location[0] += myView.getLeft();
23 | location[1] += myView.getTop();
24 | getRelativePositionRec((View) myView.getParent(), root, location);
25 | }
26 | }
27 |
28 | public static void setBackgroundColor(View v, int color){
29 | Drawable background = v.getBackground();
30 | if (background instanceof ShapeDrawable) {
31 | ShapeDrawable shapeDrawable = (ShapeDrawable)background;
32 | shapeDrawable.getPaint().setColor(color);
33 | } else if (background instanceof GradientDrawable) {
34 | GradientDrawable gradientDrawable = (GradientDrawable)background;
35 | gradientDrawable.setColor(color);
36 | } else {
37 | v.setBackgroundColor(color);
38 | }
39 | }
40 |
41 | public static Bitmap getCroppedBitmap(Bitmap bitmap, int centerLocation[], int radius) {
42 | Bitmap output = Bitmap.createBitmap(2*radius,
43 | 2*radius, Bitmap.Config.ARGB_8888);
44 | Canvas canvas = new Canvas(output);
45 |
46 | final int color = 0xff424242;
47 | final Paint paint = new Paint();
48 |
49 | Rect sourceRect = new Rect(centerLocation[0] - radius,
50 | centerLocation[1] - radius,
51 | centerLocation[0] + radius,
52 | centerLocation[1] + radius);
53 | Rect destRect = new Rect(0,0,2*radius, 2*radius);
54 |
55 | paint.setAntiAlias(true);
56 | canvas.drawARGB(0, 0, 0, 0);
57 | paint.setColor(color);
58 | canvas.drawCircle(radius, radius,
59 | radius, paint);
60 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
61 | canvas.drawBitmap(bitmap, sourceRect, destRect, paint);
62 | return output;
63 | }
64 |
65 | public static Bitmap getCroppedBitmap(Bitmap bitmap, int rectLocation[]) {
66 | int xStart = rectLocation[0];
67 | int yStart = rectLocation[1];
68 | int xEnd = rectLocation[2];
69 | int yEnd = rectLocation[3];
70 | int width = xEnd - xStart;
71 | int height = yEnd - yStart;
72 |
73 | Bitmap output = Bitmap.createBitmap(width,
74 | height, Bitmap.Config.ARGB_8888);
75 | Canvas canvas = new Canvas(output);
76 |
77 | final int color = 0xff424242;
78 | final Paint paint = new Paint();
79 |
80 | Rect sourceRect = new Rect(xStart,
81 | yStart,
82 | xEnd,
83 | yEnd);
84 | Rect destRect = new Rect(0,0,width, height);
85 |
86 | paint.setAntiAlias(true);
87 | canvas.drawARGB(0, 0, 0, 0);
88 | paint.setColor(color);
89 | canvas.drawRect(destRect, paint);
90 | // canvas.drawCircle(radius, radius,
91 | // radius, paint);
92 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
93 | canvas.drawBitmap(bitmap, sourceRect, destRect, paint);
94 | return output;
95 | }
96 |
97 | public static int getStatusBarHeight(Context context) {
98 | int height = 0;
99 | int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
100 | if (resId > 0) {
101 | height = context.getResources().getDimensionPixelSize(resId);
102 | }
103 | return height;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## !!!NOTE!!!
2 | this source code is clone from https://github.com/tokopedia/ShowCase and i just edit a layout so its compaitible with my case.
3 |
4 | # ShowCase
5 |
6 | This library is used to showcase chain of views to guide the users about the feature on the screen.
7 |
8 |
9 |
10 |
11 |
12 | |
13 |
14 |
15 |
16 | Dependencies
17 | -------
18 | ```
19 | dependencies {
20 | implementation 'com.github.gandhi-wibowo:showcase:v1.0.0'
21 | }
22 | ```
23 |
24 | Usage
25 | -------
26 |
27 | First, create the `ShowCaseDialog`. Use Builder Pattern.
28 |
29 | As an example:
30 |
31 | ```java
32 | showCaseDialog = new ShowCaseBuilder()
33 | .textColorRes(android.R.color.white)
34 | .shadowColorRes(R.color.shadow)
35 | .titleTextSizeRes(R.dimen.text_title)
36 | .textSizeRes(R.dimen.text_normal)
37 | .spacingRes(R.dimen.spacing_normal)
38 | .backgroundContentColorRes(R.color.blue)
39 | .circleIndicatorBackgroundDrawableRes(R.drawable.selector_circle_green)
40 | .setLineColorRes(R.color.blue)
41 | .prevDrawableRes(R.drawable.ic_btn_prev)
42 | .nextDrawableRes(R.drawable.ic_btn_next)
43 | .skipDrawableRes(R.drawable.ic_btn_skip)
44 | .finishDrawableRes(R.drawable.ic_btn_finish)
45 | .useCircleIndicator(true)
46 | .clickable(true)
47 | .build();
48 | ```
49 |
50 | ### this section is what i just edit.
51 | ```java
52 | .setLineColorRes(R.color.blue)
53 | .prevDrawableRes(R.drawable.ic_btn_prev)
54 | .nextDrawableRes(R.drawable.ic_btn_next)
55 | .skipDrawableRes(R.drawable.ic_btn_skip)
56 | .finishDrawableRes(R.drawable.ic_btn_finish)
57 | ```
58 | After the dialog has been created, provide the `ArrayList` of `ShowCaseObject`
59 |
60 | As an example to showcase 2 views:
61 |
62 | ```
63 | ArrayList showCaseList = new ArrayList<>();
64 | showCaseList.add(new ShowCaseObject(
65 | viewToHighLight,
66 | "Title",
67 | "Description"));
68 | showCaseList.add(new ShowCaseObject(
69 | view2,
70 | null,
71 | "Description 2");
72 | showCaseDialog.show(this, showCaseList);
73 | ```
74 |
75 | Item Customization
76 | -------
77 | ```
78 | showCaseList.add(new ShowCaseObject(@Nullable View view,
79 | @Nullable String title,
80 | String text,
81 | ShowCaseContentPosition showCaseContentPosition,
82 | int tintBackgroundColor) );
83 | ```
84 | View
85 | is the view to anchor. Fill `null` if no view to anchor.
86 |
87 | Title
88 | is the title to show, will be bold in default. Fill `null` if you don't use title.
89 |
90 | text
91 | is the text to show. By default will parse HTML.
92 |
93 | position (optional)
94 | default is `UNDEFINED` and will position the content automatically.
95 | Other option is `LEFT`,`TOP`,`RIGHT`,`BOTTOM` to position manually.
96 |
97 | tintBackgroundColor (optional)
98 | to override the backgroundColor to the view. The default is transparent.
99 |
100 | Custom Target
101 | -------
102 | ```
103 | showCaseList.add(
104 | new ShowCaseObject(
105 | findViewById(android.R.id.content),
106 | "Show case using custom target",
107 | "This is highlighted using custom target")
108 | .withCustomTarget(new int[]{ xCenter, yCenter}
109 | , radius) );
110 | ```
111 | Use ```.withCustomTarget``` to highlight circle area for the specific coordinate (x,y) and radius.
112 | ```
113 | showCaseList.add(
114 | new ShowCaseObject(
115 | findViewById(android.R.id.content),
116 | "Show case using custom target",
117 | "This is highlighted using custom target")
118 | .withCustomTarget(new int[]{ left, top, right, bottom} ) );
119 | ```
120 | Use ```.withCustomTarget``` to highlight rectangle area for the specific coordinate (x,y) and radius.
121 |
122 | Custom Layout Customization
123 | -------
124 | ```java
125 | showCaseDialog = new ShowCaseBuilder()
126 | .customView(R.layout.customView)
127 | .build();
128 | ```
129 |
130 | Note: To use most of default functionality, the views in the custom layout should correspond with the same id with the id in ```R.layout.tutorial_view.xml```
131 |
132 | License
133 | -------
134 |
135 | Copyright 2019 Tokopedia
136 |
137 | Licensed under the Apache License, Version 2.0 (the "License");
138 | you may not use this file except in compliance with the License.
139 | You may obtain a copy of the License at
140 |
141 | http://www.apache.org/licenses/LICENSE-2.0
142 |
143 | Unless required by applicable law or agreed to in writing, software
144 | distributed under the License is distributed on an "AS IS" BASIS,
145 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
146 | See the License for the specific language governing permissions and
147 | limitations under the License.
148 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseBuilder.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 |
4 | import android.os.Parcel;
5 | import android.os.Parcelable;
6 |
7 | public class ShowCaseBuilder implements Parcelable {
8 |
9 | private int layoutRes;
10 | private int titleTextColorRes;
11 | private int textColorRes;
12 | private int shadowColorRes;
13 | private int titleTextSizeRes;
14 | private int textSizeRes;
15 | private int spacingRes;
16 | private int backgroundContentColorRes;
17 | private int circleIndicatorBackgroundDrawableRes;
18 | private int prevDrawableRes;
19 | private int nextDrawableRes;
20 | private int finishDrawableRes;
21 | private boolean useCircleIndicator = true;
22 | private boolean clickable = false;
23 | private boolean useArrow = true;
24 | private int arrowWidth;
25 | private String packageName;
26 |
27 | private int skipDrawableRes;
28 | private int lineColorRes;
29 | private boolean useSkipWord;
30 |
31 | public ShowCaseBuilder setPackageName(String packageName) {
32 | this.packageName = packageName;
33 | return this;
34 | }
35 |
36 | public ShowCaseBuilder customView(int customViewRes) {
37 | this.layoutRes = customViewRes;
38 | return this;
39 | }
40 |
41 | public ShowCaseBuilder textColorRes(int textColorRes) {
42 | this.textColorRes = textColorRes;
43 | return this;
44 | }
45 |
46 | public ShowCaseBuilder titleTextColorRes(int titleTextColorRes) {
47 | this.titleTextColorRes = titleTextColorRes;
48 | return this;
49 | }
50 |
51 | public ShowCaseBuilder shadowColorRes(int shadowColorRes) {
52 | this.shadowColorRes = shadowColorRes;
53 | return this;
54 | }
55 |
56 | public ShowCaseBuilder useArrow(boolean useArrow) {
57 | this.useArrow = useArrow;
58 | return this;
59 | }
60 |
61 | public ShowCaseBuilder useSkipWord(boolean useSkipWord) {
62 | this.useSkipWord = useSkipWord;
63 | return this;
64 | }
65 |
66 | public ShowCaseBuilder textSizeRes(int textSizeRes) {
67 | this.textSizeRes = textSizeRes;
68 | return this;
69 | }
70 |
71 | public ShowCaseBuilder titleTextSizeRes(int titleTextSizeRes) {
72 | this.titleTextSizeRes = titleTextSizeRes;
73 | return this;
74 | }
75 |
76 | public ShowCaseBuilder spacingRes(int spacingRes) {
77 | this.spacingRes = spacingRes;
78 | return this;
79 | }
80 |
81 | public ShowCaseBuilder arrowWidth(int arrowWidth) {
82 | this.arrowWidth = arrowWidth;
83 | return this;
84 | }
85 |
86 | public ShowCaseBuilder backgroundContentColorRes(int backgroundContentColorRes) {
87 | this.backgroundContentColorRes = backgroundContentColorRes;
88 | return this;
89 | }
90 |
91 | public ShowCaseBuilder circleIndicatorBackgroundDrawableRes(int circleIndicatorBackgroundDrawableRes) {
92 | this.circleIndicatorBackgroundDrawableRes = circleIndicatorBackgroundDrawableRes;
93 | return this;
94 | }
95 |
96 | public ShowCaseBuilder setLineColorRes(int lineColorRes) {
97 | this.lineColorRes = lineColorRes;
98 | return this;
99 | }
100 |
101 | public ShowCaseBuilder finishDrawableRes(int finishDrawableRes) {
102 | this.finishDrawableRes = finishDrawableRes;
103 | return this;
104 | }
105 |
106 | public ShowCaseBuilder prevDrawableRes(int prevDrawableRes) {
107 | this.prevDrawableRes = prevDrawableRes;
108 | return this;
109 | }
110 |
111 | public ShowCaseBuilder nextDrawableRes(int nextDrawableRes) {
112 | this.nextDrawableRes = nextDrawableRes;
113 | return this;
114 | }
115 |
116 | public ShowCaseBuilder skipDrawableRes(int skipDrawableRes) {
117 | this.skipDrawableRes = skipDrawableRes;
118 | return this;
119 | }
120 |
121 |
122 | public ShowCaseBuilder clickable(boolean clickable) {
123 | this.clickable = clickable;
124 | return this;
125 | }
126 |
127 | public ShowCaseBuilder useCircleIndicator(boolean useCircleIndicator) {
128 | this.useCircleIndicator = useCircleIndicator;
129 | return this;
130 | }
131 |
132 | public String getPackageName() {
133 | return packageName;
134 | }
135 | public int getLineColorRes() {
136 | return lineColorRes;
137 | }
138 |
139 | public int getTextColorRes() {
140 | return textColorRes;
141 | }
142 |
143 | public int getTitleTextColorRes() {
144 | return titleTextColorRes;
145 | }
146 |
147 | public int getTitleTextSizeRes() {
148 | return titleTextSizeRes;
149 | }
150 |
151 | public int getFinishDrawableRes() {
152 | return finishDrawableRes;
153 | }
154 |
155 | public int getNextDrawableRes() {
156 | return nextDrawableRes;
157 | }
158 |
159 | public int getSkipDrawableRes() {
160 | return skipDrawableRes;
161 | }
162 |
163 | public int getPrevDrawableRes() {
164 | return prevDrawableRes;
165 | }
166 |
167 | public boolean useCircleIndicator() {
168 | return useCircleIndicator;
169 | }
170 |
171 | public int getShadowColorRes() {
172 | return shadowColorRes;
173 | }
174 |
175 | public int getTextSizeRes() {
176 | return textSizeRes;
177 | }
178 |
179 | public int getBackgroundContentColorRes() {
180 | return backgroundContentColorRes;
181 | }
182 |
183 | public int getCircleIndicatorBackgroundDrawableRes() {
184 | return circleIndicatorBackgroundDrawableRes;
185 | }
186 |
187 | public boolean isUseArrow() {
188 | return useArrow;
189 | }
190 |
191 | public boolean isUseSkipWord(){
192 | return useSkipWord;
193 | }
194 |
195 | public int getLayoutRes() {
196 | return layoutRes;
197 | }
198 |
199 | public int getArrowWidth() {
200 | return arrowWidth;
201 | }
202 |
203 | public int getSpacingRes() {
204 | return spacingRes;
205 | }
206 |
207 | public boolean isClickable() {
208 | return clickable;
209 | }
210 |
211 | public ShowCaseDialog build() {
212 | return ShowCaseDialog.newInstance(this);
213 | }
214 |
215 | @Override
216 | public int describeContents() {
217 | return 0;
218 | }
219 |
220 | @Override
221 | public void writeToParcel(Parcel dest, int flags) {
222 | dest.writeInt(this.layoutRes);
223 | dest.writeInt(this.titleTextColorRes);
224 | dest.writeInt(this.textColorRes);
225 | dest.writeInt(this.shadowColorRes);
226 | dest.writeInt(this.titleTextSizeRes);
227 | dest.writeInt(this.textSizeRes);
228 | dest.writeInt(this.spacingRes);
229 | dest.writeInt(this.backgroundContentColorRes);
230 | dest.writeInt(this.circleIndicatorBackgroundDrawableRes);
231 | dest.writeInt(this.prevDrawableRes);
232 | dest.writeInt(this.nextDrawableRes);
233 | dest.writeInt(this.finishDrawableRes);
234 | dest.writeByte(this.useCircleIndicator ? (byte) 1 : (byte) 0);
235 | dest.writeByte(this.clickable ? (byte) 1 : (byte) 0);
236 | dest.writeByte(this.useArrow ? (byte) 1 : (byte) 0);
237 | dest.writeInt(this.arrowWidth);
238 | dest.writeInt(this.skipDrawableRes);
239 | dest.writeInt(this.lineColorRes);
240 | dest.writeByte(this.useSkipWord ? (byte) 1 : (byte) 0);
241 | }
242 |
243 | public ShowCaseBuilder() {
244 | }
245 |
246 | protected ShowCaseBuilder(Parcel in) {
247 | this.layoutRes = in.readInt();
248 | this.titleTextColorRes = in.readInt();
249 | this.textColorRes = in.readInt();
250 | this.shadowColorRes = in.readInt();
251 | this.titleTextSizeRes = in.readInt();
252 | this.textSizeRes = in.readInt();
253 | this.spacingRes = in.readInt();
254 | this.backgroundContentColorRes = in.readInt();
255 | this.circleIndicatorBackgroundDrawableRes = in.readInt();
256 | this.prevDrawableRes = in.readInt();
257 | this.nextDrawableRes = in.readInt();
258 | this.finishDrawableRes = in.readInt();
259 | this.useCircleIndicator = in.readByte() != 0;
260 | this.clickable = in.readByte() != 0;
261 | this.useArrow = in.readByte() != 0;
262 | this.arrowWidth = in.readInt();
263 | this.skipDrawableRes = in.readInt();
264 | this.lineColorRes = in.readInt();
265 | this.useSkipWord = in.readByte() != 0;
266 | }
267 |
268 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
269 | @Override
270 | public ShowCaseBuilder createFromParcel(Parcel source) {
271 | return new ShowCaseBuilder(source);
272 | }
273 |
274 | @Override
275 | public ShowCaseBuilder[] newArray(int size) {
276 | return new ShowCaseBuilder[size];
277 | }
278 | };
279 | }
280 |
281 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseDialog.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 |
4 | import android.app.Dialog;
5 | import android.os.Bundle;
6 | import android.os.Handler;
7 | import android.text.TextUtils;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.view.Window;
12 | import android.widget.ScrollView;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.appcompat.app.AppCompatActivity;
17 | import androidx.core.widget.NestedScrollView;
18 | import androidx.fragment.app.DialogFragment;
19 | import androidx.fragment.app.FragmentActivity;
20 | import androidx.fragment.app.FragmentManager;
21 | import androidx.fragment.app.FragmentTransaction;
22 |
23 | import java.util.ArrayList;
24 |
25 | public class ShowCaseDialog extends DialogFragment {
26 | private static final String ARG_BUILDER = "BUILDER";
27 | public static final int DELAY_SCROLLING = 350;
28 | public static final String TAG = ShowCaseDialog.class.getSimpleName();
29 | public static final int MAX_RETRY_LAYOUT = 3;
30 |
31 | private ArrayList tutorsList;
32 | private int currentTutorIndex = -1;
33 | private ShowCaseBuilder builder;
34 | private String tag;
35 |
36 | boolean hasViewGroupHandled = false;
37 |
38 | private OnShowCaseStepListener listener;
39 |
40 | private int retryCounter = 0;
41 |
42 | public interface OnShowCaseStepListener {
43 | /**
44 | * @param previousStep
45 | * @param nextStep
46 | * @param showCaseObject
47 | * @return true if already fully handled show case step inthis function
48 | */
49 | boolean onShowCaseGoTo(int previousStep, int nextStep, ShowCaseObject showCaseObject);
50 | }
51 |
52 | public void setShowCaseStepListener(OnShowCaseStepListener listener) {
53 | this.listener = listener;
54 | }
55 |
56 | static ShowCaseDialog newInstance(ShowCaseBuilder builder) {
57 | final Bundle args = new Bundle();
58 | final ShowCaseDialog fragment = new ShowCaseDialog();
59 | args.putParcelable(ARG_BUILDER, builder);
60 | fragment.setArguments(args);
61 | return fragment;
62 | }
63 |
64 | @Override
65 | public void onCreate(@Nullable Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 | getArgs(getArguments());
68 | setRetainInstance(true);
69 | }
70 |
71 | private void getArgs(Bundle args) {
72 | builder = (ShowCaseBuilder) args.get(ARG_BUILDER);
73 | }
74 |
75 | @Override
76 | @NonNull
77 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
78 | final Dialog dialog = new Dialog(getActivity(), R.style.ShowCase) {
79 | @Override
80 | public void onBackPressed() {
81 | if (builder.isClickable()) {
82 | previous();
83 | }
84 | }
85 | };
86 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
87 | return dialog;
88 | }
89 |
90 | @Override
91 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
92 | @Nullable Bundle savedInstanceState) {
93 | final View view = new ShowCaseLayout(getActivity(), builder);
94 | initViews(((ShowCaseLayout) view));
95 | return view;
96 | }
97 |
98 | private void initViews(ShowCaseLayout view) {
99 | view.setShowCaseListener(new ShowCaseListener() {
100 | @Override
101 | public void onPrevious() {
102 | previous();
103 | }
104 |
105 | @Override
106 | public void onNext() {
107 | next();
108 | }
109 |
110 | @Override
111 | public void onComplete() {
112 | if (!TextUtils.isEmpty(tag)) {
113 | ShowCasePreference.setShown(getActivity(), tag, true);
114 | }
115 | ShowCaseDialog.this.close();
116 | }
117 | });
118 |
119 | setCancelable(builder.isClickable());
120 | }
121 |
122 | public void next() {
123 | if ((currentTutorIndex + 1) >= tutorsList.size()) {
124 | this.close();
125 | } else {
126 | ShowCaseDialog.this.show(this.getActivity(), tag, tutorsList, currentTutorIndex + 1);
127 | }
128 | }
129 |
130 | public void previous() {
131 | if ((currentTutorIndex - 1) < 0) {
132 | currentTutorIndex = 0;
133 | } else {
134 | ShowCaseDialog.this.show(getActivity(), tag, tutorsList, currentTutorIndex - 1);
135 | }
136 | }
137 |
138 | @Override
139 | public void onStart() {
140 | super.onStart();
141 | final Window window = getDialog().getWindow();
142 | if (window != null) {
143 | window.setBackgroundDrawableResource(android.R.color.transparent);
144 | window.setDimAmount(0f);
145 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
146 | }
147 | }
148 |
149 | public boolean hasShown(AppCompatActivity activity, String tag) {
150 | return ShowCasePreference.hasShown(activity, tag);
151 | }
152 |
153 | public void show(AppCompatActivity activity, @Nullable String tag, final ArrayList tutorList) {
154 | show(activity, tag, tutorList, 0);
155 | }
156 |
157 | public void show(final FragmentActivity activity, @Nullable String tag, final ArrayList tutorList, int indexToShow) {
158 | if (activity == null || activity.isFinishing()) {
159 | return;
160 | }
161 | try {
162 | this.tutorsList = tutorList;
163 | this.tag = tag;
164 | if (indexToShow < 0 || indexToShow >= tutorList.size()) {
165 | indexToShow = 0;
166 | }
167 | int previousIndex = currentTutorIndex;
168 | currentTutorIndex = indexToShow;
169 |
170 | hasViewGroupHandled = false;
171 | if (listener != null) {
172 | hasViewGroupHandled = listener.onShowCaseGoTo(previousIndex, currentTutorIndex, tutorList.get(currentTutorIndex));
173 | }
174 |
175 | // has been handled by listener
176 | if (hasViewGroupHandled) return;
177 |
178 | final ShowCaseObject showCaseObject = tutorList.get(currentTutorIndex);
179 | final ViewGroup viewGroup = showCaseObject.getScrollView();
180 | if (viewGroup != null) {
181 | final View viewToFocus = showCaseObject.getView();
182 | if (viewToFocus != null) {
183 | hideLayout();
184 | viewGroup.post(new Runnable() {
185 | @Override
186 | public void run() {
187 | if (viewGroup instanceof ScrollView) {
188 | ScrollView scrollView = (ScrollView) viewGroup;
189 | int relativeLocation[] = new int[2];
190 | ViewHelper.getRelativePositionRec(viewToFocus, viewGroup, relativeLocation);
191 | scrollView.smoothScrollTo(0, relativeLocation[1]);
192 | scrollView.postDelayed(new Runnable() {
193 | @Override
194 | public void run() {
195 | showLayout(activity, showCaseObject);
196 | }
197 | }, DELAY_SCROLLING);
198 | } else if (viewGroup instanceof NestedScrollView) {
199 | NestedScrollView scrollView = (NestedScrollView) viewGroup;
200 | int relativeLocation[] = new int[2];
201 | ViewHelper.getRelativePositionRec(viewToFocus, viewGroup, relativeLocation);
202 | scrollView.smoothScrollTo(0, relativeLocation[1]);
203 | scrollView.postDelayed(new Runnable() {
204 | @Override
205 | public void run() {
206 | showLayout(activity, showCaseObject);
207 | }
208 | }, DELAY_SCROLLING);
209 | }
210 | }
211 | });
212 | hasViewGroupHandled = true;
213 | } else {
214 | hasViewGroupHandled = false;
215 | }
216 | }
217 |
218 | if (!hasViewGroupHandled) {
219 | showLayout(activity, tutorsList.get(currentTutorIndex));
220 | }
221 | } catch (Exception e) {
222 | // to Handle the unknown exception.
223 | // Since this only for first guide, if any error appears, just don't show the guide
224 | try {
225 | ShowCaseDialog.this.dismiss();
226 | } catch (Exception e2) {
227 | // no op
228 | }
229 | }
230 | }
231 |
232 | public void showLayout(FragmentActivity activity, ShowCaseObject showCaseObject) {
233 | if (activity == null || activity.isFinishing()) {
234 | return;
235 | }
236 |
237 |
238 | FragmentManager fm = activity.getSupportFragmentManager();
239 | if (!isVisible()) {
240 | try {
241 | if (!isAdded()) {
242 | show(fm, TAG);
243 | } else if (isHidden()) {
244 | FragmentTransaction ft = fm.beginTransaction();
245 | ft.show(ShowCaseDialog.this);
246 | ft.commit();
247 | }
248 | } catch (IllegalStateException e) {
249 | // called in illegal state. just return.
250 | return;
251 | }
252 | }
253 |
254 | final View view = showCaseObject.getView();
255 | final String title = showCaseObject.getTitle();
256 | final String text = showCaseObject.getText();
257 | final ShowCaseContentPosition showCaseContentPosition = showCaseObject.getShowCaseContentPosition();
258 | final int tintBackgroundColor = showCaseObject.getTintBackgroundColor();
259 | final int[] location = showCaseObject.getLocation();
260 | final int radius = showCaseObject.getRadius();
261 |
262 | if (view == null) {
263 | layoutShowTutorial(null, title, text, showCaseContentPosition,
264 | tintBackgroundColor, location, radius);
265 | } else {
266 | view.post(() -> layoutShowTutorial(view, title, text, showCaseContentPosition,
267 | tintBackgroundColor, location, radius));
268 | }
269 | }
270 |
271 | public void hideLayout() {
272 | final ShowCaseLayout layout = (ShowCaseLayout) ShowCaseDialog.this.getView();
273 | if (layout == null) {
274 | return;
275 | }
276 | layout.hideTutorial();
277 | }
278 |
279 | private void layoutShowTutorial(final View view, final String title, final String text,
280 | final ShowCaseContentPosition showCaseContentPosition,
281 | final int tintBackgroundColor, final int[] customTarget, final int radius) {
282 |
283 | try {
284 | final ShowCaseLayout layout = (ShowCaseLayout) ShowCaseDialog.this.getView();
285 | if (layout == null) {
286 | if (retryCounter >= MAX_RETRY_LAYOUT) {
287 | retryCounter = 0;
288 | return;
289 | }
290 | // wait until the layout is ready, and call itself
291 | new Handler().postDelayed(new Runnable() {
292 | @Override
293 | public void run() {
294 | retryCounter++;
295 | layoutShowTutorial(view, title, text,
296 | showCaseContentPosition, tintBackgroundColor, customTarget, radius);
297 | }
298 | }, 1000);
299 | return;
300 | }
301 | retryCounter = 0;
302 | layout.showTutorial(view, title, text, currentTutorIndex, tutorsList.size(),
303 | showCaseContentPosition, tintBackgroundColor, customTarget, radius);
304 | } catch (Throwable t) {
305 | // do nothing
306 | }
307 |
308 | }
309 |
310 | public void close() {
311 | try {
312 | dismiss();
313 | final ShowCaseLayout layout = (ShowCaseLayout) ShowCaseDialog.this.getView();
314 | if (layout == null) {
315 | return;
316 | }
317 | layout.closeTutorial();
318 | } catch (Exception e) {
319 | // no op
320 | }
321 | }
322 |
323 | }
324 |
325 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseLayout.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PorterDuff;
10 | import android.graphics.PorterDuffXfermode;
11 | import android.graphics.drawable.Drawable;
12 | import android.os.Build;
13 | import android.text.Html;
14 | import android.text.Spanned;
15 | import android.text.TextUtils;
16 | import android.util.AttributeSet;
17 | import android.util.TypedValue;
18 | import android.view.Gravity;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.view.ViewTreeObserver;
23 | import android.widget.FrameLayout;
24 | import android.widget.ImageView;
25 | import android.widget.TextView;
26 |
27 | import androidx.annotation.Nullable;
28 | import androidx.annotation.RequiresApi;
29 | import androidx.core.content.ContextCompat;
30 |
31 | public class ShowCaseLayout extends FrameLayout {
32 |
33 | // customized attribute
34 | private int layoutRes;
35 | private int textColor;
36 | private int titleTextColor;
37 | private int shadowColor;
38 | private float textSize;
39 | private float textTitleSize;
40 | private int spacing;
41 | private int arrowMargin;
42 | private int arrowWidth;
43 | private int lineColorRes;
44 | private boolean useCircleIndicator;
45 |
46 | private boolean isCancelable;
47 | private boolean hasSkipWord;
48 |
49 | private Drawable prevDrawable;
50 | private Drawable nextDrawable;
51 | private Drawable finishDrawable;
52 | private Drawable skipDrawable;
53 |
54 | private int backgroundContentColor;
55 | private int circleBackgroundDrawableRes;
56 |
57 | // View
58 | private ViewGroup viewGroup;
59 | private Bitmap bitmap;
60 | private View lastTutorialView;
61 | private Paint viewPaint;
62 |
63 | // listener
64 | private ShowCaseListener showCaseListener;
65 |
66 | ShowCaseContentPosition showCaseContentPosition;
67 |
68 | private int highlightLocX;
69 | private int highlightLocY;
70 |
71 | // determined if this is last chain
72 | private boolean isStart;
73 | private boolean isLast;
74 |
75 | // path for arrow
76 | private Path path;
77 | private Paint arrowPaint;
78 | private TextView textViewTitle;
79 | private TextView textViewDesc;
80 | private ImageView prevButton;
81 | private ImageView nextButton;
82 | private ImageView skipButton;
83 | private ViewGroup viewGroupIndicator;
84 |
85 | public ShowCaseLayout(Context context, @Nullable ShowCaseBuilder builder) {
86 | super(context);
87 | init(context, builder);
88 | }
89 |
90 | public ShowCaseLayout(Context context) {
91 | super(context);
92 | init(context, null);
93 | }
94 |
95 | public ShowCaseLayout(Context context, AttributeSet attrs) {
96 | super(context, attrs);
97 | init(context, null);
98 | }
99 |
100 | public ShowCaseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
101 | super(context, attrs, defStyleAttr);
102 | init(context, null);
103 | }
104 |
105 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
106 | public ShowCaseLayout(Context context,
107 | AttributeSet attrs,
108 | int defStyleAttr,
109 | int defStyleRes) {
110 | super(context, attrs, defStyleAttr, defStyleRes);
111 | init(context, null);
112 | }
113 |
114 | private void init(Context context, @Nullable ShowCaseBuilder builder) {
115 | setVisibility(View.GONE);
116 |
117 | if (isInEditMode()) {
118 | return;
119 | }
120 |
121 | applyAttrs(context, builder);
122 |
123 | //setBackground, color
124 | initFrame();
125 |
126 | // setContentView
127 | initContent(context, builder);
128 |
129 | setClickable(this.isCancelable);
130 | setFocusable(this.isCancelable);
131 |
132 | if (this.isCancelable) {
133 | this.setOnClickListener(new OnClickListener() {
134 | @Override
135 | public void onClick(View v) {
136 | onNextClicked();
137 | }
138 | });
139 | }
140 | }
141 |
142 | private void onNextClicked() {
143 | if (showCaseListener != null) {
144 | if (this.isLast) {
145 | ShowCaseLayout.this.showCaseListener.onComplete();
146 | } else {
147 | ShowCaseLayout.this.showCaseListener.onNext();
148 | }
149 | }
150 | }
151 |
152 | private void initFrame() {
153 | setWillNotDraw(false);
154 |
155 | viewPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
156 | viewPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
157 |
158 | arrowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
159 | arrowPaint.setColor(backgroundContentColor);
160 | arrowPaint.setStyle(Paint.Style.FILL);
161 |
162 | setBackgroundColor(shadowColor);
163 | }
164 |
165 | public void setShowCaseListener(ShowCaseListener showCaseListener) {
166 | this.showCaseListener = showCaseListener;
167 | }
168 |
169 | public void showTutorial(View view,
170 | String title,
171 | String text,
172 | int currentTutorIndex,
173 | int tutorsListSize,
174 | ShowCaseContentPosition showCaseContentPosition,
175 | int tintBackgroundColor,
176 | final int[] customTarget, final int radius) throws Throwable {
177 |
178 | this.isStart = currentTutorIndex == 0;
179 |
180 | this.isLast = currentTutorIndex == tutorsListSize - 1;
181 | this.showCaseContentPosition = showCaseContentPosition;
182 |
183 | if (this.bitmap != null) {
184 | this.bitmap.recycle();
185 | }
186 | if (this.lastTutorialView != null) {
187 | this.lastTutorialView.setDrawingCacheEnabled(false);
188 | }
189 |
190 | if (TextUtils.isEmpty(title)) {
191 | textViewTitle.setVisibility(View.GONE);
192 | } else {
193 | textViewTitle.setText(fromHtml(title));
194 | textViewTitle.setVisibility(View.VISIBLE);
195 | }
196 |
197 | textViewDesc.setText(fromHtml(text));
198 |
199 | if (skipButton != null){
200 | if (hasSkipWord){
201 | skipButton.setImageDrawable(skipDrawable);
202 | skipButton.setVisibility(VISIBLE);
203 | }else {
204 | skipButton.setImageDrawable(skipDrawable);
205 | skipButton.setVisibility(GONE);
206 | }
207 | }
208 |
209 | if (prevButton != null) {
210 | if (isStart) {
211 | prevButton.setVisibility(View.GONE);
212 | } else {
213 | prevButton.setImageDrawable(prevDrawable);
214 | prevButton.setVisibility(View.VISIBLE);
215 | }
216 | }
217 |
218 | if (nextButton != null) {
219 | if (isLast) {
220 | nextButton.setImageDrawable(finishDrawable);
221 | } else if (currentTutorIndex < tutorsListSize - 1) { // has next
222 | nextButton.setImageDrawable(nextDrawable);
223 | }
224 | }
225 |
226 | makeCircleIndicator(!isStart || !isLast, currentTutorIndex, tutorsListSize);
227 |
228 | if (view == null) {
229 | this.lastTutorialView = null;
230 | this.bitmap = null;
231 | this.highlightLocX = 0;
232 | this.highlightLocY = 0;
233 | moveViewToCenter();
234 | } else {
235 | this.lastTutorialView = view;
236 | if (view.willNotCacheDrawing()) {
237 | view.setWillNotCacheDrawing(false);
238 | }
239 | view.setDrawingCacheEnabled(true);
240 | view.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_LOW);
241 | if (tintBackgroundColor == 0) {
242 | this.bitmap = view.getDrawingCache();
243 | } else {
244 | Bitmap bitmapTemp = view.getDrawingCache();
245 |
246 | Bitmap bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(),
247 | view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
248 | Canvas bigCanvas = new Canvas(bigBitmap);
249 | bigCanvas.drawColor(tintBackgroundColor);
250 | Paint paint = new Paint();
251 | bigCanvas.drawBitmap(bitmapTemp, 0f, 0f, paint);
252 |
253 | this.bitmap = bigBitmap;
254 | }
255 |
256 | //set custom target to view
257 | if (customTarget != null) {
258 | if (customTarget.length == 2) {
259 | this.bitmap = ViewHelper.getCroppedBitmap(bitmap, customTarget, radius);
260 | } else if (customTarget.length == 4) {
261 | this.bitmap = ViewHelper.getCroppedBitmap(bitmap, customTarget);
262 | }
263 |
264 | this.highlightLocX = customTarget[0] - radius;
265 | this.highlightLocY = customTarget[1] - radius;
266 | } else { // use view location as target
267 | final int[] location = new int[2];
268 | view.getLocationInWindow(location);
269 |
270 | this.highlightLocX = location[0];
271 | this.highlightLocY = location[1] - ViewHelper.getStatusBarHeight(getContext());
272 | }
273 |
274 | this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
275 | @Override
276 | public void onGlobalLayout() {
277 | if (ShowCaseLayout.this.bitmap != null) {
278 | moveViewBasedHighlight(ShowCaseLayout.this.highlightLocX,
279 | ShowCaseLayout.this.highlightLocY,
280 | ShowCaseLayout.this.highlightLocX + ShowCaseLayout.this.bitmap.getWidth(),
281 | ShowCaseLayout.this.highlightLocY + ShowCaseLayout.this.bitmap.getHeight());
282 |
283 | if (Build.VERSION.SDK_INT < 16) {
284 | ShowCaseLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
285 | } else {
286 | ShowCaseLayout.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
287 | }
288 | invalidate();
289 | }
290 | }
291 | });
292 | }
293 |
294 | this.setVisibility(View.VISIBLE);
295 | }
296 |
297 | public void hideTutorial() {
298 | this.setVisibility(View.INVISIBLE);
299 | }
300 |
301 | private void makeCircleIndicator(boolean hasMoreOneCircle,
302 | int currentTutorIndex,
303 | int tutorsListSize) {
304 | if (useCircleIndicator && this.viewGroupIndicator != null) {
305 | if (hasMoreOneCircle) { // has more than 1 circle
306 | // already has circle indicator
307 | if (this.viewGroupIndicator.getChildCount() == tutorsListSize) {
308 | for (int i = 0; i < tutorsListSize; i++) {
309 | View viewCircle = this.viewGroupIndicator.getChildAt(i);
310 | if (i == currentTutorIndex) {
311 | viewCircle.setSelected(true);
312 | } else {
313 | viewCircle.setSelected(false);
314 | }
315 | }
316 | } else { //reinitialize, the size is different
317 | this.viewGroupIndicator.removeAllViews();
318 | LayoutInflater inflater = LayoutInflater.from(getContext());
319 | for (int i = 0; i < tutorsListSize; i++) {
320 | View viewCircle = inflater.inflate(R.layout.circle_green_view,
321 | viewGroupIndicator,
322 | false);
323 | viewCircle.setBackgroundResource(this.circleBackgroundDrawableRes);
324 | if (i == currentTutorIndex) {
325 | viewCircle.setSelected(true);
326 | }
327 | this.viewGroupIndicator.addView(viewCircle);
328 | }
329 | }
330 | } else {
331 | this.viewGroupIndicator.removeAllViews();
332 | }
333 | }
334 | }
335 |
336 | public void closeTutorial() {
337 | setVisibility(View.GONE);
338 | if (this.bitmap != null) {
339 | this.bitmap.recycle();
340 | this.bitmap = null;
341 | }
342 | if (lastTutorialView != null) {
343 | this.lastTutorialView.setDrawingCacheEnabled(false);
344 | this.lastTutorialView = null;
345 | }
346 | }
347 |
348 | @Override
349 | public void onDetachedFromWindow() {
350 | super.onDetachedFromWindow();
351 | recycleResources();
352 | }
353 |
354 | @Override
355 | public void onDraw(Canvas canvas) {
356 | if (bitmap == null || bitmap.isRecycled()) {
357 | return;
358 | }
359 | super.onDraw(canvas);
360 | canvas.drawBitmap(this.bitmap, this.highlightLocX, this.highlightLocY, viewPaint);
361 |
362 | // drawArrow
363 | if (path != null && this.viewGroup.getVisibility() == View.VISIBLE) {
364 | canvas.drawPath(path, arrowPaint);
365 | }
366 | }
367 |
368 | private void applyAttrs(Context context, @Nullable ShowCaseBuilder builder) {
369 | // set Default value before check builder (might be null)
370 | this.layoutRes = R.layout.tutorial_view;
371 |
372 | this.textColor = Color.WHITE;
373 | this.titleTextColor = Color.WHITE;
374 | this.textTitleSize = getResources().getDimension(R.dimen.text_title);
375 | this.textSize = getResources().getDimension(R.dimen.text_normal);
376 |
377 | this.shadowColor = ContextCompat.getColor(context, R.color.shadow);
378 | this.spacing = (int) getResources().getDimension(R.dimen.spacing_normal);
379 |
380 | this.arrowMargin = this.spacing / 3;
381 | this.arrowWidth = (int) (1.5 * this.spacing);
382 |
383 | this.backgroundContentColor = Color.BLACK;
384 | this.circleBackgroundDrawableRes = R.drawable.selector_circle_green;
385 |
386 | this.prevDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_prev);
387 | this.nextDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_next);
388 | this.finishDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_finish);
389 | this.skipDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_skip);
390 |
391 | this.lineColorRes = ContextCompat.getColor(context,R.color.blue);
392 |
393 | if (builder == null) {
394 | return;
395 | }
396 |
397 | this.layoutRes = builder.getLayoutRes() != 0 ?
398 | builder.getLayoutRes()
399 | : this.layoutRes;
400 |
401 | this.textColor = builder.getTextColorRes() != 0 ?
402 | ContextCompat.getColor(context, builder.getTextColorRes())
403 | : this.textColor;
404 |
405 | this.lineColorRes = builder.getLineColorRes() != 0 ?
406 | ContextCompat.getColor(context, builder.getLineColorRes())
407 | : this.lineColorRes;
408 |
409 | this.titleTextColor = builder.getTitleTextColorRes() != 0 ?
410 | ContextCompat.getColor(context, builder.getTitleTextColorRes())
411 | : this.titleTextColor;
412 |
413 | this.textTitleSize = builder.getTitleTextSizeRes() != 0 ?
414 | getResources().getDimension(builder.getTitleTextSizeRes())
415 | : this.textTitleSize;
416 |
417 | this.textSize = builder.getTextSizeRes() != 0 ?
418 | getResources().getDimension(builder.getTextSizeRes())
419 | : this.textSize;
420 |
421 | this.backgroundContentColor = builder.getBackgroundContentColorRes() != 0 ?
422 | ContextCompat.getColor(context, builder.getBackgroundContentColorRes())
423 | : this.backgroundContentColor;
424 |
425 | this.shadowColor = builder.getShadowColorRes() != 0 ?
426 | ContextCompat.getColor(context, builder.getShadowColorRes()) :
427 | this.shadowColor;
428 |
429 | this.spacing = builder.getSpacingRes() != 0 ?
430 | (int) getResources().getDimension(builder.getSpacingRes())
431 | : this.spacing;
432 |
433 | this.circleBackgroundDrawableRes = builder.getCircleIndicatorBackgroundDrawableRes() != 0 ?
434 | builder.getCircleIndicatorBackgroundDrawableRes()
435 | : this.circleBackgroundDrawableRes;
436 |
437 | this.prevDrawable = builder.getPrevDrawableRes() != 0 ?
438 | getResources().getDrawable(builder.getPrevDrawableRes())
439 | : this.prevDrawable;
440 |
441 | this.nextDrawable = builder.getNextDrawableRes() != 0 ?
442 | getResources().getDrawable(builder.getNextDrawableRes())
443 | : this.nextDrawable;
444 |
445 | this.finishDrawable = builder.getFinishDrawableRes() != 0 ?
446 | getResources().getDrawable(builder.getFinishDrawableRes())
447 | : this.finishDrawable;
448 |
449 | this.skipDrawable = builder.getSkipDrawableRes() != 0 ?
450 | getResources().getDrawable(builder.getSkipDrawableRes())
451 | : this.skipDrawable;
452 |
453 | this.useCircleIndicator = builder.useCircleIndicator();
454 | this.hasSkipWord = builder.isUseSkipWord();
455 |
456 | this.isCancelable = builder.isClickable();
457 |
458 | if (builder.isUseArrow()) {
459 | this.arrowMargin = this.spacing / 3;
460 | this.arrowWidth = builder.getArrowWidth() != 0 ?
461 | (int) getResources().getDimension(builder.getArrowWidth())
462 | : this.arrowWidth;
463 | } else {
464 | this.arrowMargin = 0;
465 | this.arrowWidth = 0;
466 | }
467 |
468 | }
469 |
470 | private void initContent(Context context, ShowCaseBuilder builder) {
471 | this.viewGroup = (ViewGroup)
472 | LayoutInflater.from(context).inflate(this.layoutRes, this, false);
473 |
474 | int view_group_tutor_content = getResources().getIdentifier("view_group_tutor_content", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
475 | int text_title = getResources().getIdentifier("text_title", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
476 | int text_description = getResources().getIdentifier("text_description", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
477 | int view_line = getResources().getIdentifier("view_line", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
478 | int iv_previous = getResources().getIdentifier("iv_prev", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
479 | int iv_skip = getResources().getIdentifier("iv_skip", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
480 | int iv_next = getResources().getIdentifier("iv_next", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
481 | int view_group_indicator = getResources().getIdentifier("view_group_indicator", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
482 |
483 | View viewGroupTutorContent = viewGroup.findViewById(view_group_tutor_content);
484 | ViewHelper.setBackgroundColor(viewGroupTutorContent, this.backgroundContentColor);
485 |
486 | textViewTitle = (TextView) viewGroupTutorContent.findViewById(text_title);
487 |
488 | textViewTitle = (TextView) viewGroupTutorContent.findViewById(text_title);
489 | textViewTitle.setTextColor(this.titleTextColor);
490 | textViewTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, this.textTitleSize);
491 |
492 | textViewDesc = (TextView) viewGroupTutorContent.findViewById(text_description);
493 | textViewDesc.setTextColor(this.textColor);
494 | textViewDesc.setTextSize(TypedValue.COMPLEX_UNIT_PX, this.textSize);
495 |
496 | View line = viewGroupTutorContent.findViewById(view_line);
497 | if (line != null) {
498 | line.setBackgroundColor(lineColorRes);
499 | }
500 |
501 | prevButton = (ImageView) viewGroupTutorContent.findViewById(iv_previous);
502 | nextButton = (ImageView) viewGroupTutorContent.findViewById(iv_next);
503 | skipButton = (ImageView) viewGroupTutorContent.findViewById(iv_skip);
504 |
505 | viewGroupIndicator = (ViewGroup) viewGroupTutorContent.findViewById(view_group_indicator);
506 |
507 | if (prevButton != null) {
508 | prevButton.setImageDrawable(prevDrawable);
509 | prevButton.setOnClickListener(v -> {
510 | if (showCaseListener != null) ShowCaseLayout.this.showCaseListener.onPrevious();
511 | });
512 | }
513 | if (nextButton != null) {
514 | nextButton.setImageDrawable(nextDrawable);
515 | nextButton.setOnClickListener(v -> onNextClicked());
516 | }
517 |
518 | if (skipButton != null){
519 | skipButton.setImageDrawable(skipDrawable);
520 | skipButton.setOnClickListener(view -> {
521 | if (showCaseListener != null) ShowCaseLayout.this.showCaseListener.onComplete();
522 | });
523 | }
524 |
525 | this.addView(viewGroup);
526 | }
527 |
528 | private void moveViewBasedHighlight(int highlightXstart,
529 | int highlightYstart,
530 | int highlightXend,
531 | int highlightYend) {
532 | if (showCaseContentPosition == ShowCaseContentPosition.UNDEFINED) {
533 | int widthCenter = this.getWidth() / 2;
534 | int heightCenter = this.getHeight() / 2;
535 | if (highlightYend <= heightCenter) {
536 | showCaseContentPosition = ShowCaseContentPosition.BOTTOM;
537 | } else if (highlightYstart >= heightCenter) {
538 | showCaseContentPosition = ShowCaseContentPosition.TOP;
539 | } else if (highlightXend <= widthCenter) {
540 | showCaseContentPosition = ShowCaseContentPosition.RIGHT;
541 | } else if (highlightXstart >= widthCenter) {
542 | showCaseContentPosition = ShowCaseContentPosition.LEFT;
543 | } else { // not fit anywhere
544 | // if bottom is bigger, put to bottom, else put it on top
545 | if ((this.getHeight() - highlightYend) > highlightYstart) {
546 | showCaseContentPosition = ShowCaseContentPosition.BOTTOM;
547 | } else {
548 | showCaseContentPosition = ShowCaseContentPosition.TOP;
549 | }
550 | }
551 | }
552 |
553 | LayoutParams layoutParams;
554 | switch (showCaseContentPosition) {
555 | case RIGHT: {
556 | int expectedWidth = getWidth() - highlightXend - 2 * this.spacing;
557 |
558 | viewGroup.measure(MeasureSpec.makeMeasureSpec(expectedWidth, MeasureSpec.EXACTLY),
559 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
560 | int viewGroupHeight = viewGroup.getMeasuredHeight();
561 |
562 | layoutParams = new LayoutParams(
563 | expectedWidth,
564 | LayoutParams.WRAP_CONTENT,
565 | Gravity.RIGHT);
566 | layoutParams.rightMargin = this.spacing;
567 | layoutParams.leftMargin = this.spacing;
568 | layoutParams.bottomMargin = 0;
569 |
570 | // calculate diff top height between object and the content;
571 | int hightLightHeight = (highlightYend - highlightYstart);
572 | int diffHeight = hightLightHeight - viewGroupHeight;
573 |
574 | // check top margin. top should not out of window
575 | int expectedTopMargin = highlightYstart + diffHeight / 2;
576 | checkMarginTopBottom(expectedTopMargin, layoutParams, viewGroupHeight);
577 |
578 | setLayoutViewGroup(layoutParams);
579 |
580 | if (arrowWidth == 0) {
581 | path = null;
582 | } else {
583 | int highLightCenterY = (highlightYend + highlightYstart) / 2;
584 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterY, getHeight());
585 | if (recalcArrowWidth == 0) {
586 | path = null;
587 | } else {
588 | path = new Path();
589 | path.moveTo(highlightXend + this.arrowMargin, highLightCenterY);
590 | path.lineTo(highlightXend + this.spacing,
591 | highLightCenterY - arrowWidth / 2);
592 | path.lineTo(highlightXend + this.spacing,
593 | highLightCenterY + arrowWidth / 2);
594 | path.close();
595 | }
596 | }
597 | }
598 | break;
599 | case LEFT: {
600 | int expectedWidth = highlightXstart - 2 * this.spacing;
601 |
602 | viewGroup.measure(View.MeasureSpec.makeMeasureSpec(expectedWidth, View.MeasureSpec.EXACTLY),
603 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
604 | int viewGroupHeight = viewGroup.getMeasuredHeight();
605 |
606 | layoutParams = new LayoutParams(
607 | expectedWidth,
608 | LayoutParams.WRAP_CONTENT,
609 | Gravity.LEFT);
610 | layoutParams.leftMargin = this.spacing;
611 | layoutParams.rightMargin = this.spacing;
612 | layoutParams.bottomMargin = 0;
613 |
614 | // calculate diff top height between object and the content;
615 | int hightLightHeight = (highlightYend - highlightYstart);
616 | int diffHeight = hightLightHeight - viewGroupHeight;
617 |
618 | // check top margin. top should not out of window
619 | int expectedTopMargin = highlightYstart + diffHeight / 2;
620 | checkMarginTopBottom(expectedTopMargin, layoutParams, viewGroupHeight);
621 |
622 | setLayoutViewGroup(layoutParams);
623 |
624 | if (arrowWidth == 0) {
625 | path = null;
626 | } else {
627 | int highLightCenterY = (highlightYend + highlightYstart) / 2;
628 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterY, getHeight());
629 | if (recalcArrowWidth == 0) {
630 | path = null;
631 | } else {
632 | path = new Path();
633 | path.moveTo(highlightXstart - this.arrowMargin, highLightCenterY);
634 | path.lineTo(highlightXstart - this.spacing,
635 | highLightCenterY - arrowWidth / 2);
636 | path.lineTo(highlightXstart - this.spacing,
637 | highLightCenterY + arrowWidth / 2);
638 | path.close();
639 | }
640 | }
641 | }
642 | break;
643 | case BOTTOM: {
644 | layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
645 | LayoutParams.WRAP_CONTENT,
646 | Gravity.TOP);
647 | layoutParams.topMargin = highlightYend + this.spacing;
648 | layoutParams.leftMargin = this.spacing;
649 | layoutParams.rightMargin = this.spacing;
650 | layoutParams.bottomMargin = 0;
651 |
652 | setLayoutViewGroup(layoutParams);
653 |
654 | if (arrowWidth == 0) {
655 | path = null;
656 | } else {
657 | int highLightCenterX = (highlightXend + highlightXstart) / 2;
658 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterX, getWidth());
659 | if (recalcArrowWidth == 0) {
660 | path = null;
661 | } else {
662 | path = new Path();
663 | path.moveTo(highLightCenterX, highlightYend + this.arrowMargin);
664 | path.lineTo(highLightCenterX - recalcArrowWidth / 2,
665 | highlightYend + this.spacing);
666 | path.lineTo(highLightCenterX + recalcArrowWidth / 2,
667 | highlightYend + this.spacing);
668 | path.close();
669 | }
670 | }
671 | }
672 | break;
673 | case TOP: {
674 | layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
675 | LayoutParams.WRAP_CONTENT,
676 | Gravity.BOTTOM);
677 | layoutParams.bottomMargin = getHeight() - highlightYstart + this.spacing;
678 | layoutParams.topMargin = 0;
679 | layoutParams.leftMargin = this.spacing;
680 | layoutParams.rightMargin = this.spacing;
681 |
682 | setLayoutViewGroup(layoutParams);
683 |
684 | if (arrowWidth == 0) {
685 | path = null;
686 | } else {
687 | int highLightCenterX = (highlightXend + highlightXstart) / 2;
688 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterX, getWidth());
689 | if (recalcArrowWidth == 0) {
690 | path = null;
691 | } else {
692 | path = new Path();
693 | path.moveTo(highLightCenterX, highlightYstart - this.arrowMargin);
694 | path.lineTo(highLightCenterX - recalcArrowWidth / 2,
695 | highlightYstart - this.spacing);
696 | path.lineTo(highLightCenterX + recalcArrowWidth / 2,
697 | highlightYstart - this.spacing);
698 | path.close();
699 | }
700 | }
701 | }
702 | break;
703 | case UNDEFINED:
704 | moveViewToCenter();
705 | break;
706 | }
707 | }
708 |
709 | private void setLayoutViewGroup(LayoutParams params) {
710 | this.viewGroup.setVisibility(View.INVISIBLE);
711 |
712 | this.viewGroup.addOnLayoutChangeListener(new OnLayoutChangeListener() {
713 | @Override
714 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
715 | ShowCaseLayout.this.viewGroup.setVisibility(View.VISIBLE);
716 | ShowCaseLayout.this.viewGroup.removeOnLayoutChangeListener(this);
717 | }
718 | });
719 | this.viewGroup.setLayoutParams(params);
720 | invalidate();
721 | }
722 |
723 | private int getRecalculateArrowWidth(int highlightCenter, int maxWidthOrHeight) {
724 | int recalcArrowWidth = arrowWidth;
725 | int safeArrowWidth = this.spacing + (arrowWidth / 2);
726 | if (highlightCenter < safeArrowWidth ||
727 | highlightCenter > (maxWidthOrHeight - safeArrowWidth)) {
728 | recalcArrowWidth = 0;
729 | }
730 | return recalcArrowWidth;
731 | }
732 |
733 | private void moveViewToCenter() {
734 | showCaseContentPosition = ShowCaseContentPosition.UNDEFINED;
735 |
736 | LayoutParams layoutParams = new LayoutParams(
737 | LayoutParams.MATCH_PARENT,
738 | LayoutParams.WRAP_CONTENT,
739 | Gravity.CENTER);
740 | layoutParams.rightMargin = this.spacing;
741 | layoutParams.leftMargin = this.spacing;
742 | layoutParams.bottomMargin = this.spacing;
743 | layoutParams.topMargin = this.spacing;
744 |
745 | setLayoutViewGroup(layoutParams);
746 | this.path = null;
747 | }
748 |
749 | private void checkMarginTopBottom(int expectedTopMargin,
750 | LayoutParams layoutParams,
751 | int viewHeight) {
752 | if (expectedTopMargin < this.spacing) {
753 | layoutParams.topMargin = this.spacing;
754 | } else {
755 | // check bottom margin. bottom should not out of window
756 | int prevActualHeight = expectedTopMargin + viewHeight + this.spacing;
757 | if (prevActualHeight > getHeight()) {
758 | int diff = prevActualHeight - getHeight();
759 | layoutParams.topMargin = expectedTopMargin - diff;
760 | } else {
761 | layoutParams.topMargin = expectedTopMargin;
762 | }
763 | }
764 | }
765 |
766 | private void recycleResources() {
767 | if (this.bitmap != null) {
768 | this.bitmap.recycle();
769 | }
770 | this.bitmap = null;
771 | if (this.lastTutorialView != null) {
772 | this.lastTutorialView.setDrawingCacheEnabled(false);
773 | }
774 | this.lastTutorialView = null;
775 | this.viewPaint = null;
776 | }
777 |
778 | private Spanned fromHtml(String html) {
779 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
780 | return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
781 | } else {
782 | return Html.fromHtml(html);
783 | }
784 | }
785 | }
786 |
--------------------------------------------------------------------------------