Build beautiful onboarding flows, feature highlights, guided tours, coach marks, and contextual user education screens with a clean Java API.
VSpot v4.0.0 is a modern, production-ready Android spotlight overlay library designed for onboarding screens, feature discovery, app walkthroughs, and interactive product tours.
It is lightweight, Java-friendly, customizable, and built for real Android apps that need a polished onboarding experience without heavy dependencies.
- Overview
- What is VSpot?
- What is New in v4.0.0
- Why Use VSpot?
- Installation
- Quick Start
- Single Spotlight Example
- Multi-Step Product Tour
- Professional Theme Example
- Per-Step Customization
- Callbacks
- Manual Controls
- Spannable Content
- Dismiss Modes
- Gravity Options
- Spotlight Shapes
- Style API
- Advanced Customization Options
- Builder API
- Migration Guide
- Best Practices
- Troubleshooting
- Changelog
- License
- Support
VSpot helps Android developers highlight important UI elements with a beautiful dimmed overlay, spotlight cutout, message card, step indicator, connector line, and optional action controls.
Use it for:
- App onboarding
- Product walkthroughs
- Feature discovery
- First-time user education
- Coach marks
- Highlighting buttons, cards, forms, icons, images, and custom views
- Explaining new updates after an app release
- Guiding users through complex screens
VSpot is built for the classic Android View system and works smoothly with Java-based Android projects.
VSpot creates an overlay above your Activity and highlights a target view using a transparent spotlight area.
It can show a message card near the target view with:
- Title
- Description
- Step number
- Back button
- Next button
- Done button
- Skip button
- Connector line
- Target stroke
- Pulse effect
You can show one spotlight or create a complete multi-step guided tour.
Version 4.0.0 is a major professional upgrade focused on real-world production usage, cleaner APIs, safer lifecycle handling, and better UI customization.
| Feature | Description |
|---|---|
| Multi-step tours | Create full onboarding flows using addStep(...). |
| Modern message card | Rounded, elevated, Material-style message card. |
| Step indicator | Shows progress like 1 / 5. |
| Action buttons | Built-in Back, Next, Done, and Skip controls. |
| Circle spotlight | Perfect for icons, avatars, profile images, and FABs. |
| Rounded rectangle spotlight | Great for buttons, cards, inputs, and layouts. |
| Connector line | Draws a clean line between target and message card. |
| Target stroke | Adds a professional border around the highlighted target. |
| Pulse effect | Adds subtle focus around the target area. |
| Per-step settings | Each step can have its own shape, padding, gravity, and dismiss mode. |
| Theme API | Customize colors, radius, text sizes, card width, animation, and controls. |
| Accessibility support | Announces step content for accessibility services. |
| Safe layout listener cleanup | Prevents unnecessary listener leaks. |
| Better drawing performance | No bitmap allocation inside onDraw(). |
| Safer touch handling | Uses overlay-local target/message coordinates. |
| Backward compatible API | Existing setTargetView, setTitle, setContentText, and old enum names still work. |
Many onboarding libraries are either too heavy, outdated, hard to style, or focused only on simple one-time hints. VSpot v4.0.0 is designed to be small, flexible, and production-friendly.
- Simple Java API
- No complex setup
- No XML dependency required for the overlay
- Works with any existing View
- Supports single and multi-step flows
- Clean callbacks for analytics and app logic
- Customizable without subclassing
- Easy to publish as a reusable Android library
- Professional spotlight overlay
- Attractive message cards
- Smooth animations
- Clear visual connection between message and target
- User-friendly Back, Next, Skip, and Done flow
- Better onboarding completion experience
Add Maven Central in your project-level settings.gradle or settings.gradle.kts.
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "YourProjectName"
include ':app'Add VSpot in your app module build.gradle:
dependencies {
implementation "io.github.tutorialsandroid:vspot:4.0.0"
}pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "YourProjectName"
include(":app")Add VSpot in your app module build.gradle.kts:
dependencies {
implementation("io.github.tutorialsandroid:vspot:4.0.0")
}Add JitPack in your project-level settings.gradle:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}Add the dependency:
dependencies {
implementation "com.github.TutorialsAndroid:VSpot:4.0.0"
}For Kotlin DSL:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}dependencies {
implementation("com.github.TutorialsAndroid:VSpot:4.0.0")
}| Requirement | Value |
|---|---|
| Platform | Android |
| Language | Java |
| UI system | Android View system |
| Dependency style | Maven Central / JitPack / local module |
| AndroidX required by library | No |
| Activity context required | Yes |
VSpot must be created using an Activity context because it attaches the overlay to the Activity decor view.
Import VSpot:
import com.developer.spoti.vspoti.VSpotView;Show a spotlight:
View targetButton = findViewById(R.id.btnContinue);
new VSpotView.Builder(this)
.setTargetView(targetButton)
.setTitle("Continue Button")
.setContentText("Tap here to continue to the next step.")
.setGravity(VSpotView.Gravity.center)
.setDismissType(VSpotView.DismissType.targetView)
.show();That is it. VSpot will automatically attach itself above your Activity content and highlight the selected view.
Use this when you want to explain one specific UI element.
final View profileButton = findViewById(R.id.profileButton);
VSpotView spot = new VSpotView.Builder(this)
.setTargetView(profileButton)
.setTitle("Your Profile")
.setContentText("Manage your account, preferences, and app settings from here.")
.setGravity(VSpotView.Gravity.center)
.setDismissType(VSpotView.DismissType.outside)
.setAccentColor(0xFF6750A4)
.build();
spot.show();Use addStep(...) to build a complete onboarding tour.
View searchView = findViewById(R.id.searchView);
View filterButton = findViewById(R.id.filterButton);
View profileButton = findViewById(R.id.profileButton);
View createButton = findViewById(R.id.createButton);
VSpotView tour = new VSpotView.Builder(this)
.addStep(searchView,
"Smart Search",
"Quickly find content, users, or features using the search bar.")
.addStep(filterButton,
"Powerful Filters",
"Refine your results with category, date, and status filters.")
.addStep(profileButton,
"Your Profile",
"Manage your personal information and app preferences.")
.addStep(createButton,
"Create New Item",
"Tap here whenever you want to create something new.")
.setDismissType(VSpotView.DismissType.none)
.setGravity(VSpotView.Gravity.center)
.setAccentColor(0xFF6750A4)
.show();For guided onboarding, it is better to keep users inside the tour flow and let them use Next, Back, Done, or Skip. This prevents accidental dismissals.
You can create a premium-looking branded tour by customizing the visual style.
VSpotView.Style style = new VSpotView.Style();
style.overlayColor = 0xE6000000;
style.cardColor = 0xFFFFFFFF;
style.cardStrokeColor = 0x1F000000;
style.titleColor = 0xFF111827;
style.contentColor = 0xFF4B5563;
style.accentColor = 0xFF6750A4;
style.stepBadgeColor = 0xFF6750A4;
style.stepBadgeTextColor = 0xFFFFFFFF;
style.connectorColor = 0xFFFFFFFF;
style.targetStrokeColor = 0xFFFFFFFF;
style.targetPulseColor = 0x66FFFFFF;
style.cardCornerRadiusDp = 24f;
style.cardElevationDp = 14f;
style.cardShadowDyDp = 5f;
style.targetCornerRadiusDp = 18f;
style.spotlightPaddingDp = 10f;
style.maxMessageWidthDp = 360;
style.titleTextSizeSp = 19;
style.contentTextSizeSp = 14;
style.buttonTextSizeSp = 14;
style.stepTextSizeSp = 12;
style.showControls = true;
style.showConnector = true;
style.showTargetStroke = true;
style.showPulse = true;
style.showStepIndicator = true;
style.showPreviousButton = true;
style.showSkipButton = true;
style.previousButtonText = "Back";
style.nextButtonText = "Next";
style.doneButtonText = "Done";
style.skipButtonText = "Skip";
style.animationDuration = 260L;
VSpotView tour = new VSpotView.Builder(this)
.setStyle(style)
.addStep(findViewById(R.id.view1), "Welcome", "Let us quickly show you around.")
.addStep(findViewById(R.id.view2), "Explore", "This section helps you discover important actions.")
.addStep(findViewById(R.id.view3), "You are ready", "Start using the app with confidence.")
.setDismissType(VSpotView.DismissType.none)
.show();Each step can override global settings such as shape, padding, gravity, and dismiss behavior.
VSpotView.Step searchStep = new VSpotView.Step.Builder(findViewById(R.id.searchView))
.setTitle("Search Anything")
.setContentText("Use this search field to quickly find what you need.")
.setGravity(VSpotView.Gravity.center)
.setSpotlightShape(VSpotView.SpotlightShape.roundedRectangle)
.setSpotlightPaddingDp(8)
.build();
VSpotView.Step avatarStep = new VSpotView.Step.Builder(findViewById(R.id.avatarView))
.setTitle("Profile")
.setContentText("Tap your avatar to open account settings.")
.setGravity(VSpotView.Gravity.right)
.setSpotlightShape(VSpotView.SpotlightShape.circle)
.setSpotlightPaddingDp(10)
.build();
new VSpotView.Builder(this)
.addStep(searchStep)
.addStep(avatarStep)
.setDismissType(VSpotView.DismissType.none)
.show();Use callbacks to track analytics, update UI, or run logic when the tour starts, changes step, clicks the target, or ends.
VSpotView tour = new VSpotView.Builder(this)
.addStep(findViewById(R.id.view1), "Step One", "This is the first feature.")
.addStep(findViewById(R.id.view2), "Step Two", "This is the second feature.")
.setCallback(new VSpotView.Callback() {
@Override
public void onShow(VSpotView spotView) {
// Tour became visible.
}
@Override
public void onStepChanged(View targetView, int stepIndex, VSpotView.Step step) {
// Called whenever the active step changes.
// Useful for analytics: onboarding_step_viewed.
}
@Override
public void onTargetClick(View targetView, int stepIndex, VSpotView.Step step) {
// Called when the target view is clicked using DismissType.targetView.
}
@Override
public void onDismiss(View lastTargetView, boolean completed, int lastStepIndex) {
if (completed) {
// User completed the tour.
} else {
// User skipped or dismissed the tour.
}
}
})
.show();VSpot exposes useful control methods for advanced use cases.
VSpotView tour = new VSpotView.Builder(this)
.addStep(findViewById(R.id.view1), "One", "First step")
.addStep(findViewById(R.id.view2), "Two", "Second step")
.addStep(findViewById(R.id.view3), "Three", "Third step")
.build();
tour.show();
// Go to next step
tour.next();
// Go to previous step
tour.previous();
// Jump to a specific step
tour.goToStep(2);
// Finish as completed
tour.finish();
// Dismiss without completion
tour.dismiss();Useful getters:
boolean showing = tour.isShowing();
int currentIndex = tour.getCurrentIndex();
int totalSteps = tour.getStepCount();
VSpotView.Step currentStep = tour.getCurrentStep();
View currentTarget = tour.getCurrentTargetView();You can pass rich text using Spannable or any CharSequence.
SpannableString content = new SpannableString("Use filters to save time and find better results.");
content.setSpan(
new StyleSpan(Typeface.BOLD),
4,
11,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);
new VSpotView.Builder(this)
.setTargetView(findViewById(R.id.filterButton))
.setTitle("Smart Filters")
.setContentSpan(content)
.show();Required imports:
import android.graphics.Typeface;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.StyleSpan;VSpot supports multiple dismiss behaviors.
| Dismiss Type | Behavior |
|---|---|
DismissType.none |
User cannot dismiss by touching overlay. Best for controlled tours. |
DismissType.outside |
Dismisses when user taps outside the message card. |
DismissType.anywhere |
Dismisses when user taps anywhere outside the card. |
DismissType.targetView |
Performs target click, then dismisses. |
Both lowercase and uppercase enum names are supported:
VSpotView.DismissType.none
VSpotView.DismissType.NONE
VSpotView.DismissType.outside
VSpotView.DismissType.OUTSIDE
VSpotView.DismissType.anywhere
VSpotView.DismissType.ANYWHERE
VSpotView.DismissType.targetView
VSpotView.DismissType.TARGET_VIEWRecommended usage:
// Best for onboarding tours
.setDismissType(VSpotView.DismissType.none)
// Best for simple hints
.setDismissType(VSpotView.DismissType.outside)
// Best when you want target click behavior
.setDismissType(VSpotView.DismissType.targetView)Gravity controls horizontal placement of the message card around the highlighted target.
| Gravity | Behavior |
|---|---|
Gravity.auto |
Default smart positioning. |
Gravity.center |
Centers message card to target center. |
Gravity.left |
Aligns message card to target left. |
Gravity.right |
Aligns message card to target right. |
Both lowercase and uppercase enum names are supported:
VSpotView.Gravity.auto
VSpotView.Gravity.center
VSpotView.Gravity.left
VSpotView.Gravity.right
VSpotView.Gravity.AUTO
VSpotView.Gravity.CENTER
VSpotView.Gravity.LEFT
VSpotView.Gravity.RIGHTExample:
new VSpotView.Builder(this)
.setTargetView(findViewById(R.id.settingsButton))
.setTitle("Settings")
.setContentText("Manage your preferences here.")
.setGravity(VSpotView.Gravity.right)
.show();VSpot supports two spotlight shapes.
| Shape | Best For |
|---|---|
roundedRectangle |
Buttons, cards, input fields, layout blocks. |
circle |
Icons, avatars, FABs, small image buttons. |
Both lowercase and uppercase enum names are supported:
VSpotView.SpotlightShape.roundedRectangle
VSpotView.SpotlightShape.circle
VSpotView.SpotlightShape.ROUNDED_RECTANGLE
VSpotView.SpotlightShape.CIRCLEExample:
new VSpotView.Builder(this)
.setTargetView(findViewById(R.id.fabAdd))
.setTitle("Create")
.setContentText("Tap here to create a new item.")
.setSpotlightShape(VSpotView.SpotlightShape.circle)
.show();VSpotView.Style gives full control over the visual design.
| Property | Description |
|---|---|
overlayColor |
Dim overlay color. |
cardColor |
Message card background color. |
cardStrokeColor |
Message card border color. |
titleColor |
Title text color. |
contentColor |
Content text color. |
accentColor |
Primary action/accent color. |
connectorColor |
Connector line color. |
targetStrokeColor |
Highlighted target border color. |
targetPulseColor |
Target pulse color. |
stepBadgeColor |
Step indicator badge background. |
stepBadgeTextColor |
Step indicator badge text color. |
| Property | Description |
|---|---|
cardCornerRadiusDp |
Message card corner radius. |
cardStrokeWidthDp |
Message card border width. |
cardElevationDp |
Message card shadow/elevation strength. |
cardShadowDyDp |
Vertical card shadow offset. |
targetCornerRadiusDp |
Target cutout corner radius. |
targetStrokeWidthDp |
Highlighted target stroke width. |
spotlightPaddingDp |
Space around highlighted view. |
connectorWidthDp |
Connector line width. |
connectorDotRadiusDp |
Connector dot radius. |
indicatorGapDp |
Gap between target and message card. |
screenHorizontalMarginDp |
Horizontal safe margin. |
screenVerticalMarginDp |
Vertical safe margin. |
maxMessageWidthDp |
Maximum message card width. |
| Property | Description |
|---|---|
titleTextSizeSp |
Title text size. |
contentTextSizeSp |
Content text size. |
buttonTextSizeSp |
Button text size. |
stepTextSizeSp |
Step badge text size. |
titleTypeface |
Title typeface. |
contentTypeface |
Content typeface. |
| Property | Description |
|---|---|
showControls |
Show or hide action buttons. |
showPreviousButton |
Show or hide Back button. |
showSkipButton |
Show or hide Skip button. |
showStepIndicator |
Show or hide step badge. |
showConnector |
Show or hide connector line. |
showTargetStroke |
Show or hide target border. |
showPulse |
Show or hide pulse outline. |
| Property | Default |
|---|---|
previousButtonText |
Back |
nextButtonText |
Next |
doneButtonText |
Done |
skipButtonText |
Skip |
| Property | Description |
|---|---|
animationDuration |
Overlay animation duration in milliseconds. |
startScale |
Overlay scale at show start. |
endScale |
Overlay scale at dismiss end. |
| Method | Description |
|---|---|
setTargetView(View view) |
Sets target view for a single spotlight. |
setTitle(String title) |
Sets title for single spotlight mode. |
setContentText(String contentText) |
Sets plain text content. |
setContentSpan(Spannable span) |
Sets spannable content. |
setContent(CharSequence content) |
Sets any CharSequence content. |
addStep(View targetView, String title, String contentText) |
Adds a tour step with plain string content. |
addStep(View targetView, String title, CharSequence content) |
Adds a tour step with rich text content. |
addStep(VSpotView.Step step) |
Adds a fully customized step. |
setGravity(Gravity gravity) |
Sets default message gravity. |
setDismissType(DismissType dismissType) |
Sets default dismiss behavior. |
setCallback(VSpotView.Callback callback) |
Sets lifecycle callbacks. |
setStyle(VSpotView.Style style) |
Applies complete custom style. |
build() |
Builds a VSpotView instance. |
show() |
Builds and immediately shows VSpot. |
| Method | Description |
|---|---|
setOverlayColor(int color) |
Sets overlay color. |
setCardColor(int color) |
Sets card color. |
setTitleColor(int color) |
Sets title color. |
setContentColor(int color) |
Sets content color. |
setAccentColor(int color) |
Sets action/step accent color. |
setConnectorColor(int color) |
Sets connector line color. |
setTargetStrokeColor(int color) |
Sets highlighted target stroke color. |
setCardCornerRadiusDp(float radiusDp) |
Sets card corner radius. |
setTargetCornerRadiusDp(float radiusDp) |
Sets target corner radius. |
setSpotlightPaddingDp(float paddingDp) |
Sets global spotlight padding. |
setSpotlightShape(SpotlightShape shape) |
Sets global spotlight shape. |
setMaxMessageWidthDp(int widthDp) |
Sets max card width. |
setShowControls(boolean showControls) |
Shows/hides action controls. |
setShowConnector(boolean showConnector) |
Shows/hides connector line. |
setShowStepIndicator(boolean showStepIndicator) |
Shows/hides step badge. |
setShowSkipButton(boolean showSkipButton) |
Shows/hides skip button. |
setShowPreviousButton(boolean showPreviousButton) |
Shows/hides previous button. |
setButtonTexts(String previous, String next, String done, String skip) |
Customizes button labels. |
setAnimationDuration(long durationMillis) |
Sets animation duration. |
VSpot v4.0.0 provides a complete styling API for developers who want full control over the spotlight overlay, message card, typography, indicator, highlight box, and content alignment.
These options are useful when you want VSpot to match your app branding or when you need different visual styles for different onboarding flows.
You can customize title and content fonts using Android Typeface.
This supports:
- Normal
- Bold
- Italic
- Bold Italic
- Custom typefaces
VSpotView.Style style = new VSpotView.Style();
style.titleTypeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
style.contentTypeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
new VSpotView.Builder(this)
.setStyle(style)
.setTargetView(myView)
.setTitle("Custom Typeface")
.setContentText("Title and content can use different typefaces.")
.show();Required import:
import android.graphics.Typeface;You can set different colors for the title and content text.
VSpotView.Style style = new VSpotView.Style();
style.titleColor = 0xFF111827;
style.contentColor = 0xFF4B5563;
new VSpotView.Builder(this)
.setStyle(style)
.setTargetView(myView)
.setTitle("Custom Text Colors")
.setContentText("VSpot lets you control title and content text colors.")
.show();You can also use fast builder methods:
new VSpotView.Builder(this)
.setTargetView(myView)
.setTitle("Custom Colors")
.setContentText("Set title and content colors directly from the builder.")
.setTitleColor(0xFF111827)
.setContentColor(0xFF4B5563)
.show();The overlay darkness can be customized using overlayColor.
Use the alpha value to control intensity.
VSpotView.Style style = new VSpotView.Style();
// Light dark overlay
style.overlayColor = 0x99000000;
// Medium dark overlay
style.overlayColor = 0xCC000000;
// Strong dark overlay
style.overlayColor = 0xE6000000;
new VSpotView.Builder(this)
.setStyle(style)
.setTargetView(myView)
.setTitle("Overlay Intensity")
.setContentText("Control how dark the non-highlighted area should be.")
.show();Builder shortcut:
new VSpotView.Builder(this)
.setTargetView(myView)
.setTitle("Dark Overlay")
.setContentText("The background intensity is fully customizable.")
.setOverlayColor(0xE6000000)
.show();You can customize the highlighted target border color using targetStrokeColor.
VSpotView.Style style = new VSpotView.Style();
style.targetStrokeColor = 0xFFFFFFFF;
style.targetPulseColor = 0x66FFFFFF;
style.showTargetStroke = true;
style.showPulse = true;
new VSpotView.Builder(this)
.setStyle(style)
.setTargetView(myView)
.setTitle("Highlight Box")
.setContentText("Customize the border and pulse around the highlighted view.")
.show();Builder shortcut:
new VSpotView.Builder(this)
.setTargetView(myView)
.setTitle("Custom Highlight")
.setContentText("The target highlight border color can be customized.")
.setTargetStrokeColor(0xFFFFFFFF)
.show();The step indicator badge can be styled using stepBadgeColor and stepBadgeTextColor.
VSpotView.Style style = new VSpotView.Style();
style.stepBadgeColor = 0xFF6750A4;
style.stepBadgeTextColor = 0xFFFFFFFF;
style.showStepIndicator = true;
new VSpotView.Builder(this)
.setStyle(style)
.addStep(view1, "Step One", "This is the first step.")
.addStep(view2, "Step Two", "This is the second step.")
.show();VSpot v4.0.0 automatically handles content-only messages.
If the title is empty or not provided, the content remains cleanly centered inside the message card.
new VSpotView.Builder(this)
.setTargetView(myView)
.setContentText("This message has content only and no title.")
.setShowControls(false)
.setShowStepIndicator(false)
.show();You can also create content-only steps:
new VSpotView.Builder(this)
.addStep(new VSpotView.Step.Builder(myView)
.setContentText("This step has no title, only centered content.")
.build())
.show();VSpotView.Style style = new VSpotView.Style();
style.overlayColor = 0xE6000000;
style.cardColor = 0xFFFFFFFF;
style.cardStrokeColor = 0x1F000000;
style.cardCornerRadiusDp = 24f;
style.titleColor = 0xFF111827;
style.contentColor = 0xFF4B5563;
style.titleTypeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
style.contentTypeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
style.targetStrokeColor = 0xFFFFFFFF;
style.targetPulseColor = 0x66FFFFFF;
style.stepBadgeColor = 0xFF6750A4;
style.stepBadgeTextColor = 0xFFFFFFFF;
style.connectorColor = 0xFFFFFFFF;
style.accentColor = 0xFF6750A4;
style.showTargetStroke = true;
style.showPulse = true;
style.showStepIndicator = true;
style.showConnector = true;
new VSpotView.Builder(this)
.setStyle(style)
.setTargetView(myView)
.setTitle("Fully Customizable")
.setContentText("Customize typefaces, text colors, overlay intensity, highlight box, indicator, and more.")
.show();Old usage:
VSpotView vSpot = new VSpotView.Builder(this)
.setTitle("Guide Title")
.setContentText("Guide description")
.setGravity(VSpotView.Gravity.center)
.setDismissType(VSpotView.DismissType.outside)
.setTargetView(view1)
.build();
vSpot.show();New v4 usage still supports the same style:
VSpotView vSpot = new VSpotView.Builder(this)
.setTargetView(view1)
.setTitle("Guide Title")
.setContentText("Guide description")
.setGravity(VSpotView.Gravity.center)
.setDismissType(VSpotView.DismissType.outside)
.build();
vSpot.show();Instead of manually building the next spotlight inside onDismiss, use addStep(...):
new VSpotView.Builder(this)
.addStep(view1, "Step 1", "This is the first feature.")
.addStep(view2, "Step 2", "This is the second feature.")
.addStep(view3, "Step 3", "This is the third feature.")
.setDismissType(VSpotView.DismissType.none)
.show();- Better layout calculation
- Safer Activity context resolution
- Cleaner callback system
- No repeated bitmap creation during drawing
- Global layout listener cleanup
- More predictable target and overlay touch handling
- Professional UI controls built into the message card
If your target view is inside a complex layout, RecyclerView, ViewPager, or dynamic screen, call VSpot after the view has been measured.
findViewById(R.id.myTarget).post(new Runnable() {
@Override
public void run() {
new VSpotView.Builder(MainActivity.this)
.setTargetView(findViewById(R.id.myTarget))
.setTitle("Ready")
.setContentText("This view is now ready to highlight.")
.show();
}
});.setDismissType(VSpotView.DismissType.none)This avoids accidental dismissal and keeps the flow controlled.
Circle spotlight works best for icons, avatars, FABs, and small image buttons.
.setSpotlightShape(VSpotView.SpotlightShape.circle)Good onboarding content should be easy to read.
Recommended:
Create posts, alerts, and reports from here.
Avoid:
This button is used for the purpose of creating many different types of content and it can be clicked whenever you want to perform the creation process.
Use callbacks to save whether the user completed or skipped onboarding.
.setCallback(new VSpotView.Callback() {
@Override
public void onDismiss(View lastTargetView, boolean completed, int lastStepIndex) {
getSharedPreferences("app_prefs", MODE_PRIVATE)
.edit()
.putBoolean("home_tour_completed", completed)
.apply();
}
})SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
boolean completed = prefs.getBoolean("home_tour_completed", false);
if (!completed) {
new VSpotView.Builder(this)
.addStep(findViewById(R.id.view1), "Welcome", "Let us show you around.")
.addStep(findViewById(R.id.view2), "Explore", "Discover important actions here.")
.setDismissType(VSpotView.DismissType.none)
.setCallback(new VSpotView.Callback() {
@Override
public void onDismiss(View lastTargetView, boolean completed, int lastStepIndex) {
prefs.edit().putBoolean("home_tour_completed", true).apply();
}
})
.show();
}package com.example.app;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.developer.spoti.vspoti.VSpotView;
public class MainActivity extends AppCompatActivity {
private VSpotView onboardingTour;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View searchView = findViewById(R.id.searchView);
final View filterButton = findViewById(R.id.filterButton);
final View profileButton = findViewById(R.id.profileButton);
final View createButton = findViewById(R.id.createButton);
searchView.post(new Runnable() {
@Override
public void run() {
showOnboardingIfNeeded(searchView, filterButton, profileButton, createButton);
}
});
}
private void showOnboardingIfNeeded(View searchView,
View filterButton,
View profileButton,
View createButton) {
final SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
boolean alreadyCompleted = prefs.getBoolean("home_tour_completed", false);
if (alreadyCompleted) {
return;
}
VSpotView.Style style = new VSpotView.Style();
style.accentColor = 0xFF6750A4;
style.stepBadgeColor = 0xFF6750A4;
style.cardCornerRadiusDp = 24f;
style.targetCornerRadiusDp = 18f;
style.spotlightPaddingDp = 10f;
style.maxMessageWidthDp = 360;
style.showControls = true;
style.showConnector = true;
style.showTargetStroke = true;
style.showPulse = true;
onboardingTour = new VSpotView.Builder(this)
.setStyle(style)
.addStep(searchView,
"Smart Search",
"Quickly find anything inside the app using search.")
.addStep(filterButton,
"Filters",
"Use filters to narrow results and save time.")
.addStep(new VSpotView.Step.Builder(profileButton)
.setTitle("Profile")
.setContentText("Manage your account and preferences from here.")
.setSpotlightShape(VSpotView.SpotlightShape.circle)
.setSpotlightPaddingDp(10)
.build())
.addStep(createButton,
"Create New",
"Tap here to create your first item.")
.setGravity(VSpotView.Gravity.center)
.setDismissType(VSpotView.DismissType.none)
.setCallback(new VSpotView.Callback() {
@Override
public void onDismiss(View lastTargetView, boolean completed, int lastStepIndex) {
prefs.edit()
.putBoolean("home_tour_completed", true)
.apply();
}
})
.build();
onboardingTour.show();
}
@Override
protected void onDestroy() {
if (onboardingTour != null && onboardingTour.isShowing()) {
onboardingTour.dismiss();
}
super.onDestroy();
}
}Make sure:
- You are using an Activity context, not application context.
- Target view is not
null. - Target view is already measured.
- You call VSpot after
setContentView(...).
Correct:
new VSpotView.Builder(this)
.setTargetView(findViewById(R.id.myButton))
.setTitle("Button")
.setContentText("This is a highlighted button.")
.show();Incorrect:
new VSpotView.Builder(getApplicationContext())
.setTargetView(findViewById(R.id.myButton))
.show();Use post(...) so the target view has completed layout.
View target = findViewById(R.id.myButton);
target.post(new Runnable() {
@Override
public void run() {
new VSpotView.Builder(MainActivity.this)
.setTargetView(target)
.setTitle("Ready")
.setContentText("Now the position is calculated correctly.")
.show();
}
});Increase spotlight padding:
.setSpotlightPaddingDp(14f)Or per step:
new VSpotView.Step.Builder(targetView)
.setTitle("Feature")
.setContentText("Description")
.setSpotlightPaddingDp(14)
.build();Set max message width:
.setMaxMessageWidthDp(300)Use:
.setDismissType(VSpotView.DismissType.none)Use:
new VSpotView.Builder(this)
.setTargetView(findViewById(R.id.myTarget))
.setTitle("Simple Hint")
.setContentText("This is a simple spotlight message.")
.setShowControls(false)
.setShowStepIndicator(false)
.setDismissType(VSpotView.DismissType.outside)
.show();new VSpotView.Builder(activity)
.setTargetView(view)
.setTitle("Title")
.setContentText("Content")
.setGravity(VSpotView.Gravity.center)
.setDismissType(VSpotView.DismissType.outside)
.setSpotlightShape(VSpotView.SpotlightShape.roundedRectangle)
.setSpotlightPaddingDp(8f)
.setAccentColor(0xFF6750A4)
.setCardCornerRadiusDp(22f)
.setMaxMessageWidthDp(340)
.setShowControls(true)
.setShowConnector(true)
.setShowStepIndicator(true)
.setButtonTexts("Back", "Next", "Done", "Skip")
.setAnimationDuration(260L)
.show();If you are using VSpot as a local Android library module:
include ':app'
include ':vspoti'dependencies {
implementation project(':vspoti')
}VSpot follows semantic versioning:
MAJOR.MINOR.PATCH
Example:
4.0.0
Meaning:
4= Major version with important API/features update0= Minor feature version0= Patch/fix version
- Added multi-step guided tours
- Added Back, Next, Done, and Skip controls
- Added step indicator badge
- Added professional card UI
- Added style customization API
- Added circle spotlight support
- Added rounded rectangle spotlight support
- Added connector line between target and card
- Added target stroke and pulse effect
- Added accessibility announcements
- Added callback system
- Added manual controls:
next(),previous(),goToStep(),finish(),dismiss() - Added per-step shape, padding, gravity, and dismiss behavior
- Improved layout positioning
- Improved touch handling
- Improved lifecycle cleanup
- Removed expensive bitmap allocation from drawing cycle
- Kept old single-step builder API compatible
Contributions are welcome.
You can help by:
- Reporting bugs
- Suggesting new features
- Improving documentation
- Adding sample screens
- Testing on different Android versions and screen sizes
- Creating pull requests
git clone https://github.com/TutorialsAndroid/VSpot.git
cd VSpotOpen the project in Android Studio, run the sample app, make your changes, and submit a pull request.
If VSpot helps you build better Android apps, please consider supporting the project.
- Star the repository
- Share it with other Android developers
- Use it in your apps
- Report issues clearly
- Contribute improvements
- Sponsor the project on GitHub
MIT License
Copyright (c) TutorialsAndroid
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.




