├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── styles.xml │ │ │ └── strings.xml │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── dimorinny │ │ └── showcasesample │ │ └── MainActivity.java ├── proguard-rules.pro └── build.gradle.kts ├── showcasecard ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ └── style.xml │ │ ├── drawable-hdpi │ │ │ ├── background_showcase_left_top.9.png │ │ │ ├── background_showcase_right_top.9.png │ │ │ ├── background_showcase_left_bottom.9.png │ │ │ └── background_showcase_right_bottom.9.png │ │ ├── drawable-mdpi │ │ │ ├── background_showcase_left_top.9.png │ │ │ ├── background_showcase_right_top.9.png │ │ │ ├── background_showcase_left_bottom.9.png │ │ │ └── background_showcase_right_bottom.9.png │ │ ├── drawable-xhdpi │ │ │ ├── background_showcase_left_top.9.png │ │ │ ├── background_showcase_right_top.9.png │ │ │ ├── background_showcase_left_bottom.9.png │ │ │ └── background_showcase_right_bottom.9.png │ │ ├── drawable-xxhdpi │ │ │ ├── background_showcase_left_top.9.png │ │ │ ├── background_showcase_left_bottom.9.png │ │ │ ├── background_showcase_right_top.9.png │ │ │ └── background_showcase_right_bottom.9.png │ │ ├── drawable-xxxhdpi │ │ │ ├── background_showcase_left_top.9.png │ │ │ ├── background_showcase_right_top.9.png │ │ │ ├── background_showcase_left_bottom.9.png │ │ │ └── background_showcase_right_bottom.9.png │ │ └── layout │ │ │ └── item_show_case_content.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── dimorinny │ │ └── showcasecard │ │ ├── radius │ │ ├── ShowCaseRadius.java │ │ ├── Radius.java │ │ └── ViewRadius.java │ │ ├── position │ │ ├── ShowCasePosition.java │ │ ├── Position.java │ │ ├── TopLeft.java │ │ ├── TopLeftToolbar.java │ │ ├── TopRight.java │ │ ├── TopRightToolbar.java │ │ ├── BottomLeft.java │ │ ├── BottomRight.java │ │ ├── BottomCenter.java │ │ ├── Center.java │ │ └── ViewPosition.java │ │ ├── util │ │ ├── ActivityUtils.java │ │ ├── ViewUtils.java │ │ ├── MeasuredUtils.java │ │ └── NavigationBarUtils.java │ │ ├── widget │ │ └── WrapWidthTextView.java │ │ ├── step │ │ ├── ShowCaseStepScroller.java │ │ ├── ShowCaseStep.java │ │ └── ShowCaseStepDisplayer.java │ │ └── ShowCaseView.java ├── build.gradle.kts └── proguard-rules.pro ├── art ├── demo.gif └── logo.png ├── settings.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── README.md ├── .gitignore ├── gradlew.bat ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /showcasecard/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /art/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/art/demo.gif -------------------------------------------------------------------------------- /art/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/art/logo.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.buildFileName = "build.gradle.kts" 2 | 3 | include(":app") 4 | include(":showcasecard") -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /showcasecard/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | showcasecard 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /showcasecard/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 70dp 4 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/radius/ShowCaseRadius.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.radius; 2 | 3 | public interface ShowCaseRadius { 4 | float getRadius(); 5 | } 6 | -------------------------------------------------------------------------------- /showcasecard/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #80000000 4 | #333333 5 | -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-hdpi/background_showcase_left_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-hdpi/background_showcase_left_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-mdpi/background_showcase_left_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-mdpi/background_showcase_left_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-hdpi/background_showcase_right_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-hdpi/background_showcase_right_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-mdpi/background_showcase_right_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-mdpi/background_showcase_right_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xhdpi/background_showcase_left_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xhdpi/background_showcase_left_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xhdpi/background_showcase_right_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xhdpi/background_showcase_right_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxhdpi/background_showcase_left_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxhdpi/background_showcase_left_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-hdpi/background_showcase_left_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-hdpi/background_showcase_left_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-hdpi/background_showcase_right_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-hdpi/background_showcase_right_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-mdpi/background_showcase_left_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-mdpi/background_showcase_left_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-mdpi/background_showcase_right_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-mdpi/background_showcase_right_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xhdpi/background_showcase_left_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xhdpi/background_showcase_left_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xhdpi/background_showcase_right_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xhdpi/background_showcase_right_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxhdpi/background_showcase_left_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxhdpi/background_showcase_left_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxhdpi/background_showcase_right_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxhdpi/background_showcase_right_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_left_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_left_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_right_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_right_top.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxhdpi/background_showcase_right_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxhdpi/background_showcase_right_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_left_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_left_bottom.9.png -------------------------------------------------------------------------------- /showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_right_bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimorinny/show-case-card-view/HEAD/showcasecard/src/main/res/drawable-xxxhdpi/background_showcase_right_bottom.9.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 08 22:00:17 MSK 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /showcasecard/src/main/res/layout/item_show_case_content.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /showcasecard/src/main/res/values/style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/radius/Radius.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.radius; 2 | 3 | public class Radius implements ShowCaseRadius { 4 | 5 | private float radius = 0; 6 | 7 | public Radius(float radius) { 8 | this.radius = radius; 9 | } 10 | 11 | @Override 12 | public float getRadius() { 13 | return this.radius; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/ShowCasePosition.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Point; 5 | import android.graphics.PointF; 6 | import android.support.annotation.Nullable; 7 | import android.widget.ScrollView; 8 | 9 | public interface ShowCasePosition { 10 | 11 | PointF getPosition(Activity activity); 12 | 13 | @Nullable 14 | Point getScrollPosition(@Nullable ScrollView scrollView); 15 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Showcasesample 3 | Top Left 4 | Top Right 5 | Bottom left 6 | Bottom right 7 | Top left toolbar 8 | Top right toolbar 9 | View position 10 | List of steps 11 | An example view to scroll to 12 | 13 | -------------------------------------------------------------------------------- /showcasecard/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | 5 | android { 6 | compileSdkVersion(27) 7 | 8 | defaultConfig { 9 | minSdkVersion(15) 10 | targetSdkVersion(27) 11 | versionCode = 4 12 | versionName = "0.0.4" 13 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | getByName("release") { 17 | isMinifyEnabled = false 18 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | api("com.android.support:appcompat-v7:27.1.1") 25 | } -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/util/ActivityUtils.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.util; 2 | 3 | import android.app.Activity; 4 | 5 | public class ActivityUtils { 6 | 7 | public static int getOrientation(Activity activity) { 8 | return activity.getResources().getConfiguration().orientation; 9 | } 10 | 11 | public static int statusBarHeight(Activity activity) { 12 | int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android"); 13 | if (resourceId > 0) { 14 | return activity.getResources().getDimensionPixelSize(resourceId); 15 | } 16 | 17 | return 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/damerkurev/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /showcasecard/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/damerkurev/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/Position.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Point; 5 | import android.graphics.PointF; 6 | import android.support.annotation.Nullable; 7 | import android.widget.ScrollView; 8 | 9 | public class Position implements ShowCasePosition { 10 | 11 | private PointF position; 12 | 13 | public Position(PointF position) { 14 | this.position = position; 15 | } 16 | 17 | @Override 18 | public PointF getPosition(Activity activity) { 19 | return position; 20 | } 21 | 22 | @Nullable 23 | @Override 24 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/radius/ViewRadius.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.radius; 2 | 3 | import android.view.View; 4 | 5 | public class ViewRadius implements ShowCaseRadius { 6 | 7 | private View view; 8 | private float rate = .7F; 9 | 10 | public ViewRadius(View view, float rate) { 11 | this.view = view; 12 | this.rate = rate; 13 | } 14 | 15 | public ViewRadius(View view) { 16 | this.view = view; 17 | } 18 | 19 | @Override 20 | public float getRadius() { 21 | return Math.max(view.getHeight(), view.getWidth()) * rate; 22 | } 23 | 24 | public View getView() { 25 | return view; 26 | } 27 | 28 | public float getRate() { 29 | return rate; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/TopLeft.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Point; 5 | import android.graphics.PointF; 6 | import android.support.annotation.Nullable; 7 | import android.widget.ScrollView; 8 | 9 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 10 | 11 | public class TopLeft implements ShowCasePosition { 12 | 13 | @Override 14 | public PointF getPosition(Activity activity) { 15 | return new PointF( 16 | NavigationBarUtils.navigationBarMarginForLeftOrientation(activity), 17 | 0F 18 | ); 19 | } 20 | 21 | @Nullable 22 | @Override 23 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | } 4 | 5 | android { 6 | compileOptions { 7 | setSourceCompatibility(JavaVersion.VERSION_1_8) 8 | setTargetCompatibility(JavaVersion.VERSION_1_8) 9 | } 10 | 11 | compileSdkVersion(27) 12 | 13 | defaultConfig { 14 | applicationId = "ru.dimorinny.showcasesample" 15 | minSdkVersion(15) 16 | targetSdkVersion(27) 17 | versionCode = 1 18 | versionName = "0.0.1" 19 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" 20 | } 21 | buildTypes { 22 | getByName("release") { 23 | isMinifyEnabled = false 24 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 25 | } 26 | } 27 | } 28 | 29 | repositories { 30 | google() 31 | } 32 | 33 | dependencies { 34 | implementation(project(":showcasecard")) 35 | implementation("com.android.support:appcompat-v7:27.1.1") 36 | } -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/TopLeftToolbar.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.support.annotation.Nullable; 8 | import android.widget.ScrollView; 9 | 10 | import ru.dimorinny.showcasecard.util.ActivityUtils; 11 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 12 | 13 | public class TopLeftToolbar implements ShowCasePosition { 14 | 15 | @Override 16 | public PointF getPosition(Activity activity) { 17 | switch (ActivityUtils.getOrientation(activity)) { 18 | case Configuration.ORIENTATION_LANDSCAPE: 19 | return new PointF( 20 | NavigationBarUtils.navigationBarMarginForLeftOrientation(activity), 21 | (float) ActivityUtils.statusBarHeight(activity) 22 | ); 23 | default: 24 | return new PointF( 25 | 0F, 26 | ActivityUtils.statusBarHeight(activity) 27 | ); 28 | } 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/TopRight.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.support.annotation.Nullable; 8 | import android.widget.ScrollView; 9 | 10 | import ru.dimorinny.showcasecard.util.ActivityUtils; 11 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 12 | 13 | public class TopRight implements ShowCasePosition { 14 | 15 | @Override 16 | public PointF getPosition(Activity activity) { 17 | float width = (float) activity.getWindow().getDecorView().getWidth(); 18 | 19 | switch (ActivityUtils.getOrientation(activity)) { 20 | case Configuration.ORIENTATION_LANDSCAPE: 21 | return new PointF( 22 | width - NavigationBarUtils.navigationBarMarginForRightOrientation(activity), 23 | 0F 24 | ); 25 | default: 26 | return new PointF( 27 | width, 28 | 0F 29 | ); 30 | } 31 | } 32 | 33 | @Nullable 34 | @Override 35 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/TopRightToolbar.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.support.annotation.Nullable; 8 | import android.widget.ScrollView; 9 | 10 | import ru.dimorinny.showcasecard.util.ActivityUtils; 11 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 12 | 13 | public class TopRightToolbar implements ShowCasePosition { 14 | 15 | @Override 16 | public PointF getPosition(Activity activity) { 17 | float width = (float) activity.getWindow().getDecorView().getWidth(); 18 | 19 | switch (ActivityUtils.getOrientation(activity)) { 20 | case Configuration.ORIENTATION_LANDSCAPE: 21 | return new PointF( 22 | width - NavigationBarUtils.navigationBarMarginForRightOrientation(activity), 23 | (float) ActivityUtils.statusBarHeight(activity) 24 | ); 25 | default: 26 | return new PointF( 27 | width, 28 | ActivityUtils.statusBarHeight(activity) 29 | ); 30 | } 31 | } 32 | 33 | @Nullable 34 | @Override 35 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/BottomLeft.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.support.annotation.Nullable; 8 | import android.widget.ScrollView; 9 | 10 | import ru.dimorinny.showcasecard.util.ActivityUtils; 11 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 12 | 13 | public class BottomLeft implements ShowCasePosition { 14 | 15 | @Override 16 | public PointF getPosition(Activity activity) { 17 | switch (ActivityUtils.getOrientation(activity)) { 18 | case Configuration.ORIENTATION_LANDSCAPE: 19 | return new PointF( 20 | NavigationBarUtils.navigationBarMarginForLeftOrientation(activity), 21 | ((float) activity.getWindow().getDecorView().getHeight()) 22 | ); 23 | default: 24 | return new PointF( 25 | 0F, 26 | ((float) activity.getWindow().getDecorView().getHeight()) - 27 | ((float) NavigationBarUtils.navigationBarHeight(activity)) 28 | ); 29 | } 30 | } 31 | 32 | @Nullable 33 | @Override 34 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/BottomRight.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.support.annotation.Nullable; 8 | import android.widget.ScrollView; 9 | 10 | import ru.dimorinny.showcasecard.util.ActivityUtils; 11 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 12 | 13 | public class BottomRight implements ShowCasePosition { 14 | 15 | @Override 16 | public PointF getPosition(Activity activity) { 17 | float width = (float) activity.getWindow().getDecorView().getWidth(); 18 | float height = (float) activity.getWindow().getDecorView().getHeight(); 19 | 20 | switch (ActivityUtils.getOrientation(activity)) { 21 | case Configuration.ORIENTATION_LANDSCAPE: 22 | return new PointF( 23 | width - NavigationBarUtils.navigationBarMarginForRightOrientation(activity), 24 | height 25 | ); 26 | default: 27 | return new PointF( 28 | width, 29 | height - (float) NavigationBarUtils.navigationBarHeight(activity) 30 | ); 31 | } 32 | } 33 | 34 | @Nullable 35 | @Override 36 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/BottomCenter.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.support.annotation.Nullable; 8 | import android.widget.ScrollView; 9 | 10 | import ru.dimorinny.showcasecard.util.ActivityUtils; 11 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 12 | 13 | public class BottomCenter implements ShowCasePosition { 14 | 15 | @Override 16 | public PointF getPosition(Activity activity) { 17 | switch (ActivityUtils.getOrientation(activity)) { 18 | case Configuration.ORIENTATION_LANDSCAPE: 19 | return new PointF((activity.getWindow().getDecorView().getWidth() - 20 | NavigationBarUtils.navigationBarMarginForLeftOrientation(activity) - 21 | NavigationBarUtils.navigationBarMarginForRightOrientation(activity)) / 2, 22 | (float) activity.getWindow().getDecorView().getHeight()); 23 | default: 24 | return new PointF(activity.getWindow().getDecorView().getWidth() / 2, 25 | (float) activity.getWindow().getDecorView().getHeight() - (float) NavigationBarUtils.navigationBarHeight(activity)); 26 | } 27 | } 28 | 29 | @Nullable 30 | @Override 31 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 32 | return null; 33 | } 34 | } -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/util/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.util; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.PointF; 6 | import android.util.DisplayMetrics; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | public class ViewUtils { 11 | 12 | public static int convertDpToPx(View view, int dp) { 13 | return convertDpToPx(view.getContext(), dp); 14 | } 15 | 16 | public static int convertDpToPx(Context context, int dp) { 17 | Resources resources = context.getResources(); 18 | DisplayMetrics metrics = resources.getDisplayMetrics(); 19 | return Math.round(dp * (metrics.densityDpi / 160.0f)); 20 | } 21 | 22 | public static PointF getAbsoluteCenterPosition(View view) { 23 | int[] positions = new int[2]; 24 | view.getLocationOnScreen(positions); 25 | return new PointF( 26 | (float) (positions[0] + view.getWidth() / 2), 27 | (float) (positions[1] + view.getHeight() / 2) 28 | ); 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | public static T findViewWithType(ViewGroup viewGroup, Class clazz) { 33 | for (int i = 0; i < viewGroup.getChildCount() - 1; i++) { 34 | View view = viewGroup.getChildAt(i); 35 | 36 | if (view.getClass() == clazz) { 37 | return (T) view; 38 | } 39 | } 40 | 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/Center.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.support.annotation.Nullable; 8 | import android.widget.ScrollView; 9 | 10 | import ru.dimorinny.showcasecard.util.ActivityUtils; 11 | import ru.dimorinny.showcasecard.util.NavigationBarUtils; 12 | 13 | public class Center implements ShowCasePosition { 14 | 15 | @Override 16 | public PointF getPosition(Activity activity) { 17 | float y; 18 | switch (ActivityUtils.getOrientation(activity)) { 19 | case Configuration.ORIENTATION_LANDSCAPE: 20 | y = (float) activity.getWindow().getDecorView().getHeight() / 2; 21 | return new PointF((activity.getWindow().getDecorView().getWidth() - 22 | NavigationBarUtils.navigationBarMarginForLeftOrientation(activity) - 23 | NavigationBarUtils.navigationBarMarginForRightOrientation(activity)) / 2, 24 | y); 25 | default: 26 | y = (activity.getWindow().getDecorView().getHeight() - (float) NavigationBarUtils.navigationBarHeight(activity)) / 2; 27 | return new PointF(activity.getWindow().getDecorView().getWidth() / 2, 28 | y); 29 | } 30 | } 31 | 32 | @Nullable 33 | @Override 34 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 35 | return null; 36 | } 37 | } -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/widget/WrapWidthTextView.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatTextView; 5 | import android.text.Layout; 6 | import android.util.AttributeSet; 7 | 8 | public class WrapWidthTextView extends AppCompatTextView { 9 | 10 | public WrapWidthTextView(Context context) { 11 | super(context); 12 | } 13 | 14 | public WrapWidthTextView(Context context, AttributeSet attrs) { 15 | super(context, attrs); 16 | } 17 | 18 | public WrapWidthTextView(Context context, AttributeSet attrs, int defStyleAttr) { 19 | super(context, attrs, defStyleAttr); 20 | } 21 | 22 | @Override 23 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 24 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 25 | Layout layout = getLayout(); 26 | if (layout != null) { 27 | int width = (int) Math.ceil(getMaxLineWidth(layout)) 28 | + getCompoundPaddingLeft() + getCompoundPaddingRight(); 29 | int height = getMeasuredHeight(); 30 | setMeasuredDimension(width, height); 31 | } 32 | } 33 | 34 | private float getMaxLineWidth(Layout layout) { 35 | float max_width = 0.0f; 36 | int lines = layout.getLineCount(); 37 | for (int i = 0; i < lines; i++) { 38 | if (layout.getLineWidth(i) > max_width) { 39 | max_width = layout.getLineWidth(i); 40 | } 41 | } 42 | return max_width; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/position/ViewPosition.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.position; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Point; 5 | import android.graphics.PointF; 6 | import android.graphics.Rect; 7 | import android.support.annotation.Nullable; 8 | import android.view.View; 9 | import android.widget.ScrollView; 10 | 11 | import ru.dimorinny.showcasecard.util.ViewUtils; 12 | 13 | public class ViewPosition implements ShowCasePosition { 14 | 15 | private View view; 16 | 17 | public ViewPosition(View view) { 18 | this.view = view; 19 | } 20 | 21 | @Override 22 | public PointF getPosition(Activity activity) { 23 | return ViewUtils.getAbsoluteCenterPosition(view); 24 | } 25 | 26 | @Nullable 27 | @Override 28 | public Point getScrollPosition(@Nullable ScrollView scrollView) { 29 | 30 | if (scrollView == null || scrollView.findViewById(view.getId()) == null) { 31 | // scrollview not set, or child is not part of the scrollview content: do not scroll. 32 | return null; 33 | } 34 | 35 | // get the top of the item relative to the ScrollView: 36 | Rect offsetViewBounds = new Rect(); 37 | view.getDrawingRect(offsetViewBounds); 38 | scrollView.offsetDescendantRectToMyCoords(view, offsetViewBounds); 39 | int relativeTop = offsetViewBounds.top; 40 | 41 | // put the item in the middle of the screen: 42 | int scrollToY = relativeTop - (scrollView.getHeight() / 2); 43 | if (scrollToY < 0) { 44 | scrollToY = 0; 45 | } 46 | 47 | return new Point(scrollView.getScrollX(), scrollToY); 48 | } 49 | 50 | public View getView() { 51 | return view; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/util/MeasuredUtils.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.util; 2 | 3 | import android.view.View; 4 | import android.view.ViewTreeObserver; 5 | 6 | import java.util.List; 7 | 8 | public class MeasuredUtils { 9 | 10 | public interface OnMeasuredHandler { 11 | 12 | void onMeasured(); 13 | } 14 | 15 | public static void afterMeasured(final View view, final OnMeasuredHandler handler) { 16 | view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 17 | @Override 18 | public void onGlobalLayout() { 19 | if (view.getMeasuredWidth() > 0 && view.getMeasuredHeight() > 0) { 20 | view.getViewTreeObserver().removeGlobalOnLayoutListener(this); 21 | handler.onMeasured(); 22 | } 23 | } 24 | }); 25 | } 26 | 27 | public static void afterOrAlreadyMeasuredViews(final List views, final OnMeasuredHandler handler) { 28 | final int[] count = {views.size()}; 29 | 30 | if (count[0] <= 0) { 31 | handler.onMeasured(); 32 | } 33 | 34 | for (View view : views) { 35 | afterOrAlreadyMeasured(view, new OnMeasuredHandler() { 36 | @Override 37 | public void onMeasured() { 38 | count[0]--; 39 | if (count[0] <= 0) { 40 | handler.onMeasured(); 41 | } 42 | } 43 | }); 44 | } 45 | } 46 | 47 | public static void afterOrAlreadyMeasured(final View view, final OnMeasuredHandler handler) { 48 | if (view.getMeasuredWidth() != 0 && view.getMeasuredHeight() != 0) { 49 | handler.onMeasured(); 50 | } else { 51 | afterMeasured(view, new OnMeasuredHandler() { 52 | @Override 53 | public void onMeasured() { 54 | handler.onMeasured(); 55 | } 56 | }); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Demo](https://github.com/dimorinny/show-case-card-view/blob/master/art/demo.gif?raw=true) 2 | 3 | [![](https://jitpack.io/v/dimorinny/show-case-card-view.svg)](https://jitpack.io/#dimorinny/show-case-card-view) 4 | 5 | ## Dependency 6 | 7 | Firstly, add Jitpack repository in your root build.gradle file (not your module build.gradle file): 8 | 9 | ``` 10 | allprojects { 11 | repositories { 12 | ... 13 | maven { url "https://jitpack.io" } 14 | } 15 | } 16 | ``` 17 | 18 | Add dependency to your module's build.gradle file: 19 | 20 | ``` 21 | dependencies { 22 | implementation 'com.github.dimorinny:show-case-card-view:0.0.4' 23 | } 24 | ``` 25 | 26 | ## Usage 27 | 28 | You can display a ShowCase on your activity or fragment using the below code. 29 | 30 | To display a list of (click-through) steps: 31 | ```java 32 | new ShowCaseStepDisplayer.Builder(MainActivity.this) 33 | .addStep(new ShowCaseStep(new Center(), "Message at center")) 34 | .addStep(new ShowCaseStep(view, "Message at View")) 35 | .build().start(); 36 | ``` 37 | 38 | Use withScrollView() if some step's target Views could be inside a ScrollView, they will be auto-scrolled to: 39 | 40 | ```java 41 | new ShowCaseStepDisplayer.Builder(MainActivity.this) 42 | .withScrollView(scrollView) 43 | .addStep(new ShowCaseStep(view, "Message at View to scroll to")) 44 | .addStep(new ShowCaseStep(new TopLeft(), "Message at TopLeft")) 45 | .build().start(); 46 | ``` 47 | 48 | To display a single item: 49 | 50 | ```java 51 | new ShowCaseView.Builder(MainActivity.this) 52 | .withTypedPosition(new TopLeft()) 53 | .withTypedRadius(new Radius(186F)) 54 | .withContent("This is hello world!") 55 | .build() 56 | .show(this); 57 | ``` 58 | 59 | **Available positions:** 60 | 61 | * `Position(PointF position)` 62 | * `TopLeft()` 63 | * `TopRight()` 64 | * `BottomLeft()` 65 | * `BottomRight()` 66 | * `TopLeftToolbar()` 67 | * `TopRightToolbar()` 68 | * `ViewPosition(View view)` 69 | * `Center()` 70 | * `BottomCenter()` 71 | 72 | **Available radiuses:** 73 | 74 | * `Radius(float radius)` 75 | * `ViewRadius(View view)` 76 | 77 | For more complicated usage - see [example](https://github.com/dimorinny/show-case-card-view/blob/master/app/src/main/java/ru/dimorinny/showcasesample/MainActivity.java). -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/step/ShowCaseStepScroller.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.step; 2 | 3 | import android.graphics.Point; 4 | import android.support.annotation.NonNull; 5 | import android.widget.ScrollView; 6 | 7 | /** 8 | * Created by Frank on 2017/08/17. 9 | *

10 | * Handles scrolling to a {@link ShowCaseStep} if needed. 11 | */ 12 | public class ShowCaseStepScroller { 13 | 14 | @NonNull 15 | private ScrollView scrollView; 16 | private int lastTrackedScrollY; 17 | 18 | public ShowCaseStepScroller(@NonNull ScrollView scrollView) { 19 | this.scrollView = scrollView; 20 | } 21 | 22 | interface OnCompleteListener { 23 | 24 | void onComplete(); 25 | } 26 | 27 | interface OnScrollStoppedListener { 28 | 29 | void onScrollStopped(); 30 | } 31 | 32 | /** 33 | * Scrolls the given scrollView so that showCaseItem is completely in view. 34 | * 35 | * @param showCaseItem 36 | * @param onCompleteListener 37 | */ 38 | public void scrollToShowCaseStepItem(ShowCaseStep showCaseItem, final OnCompleteListener onCompleteListener) { 39 | 40 | Point scrollTo = showCaseItem.getPosition().getScrollPosition(scrollView); 41 | 42 | if (scrollTo == null) { 43 | // no scroll needed 44 | onCompleteListener.onComplete(); 45 | return; 46 | } 47 | 48 | scrollView.smoothScrollBy(scrollTo.x, scrollTo.y - scrollView.getScrollY()); 49 | 50 | detectScrollFinished(new OnScrollStoppedListener() { 51 | @Override 52 | public void onScrollStopped() { 53 | onCompleteListener.onComplete(); 54 | } 55 | }); 56 | } 57 | 58 | private void detectScrollFinished(final OnScrollStoppedListener onScrollStoppedListener) { 59 | 60 | lastTrackedScrollY = scrollView.getScrollY(); 61 | 62 | final int checkIntervalMs = 50; 63 | 64 | scrollView.postDelayed(new Runnable() { 65 | 66 | public void run() { 67 | int newPosition = scrollView.getScrollY(); 68 | 69 | if (lastTrackedScrollY - newPosition == 0) { 70 | onScrollStoppedListener.onScrollStopped(); 71 | } else { 72 | lastTrackedScrollY = scrollView.getScrollY(); 73 | scrollView.postDelayed(this, checkIntervalMs); 74 | } 75 | } 76 | }, checkIntervalMs); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/macos,android,intellij 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.ap_ 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # Intellij 40 | *.iml 41 | .idea/ 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json 56 | 57 | ### Android Patch ### 58 | gen-external-apklibs 59 | 60 | ### Intellij ### 61 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 62 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 63 | 64 | # CMake 65 | cmake-build-debug/ 66 | 67 | ## File-based project format: 68 | *.iws 69 | 70 | ## Plugin-specific files: 71 | 72 | # IntelliJ 73 | /out/ 74 | 75 | # mpeltonen/sbt-idea plugin 76 | .idea_modules/ 77 | 78 | # JIRA plugin 79 | atlassian-ide-plugin.xml 80 | 81 | # Cursive Clojure plugin 82 | .idea/replstate.xml 83 | 84 | # Crashlytics plugin (for Android Studio and IntelliJ) 85 | com_crashlytics_export_strings.xml 86 | crashlytics.properties 87 | crashlytics-build.properties 88 | fabric.properties 89 | 90 | ### Intellij Patch ### 91 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 92 | 93 | # *.iml 94 | # modules.xml 95 | # *.ipr 96 | 97 | ### macOS ### 98 | *.DS_Store 99 | .AppleDouble 100 | .LSOverride 101 | 102 | # Icon must end with two \r 103 | Icon 104 | 105 | # Thumbnails 106 | ._* 107 | 108 | # Files that might appear in the root of a volume 109 | .DocumentRevisions-V100 110 | .fseventsd 111 | .Spotlight-V100 112 | .TemporaryItems 113 | .Trashes 114 | .VolumeIcon.icns 115 | .com.apple.timemachine.donotpresent 116 | 117 | # Directories potentially created on remote AFP share 118 | .AppleDB 119 | .AppleDesktop 120 | Network Trash Folder 121 | Temporary Items 122 | .apdisk 123 | 124 | # End of https://www.gitignore.io/api/macos,android,intellij -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/util/NavigationBarUtils.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.util; 2 | 3 | import android.app.Activity; 4 | import android.os.Build; 5 | import android.view.Gravity; 6 | import android.widget.FrameLayout; 7 | 8 | public class NavigationBarUtils { 9 | 10 | public enum NavigationBarPosition { 11 | BOTTOM, 12 | LEFT, 13 | RIGHT, 14 | UNKNOWN 15 | } 16 | 17 | public static int navigationBarHeight(Activity activity) { 18 | int resourceId = activity.getResources().getIdentifier("navigation_bar_height", "dimen", "android"); 19 | 20 | if (hasNavigationBar(activity) && resourceId > 0) { 21 | return activity.getResources().getDimensionPixelSize(resourceId); 22 | } 23 | 24 | return 0; 25 | } 26 | 27 | public static NavigationBarPosition navigationBarPosition(final Activity activity) { 28 | NavigationBarPosition result = NavigationBarPosition.UNKNOWN; 29 | 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && hasNavigationBar(activity)) { 31 | int gravity = ((FrameLayout.LayoutParams) activity.findViewById(android.R.id.navigationBarBackground) 32 | .getLayoutParams()).gravity; 33 | return gravityToNavigationBarPosition(gravity); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | private static boolean hasNavigationBar(Activity activity) { 40 | int id = activity.getResources().getIdentifier("config_showNavigationBar", "bool", "android"); 41 | 42 | return !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP 43 | && activity.findViewById(android.R.id.navigationBarBackground) == null) && 44 | id > 0 && activity.getResources().getBoolean(id); 45 | } 46 | 47 | private static NavigationBarPosition gravityToNavigationBarPosition(int gravity) { 48 | switch (gravity) { 49 | case Gravity.BOTTOM: 50 | return NavigationBarPosition.BOTTOM; 51 | case Gravity.LEFT: 52 | return NavigationBarPosition.LEFT; 53 | case Gravity.RIGHT: 54 | return NavigationBarPosition.RIGHT; 55 | default: 56 | return NavigationBarPosition.UNKNOWN; 57 | } 58 | } 59 | 60 | public static float navigationBarMarginForRightOrientation(Activity activity) { 61 | if (NavigationBarUtils.navigationBarPosition(activity) == NavigationBarUtils.NavigationBarPosition.LEFT) { 62 | return 0F; 63 | } else { 64 | return NavigationBarUtils.navigationBarHeight(activity); 65 | } 66 | } 67 | 68 | public static float navigationBarMarginForLeftOrientation(Activity activity) { 69 | if (NavigationBarUtils.navigationBarPosition(activity) == NavigationBarUtils.NavigationBarPosition.LEFT) { 70 | return NavigationBarUtils.navigationBarHeight(activity); 71 | } else { 72 | return 0F; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /showcasecard/src/main/java/ru/dimorinny/showcasecard/step/ShowCaseStep.java: -------------------------------------------------------------------------------- 1 | package ru.dimorinny.showcasecard.step; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.ColorRes; 5 | 6 | import ru.dimorinny.showcasecard.R; 7 | import ru.dimorinny.showcasecard.position.ShowCasePosition; 8 | import ru.dimorinny.showcasecard.radius.Radius; 9 | import ru.dimorinny.showcasecard.radius.ShowCaseRadius; 10 | import ru.dimorinny.showcasecard.util.ViewUtils; 11 | 12 | public class ShowCaseStep { 13 | 14 | private ShowCasePosition position; 15 | private ShowCaseRadius radius; 16 | private String message; 17 | 18 | @ColorRes 19 | private int color; 20 | 21 | public ShowCasePosition getPosition() { 22 | return position; 23 | } 24 | 25 | public ShowCaseRadius getRadius() { 26 | return radius; 27 | } 28 | 29 | public String getMessage() { 30 | return message; 31 | } 32 | 33 | public @ColorRes 34 | int getColor() { 35 | return color; 36 | } 37 | 38 | public static class Builder { 39 | 40 | private final static int DEFAULT_RADIUS_DP = 70; 41 | 42 | private ShowCaseRadius radius; 43 | private ShowCasePosition position; 44 | private String message; 45 | 46 | @ColorRes 47 | private int color = R.color.black20; 48 | 49 | public ShowCaseStep.Builder withTypedRadius(ShowCaseRadius radius) { 50 | this.radius = radius; 51 | return this; 52 | } 53 | 54 | public ShowCaseStep.Builder withTypedPosition(ShowCasePosition position) { 55 | this.position = position; 56 | return this; 57 | } 58 | 59 | public ShowCaseStep.Builder withMessage(String message) { 60 | this.message = message; 61 | return this; 62 | } 63 | 64 | public ShowCaseStep.Builder withColor(@ColorRes int overlayColor) { 65 | color = overlayColor; 66 | return this; 67 | } 68 | 69 | public ShowCaseStep build(Context context) { 70 | checkRequiredFields(); 71 | 72 | ShowCaseStep step = new ShowCaseStep(); 73 | if (radius == null) { 74 | radius = new Radius( 75 | ViewUtils.convertDpToPx(context, DEFAULT_RADIUS_DP) 76 | ); 77 | } 78 | step.position = position; 79 | step.radius = radius; 80 | step.message = message; 81 | step.color = color; 82 | 83 | return step; 84 | } 85 | 86 | private void checkRequiredFields() { 87 | if (position == null) { 88 | throw new IllegalArgumentException( 89 | "position is required field for ShowCaseStep builder" 90 | ); 91 | } 92 | 93 | if (message == null) { 94 | throw new IllegalArgumentException( 95 | "message is required field for ShowCaseStep builder" 96 | ); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 |