
Creates animated showcase effects for UI elements. Features include customizable messages, arrows, dark mode, greeting messages, and support for sequential or manual showcasing of items.
Create beautiful animated showcase effects for your compose UIs easily!
Now with multiplatform support and two different showcase layouts to choose from:
The demo app ships three small apps, each with an onboarding tour built on the library, plus a playground that exposes every parameter. Try it in your browser or jump straight into a tour:
| Scenario | What it shows | Source |
|---|---|---|
| Lumen Bank · open |
TargetShowcaseLayout morphing between a card and a round button, custom message cards, and a locked "tap the eye icon to continue" step |
BankingScreen.kt |
| Now Playing · open | A dark UI with a light overlay, TargetShape.CIRCLE, the pulse ring, and a locked step on the play button |
MusicScreen.kt |
| Messages · open |
ShowcaseLayout with a locked step on a text field: the keyboard works under the overlay and the tour continues once a message is sent |
ChatScreen.kt |
Every scenario can be switched between the two layouts from its menu, and &autoplay=1 on any of the
links above plays the tour hands-free. The message cards are ordinary composables, see
TourUi.kt.
Showcase Layout Compose can be used in both Jetpack Compose (native Android) or Compose Multiplatform (Kotlin Multiplatform) projects.
Compose multiplatform support starts at version
1.0.5-alpha-8and up.
Add the dependency to your module's build.gradle file like below
implementation("ly.com.tahaben:showcase-layout-compose:{latest-version}")Latest version
or see Releases.
Create a ShowcaseLayout and make it the root composable (put all screen composables inside it)
var isShowcasing by remember {
mutableStateOf(true)
}
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false }
) {
// screen content here
Column(
modifier = Modifier
.fillMaxSize()
) {
Text(text = "ShowcaseLayout Test 1")
Spacer(modifier = Modifier.height(16.dp))
Text(text = "ShowcaseLayout Test 2")
Spacer(modifier = Modifier.height(16.dp))
Text(text = "ShowcaseLayout Test 3")
}
}In composables you want to showcase on the modifier use Modifier.showcase(), Lets say we want to
showcase the first
text "ShowcaseLayout Test 1"
Text(
modifier = Modifier.showcase(
// should start with 1 and increment with 1 for each time you use Modifier.showcase()
index = 1,
message = ShowcaseMsg(
"This is a showcase message",
textStyle = TextStyle(color = Color.White)
)
),
text = "ShowcaseLayout Test 1"
)
you also use the old method by wrap the composables you want to showcase with Showcase()
You have 2 ways of showcasing, showcase everything subsequently or showcasing each item manually
isShowcasing = true, and stop showcasing by making it false
above we stop showcasing after we showcase the last item using `onFinished` which is called whenever
all items are showcased,
showcaseItem(i) where i is the index of the item you want to showcase
val coroutineScope = rememberCoroutineScope()
coroutineScope.launch{
showcaseItem(1)
}similarly you can show a greeting using showGreeting and passing the message
val coroutineScope = rememberCoroutineScope()
coroutineScope.launch{
showGreeting(
ShowcaseMsg(
text = "I like compose bro <3",
textStyle = TextStyle(color = Color.White)
)
)
}Done, our text is now showcased!, customize it further with Additional parameters.
Starting from version 1.0.6, Showcase Layout Compose now offers a new layout option: TargetShowcaseLayout. This layout provides a different visual approach to showcasing UI elements by highlighting specific targets with customizable shapes rather than the full-screen approach of the original ShowcaseLayout.
You can use TargetShowcaseLayout directly:
var isShowcasing by remember { mutableStateOf(true) }
TargetShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
targetShape = TargetShape.ROUNDED_RECTANGLE, // CIRCLE, RECTANGLE, or ROUNDED_RECTANGLE
cornerRadius = 8.dp, // Only used with ROUNDED_RECTANGLE
animateToNextTarget = true, // Smooth animation between targets
greeting = ShowcaseMsg(
"Welcome to TargetShowcaseLayout!",
textStyle = TextStyle(color = Color.White)
)
) {
// Your UI content here
Column {
Text(
modifier = Modifier.showcase(
index = 1,
message = ShowcaseMsg(
"This element is highlighted with TargetShowcaseLayout",
textStyle = TextStyle(color = Color.White)
)
),
text = "Target Showcase Example"
)
}
}| TargetShowcaseLayout with CIRCLE shape | TargetShowcaseLayout with RECTANGLE shape | TargetShowcaseLayout with ROUNDED_RECTANGLE shape |
|---|---|---|
![]() |
![]() |
![]() |
| Feature | TargetShowcaseLayout | ShowcaseLayout |
|---|---|---|
| Visual style | Highlights specific targets with shapes | Full-screen overlay with cutouts |
| Target shapes | Circle, Rectangle, Rounded Rectangle | Circle, Rectangle, Rounded Rectangle |
| Animations | Smooth transitions between targets | Fade transitions |
| Pulsing effect | Yes | No |
| Use cases | Focused UI tours, precise element highlighting | General app tours, feature introductions |
In addition to the parameters shared with ShowcaseLayout, TargetShowcaseLayout offers:
TargetShowcaseLayout(
// Common parameters (same as ShowcaseLayout)
isShowcasing = isShowcasing,
colors = ShowcaseLayoutDefaults.colors(),
initIndex = 0,
animationDuration = 1000,
onFinish = { isShowcasing = false },
greeting = ShowcaseMsg(
"Welcome to TargetShowcaseLayout!",
textStyle = TextStyle(color = Color.White)
),
lineThickness = 5.dp,
// TargetShowcaseLayout specific parameters
targetShape = TargetShape.CIRCLE, // CIRCLE, RECTANGLE, or ROUNDED_RECTANGLE
cornerRadius = 8.dp, // Only used with ROUNDED_RECTANGLE
animateToNextTarget = true // Smooth animation between targets, otherwise shrink and expand on each target
) {
// Your UI content here
}The same banking screen on TargetShowcaseLayout (left) and ShowcaseLayout (right):
Controls the color and alpha of the dimming overlay (and, for TargetShowcaseLayout, the color of
the pulsing ring). Build a ShowcaseLayoutDefaults.Colors with ShowcaseLayoutDefaults.colors(...).
The overlay color's own alpha is honored, so you have full control over transparency. The default is
Color.Black.copy(alpha = 0.9f); for a dark UI, pass a light overlay color.
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
colors = ShowcaseLayoutDefaults.colors(
overlayColor = Color.White.copy(alpha = 0.9f), // light overlay for a dark UI
pulseColor = Color.White // TargetShowcaseLayout only
)
)Migrating from
isDarkLayout: theisDarkLayoutparameter is deprecated and will be removed in a future release. ReplaceisDarkLayout = truewithcolors = ShowcaseLayoutDefaults.colors(overlayColor = Color.White.copy(alpha = 0.9f)). Leavingcolorsat its default reproduces the previousisDarkLayout = falselook.
overlayColor = Color.White.copy(alpha = 0.9f)
A customizable greeting message of type showcaseMsg()
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
greeting = ShowcaseMsg(
"Welcome to my app, lets take you on a quick tour!, tap anywhere to continue",
textStyle = TextStyle(color = Color.White)
)
)
the initial value of what index will showcase first.
total animation time taken when switching from current to next target in milliseconds(default is 1000ms).
val greetingString = buildAnnotatedString {
append("Welcome to ")
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append("My App")
pop()
append(", let's take you on a quick tour!")
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append("\n Tap anywhere")
pop()
append(" to continue")
}
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
greeting = ShowcaseMsg(
text = greetingString, // You can use an annotated string or a normal string here
textStyle = TextStyle(color = Color.White, textAlign = TextAlign.Center)
),
animationDuration = 1000
) By default a tap anywhere on the screen advances to the next target (or dismisses the showcase). Set
advanceOnTargetTapOnly = true to only advance when the user taps inside the highlighted target
shape — taps elsewhere are ignored. Useful when you want the user to actually interact with (or
notice) the highlighted element before moving on. The greeting/initial screen still advances on any
tap. Defaults to false. Available on both ShowcaseLayout and TargetShowcaseLayout.
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
advanceOnTargetTapOnly = true
)Starting from version 1.2.0 a step can be locked until the user does something: taps a specific
button, types a valid value, toggles a switch, ... Pass canProceed = false to Modifier.showcase()
(or Showcase()) and the step behaves like this:
canProceed becomes true the showcase advances by itself (or dismisses the item when
showing a single item with showcaseItem()).canProceed is a plain Boolean, so read it from snapshot state and let recomposition do the rest.
Pair it with onProceed, which is called once when the showcase leaves the step, to clean up after
it, for example to hide the soft keyboard the step opened so users (iOS in particular) are not left
with it on screen:
var typed by remember { mutableStateOf("") }
val keyboard = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
OutlinedTextField(
value = typed,
onValueChange = { typed = it },
modifier = Modifier.showcase(
index = 2,
message = ShowcaseMsg("Type your name to continue"),
canProceed = typed.isNotBlank(),
onProceed = {
focusManager.clearFocus()
keyboard?.hide()
}
)
)Notes:
canProceed is already true when it is reached behaves like any other step. Only a
step that is locked when reached waits for the condition and then advances on its own.TargetShape.CIRCLE the visible
cutout can be a bit larger than that; touches in the extra area are swallowed.next(), previous() and skip() from a custom composable message
still work while a step is locked, so you can keep a "Skip" escape hatch. If you don't want a
"Next" button to bypass the lock, disable it with enabled = canProceed.onProceed is not tied to locked steps: any step can use it. It fires whatever ends the step: a
tap, next()/previous()/skip(), the condition being met, the last step finishing, or a single
item being dismissed.ShowcaseLayout and TargetShowcaseLayout.Use ShowcaseMsg() to add a message and customize it with arrow, background and more.
ShowcaseMsg(
// the message text to be displayed
"Track your phone usage from here",
// text style for the message text
textStyle = TextStyle(color = Color(0xFF827717)),
// a background color for the text
msgBackground = MaterialTheme.colors.primary,
// control corner radius of msgBackground
roundedCorner = 15.dp,
// determine if the message will be displayed above or below the target composable
gravity = Gravity.Bottom,
// adds an arrow to be displayed with the message
arrow = Arrow(color = MaterialTheme.colors.primary),
// starting from version 1.0.3 ShowcaseMsg will have an enter and exit animation of FadeInOut by default you can disable it by using MsgAnimation.None
enterAnim = MsgAnimation.FadeInOut(),
exitAnim = MsgAnimation.FadeInOut()
)
| ShowcaseMsg | without ShowcaseMsg |
|---|---|
![]() |
![]() |
Starting from version 1.2.0 a message can be any composable instead of text: an image, a card with an icon, a message with buttons, ... Use the trailing-lambda constructor of ShowcaseMsg:
Modifier.showcase(
index = 2,
message = ShowcaseMsg(
gravity = Gravity.Bottom,
arrow = Arrow(color = Color.White)
) {
Card {
Column(Modifier.padding(16.dp)) {
Row {
Icon(Icons.Default.Info, contentDescription = null)
Text("Tap here to open the menu")
}
Row {
TextButton(onClick = { previous() }, enabled = hasPrevious) { Text("Back") }
TextButton(onClick = { skip() }) { Text("Skip") }
Button(onClick = { next() }) { Text("Next") }
}
}
}
}
)The same works for the greeting parameter and for showGreeting(ShowcaseMsg { ... }).
text, textStyle and roundedCorner are ignored. msgBackground is ignored by ShowcaseLayout; in TargetShowcaseLayout it still acts as the overlay color of that step.gravity, arrow, enterAnim and exitAnim keep working. The arrow starts from the edge of your composable that faces the target, and if Gravity.Auto has to move the composable to the other side of the target a Side.Top/Side.Bottom arrow points at the edge facing it instead of crossing the target. The composable is always kept on screen.ShowcaseMsgScope so buttons inside it can drive the showcase: next() advances (or dismisses a single item / greeting), previous() goes back when hasPrevious is true, and skip() ends the whole showcase and calls onFinish. next() is not affected by advanceOnTargetTapOnly or by a locked step.onClick, ...) handle their own taps.UIKitView) inside the content are not supported.Used with ShowcaseMsg to add an arrow pointing to the target
arrow = Arrow(
// From where the arrow will point at the target, can be: Top, Bottom, Right or Left
targetFrom = Side.Top,
// animates a curved arrow from the message to the target(if true targetFrom is ignored)
// might not work properly depending on the location of the target on the screen
curved = true,
// if false then just draw a line (an arrow without head :P)
hasHead = false,
// color of the arrow
color = MaterialTheme.colors.primary
)| Default Arrow | curved = true |
hasHead = false |
|---|---|---|
![]() |
![]() |
![]() |
By default, an Arrow will have a triangle as the head to change this, set head in the arrow to one of these options
TRIANGLE |
CIRCLE |
SQUARE |
ROUND_SQUARE |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
You can also animate the arrow head and change the size, see the example below.
showcase(
index = 5,
message = ShowcaseMsg(
"A Circle !",
textStyle = TextStyle(
color = Color(0xFF827717),
fontSize = 18.sp
),
msgBackground = MaterialTheme.colors.primary,
gravity = Gravity.Top,
arrow = Arrow(
color = MaterialTheme.colors.primary,
targetFrom = Side.Top,
head = Head.CIRCLE, // head style
headSize = 30f, // the size of the circle
animSize = true // animates the arrow head size
)
)
)In recent releases logs have been disabled by default, to print log statement of the current actions taken by compose layout register a listener in your ShowcaseLayout
registerEventListener(object: ShowcaseEventListener {
override fun onEvent(level: Level, event: String) {
println("$level: $event")
}
})For a complete example of the original ShowcaseLayout, check out MainScreen.kt.
For an example of the new TargetShowcaseLayout, check out the App.kt file in the shared module.
You can also clone/download this repository and run the demo app to see both layouts in action.
Contributions are always welcome!
Showcase Layout is used by:
Contact me on LinkedIn or open an issue if you used ShowcaseLayout in your app, and you want it added to this list
Create beautiful animated showcase effects for your compose UIs easily!
Now with multiplatform support and two different showcase layouts to choose from:
The demo app ships three small apps, each with an onboarding tour built on the library, plus a playground that exposes every parameter. Try it in your browser or jump straight into a tour:
| Scenario | What it shows | Source |
|---|---|---|
| Lumen Bank · open |
TargetShowcaseLayout morphing between a card and a round button, custom message cards, and a locked "tap the eye icon to continue" step |
BankingScreen.kt |
| Now Playing · open | A dark UI with a light overlay, TargetShape.CIRCLE, the pulse ring, and a locked step on the play button |
MusicScreen.kt |
| Messages · open |
ShowcaseLayout with a locked step on a text field: the keyboard works under the overlay and the tour continues once a message is sent |
ChatScreen.kt |
Every scenario can be switched between the two layouts from its menu, and &autoplay=1 on any of the
links above plays the tour hands-free. The message cards are ordinary composables, see
TourUi.kt.
Showcase Layout Compose can be used in both Jetpack Compose (native Android) or Compose Multiplatform (Kotlin Multiplatform) projects.
Compose multiplatform support starts at version
1.0.5-alpha-8and up.
Add the dependency to your module's build.gradle file like below
implementation("ly.com.tahaben:showcase-layout-compose:{latest-version}")Latest version
or see Releases.
Create a ShowcaseLayout and make it the root composable (put all screen composables inside it)
var isShowcasing by remember {
mutableStateOf(true)
}
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false }
) {
// screen content here
Column(
modifier = Modifier
.fillMaxSize()
) {
Text(text = "ShowcaseLayout Test 1")
Spacer(modifier = Modifier.height(16.dp))
Text(text = "ShowcaseLayout Test 2")
Spacer(modifier = Modifier.height(16.dp))
Text(text = "ShowcaseLayout Test 3")
}
}In composables you want to showcase on the modifier use Modifier.showcase(), Lets say we want to
showcase the first
text "ShowcaseLayout Test 1"
Text(
modifier = Modifier.showcase(
// should start with 1 and increment with 1 for each time you use Modifier.showcase()
index = 1,
message = ShowcaseMsg(
"This is a showcase message",
textStyle = TextStyle(color = Color.White)
)
),
text = "ShowcaseLayout Test 1"
)
you also use the old method by wrap the composables you want to showcase with Showcase()
You have 2 ways of showcasing, showcase everything subsequently or showcasing each item manually
isShowcasing = true, and stop showcasing by making it false
above we stop showcasing after we showcase the last item using `onFinished` which is called whenever
all items are showcased,
showcaseItem(i) where i is the index of the item you want to showcase
val coroutineScope = rememberCoroutineScope()
coroutineScope.launch{
showcaseItem(1)
}similarly you can show a greeting using showGreeting and passing the message
val coroutineScope = rememberCoroutineScope()
coroutineScope.launch{
showGreeting(
ShowcaseMsg(
text = "I like compose bro <3",
textStyle = TextStyle(color = Color.White)
)
)
}Done, our text is now showcased!, customize it further with Additional parameters.
Starting from version 1.0.6, Showcase Layout Compose now offers a new layout option: TargetShowcaseLayout. This layout provides a different visual approach to showcasing UI elements by highlighting specific targets with customizable shapes rather than the full-screen approach of the original ShowcaseLayout.
You can use TargetShowcaseLayout directly:
var isShowcasing by remember { mutableStateOf(true) }
TargetShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
targetShape = TargetShape.ROUNDED_RECTANGLE, // CIRCLE, RECTANGLE, or ROUNDED_RECTANGLE
cornerRadius = 8.dp, // Only used with ROUNDED_RECTANGLE
animateToNextTarget = true, // Smooth animation between targets
greeting = ShowcaseMsg(
"Welcome to TargetShowcaseLayout!",
textStyle = TextStyle(color = Color.White)
)
) {
// Your UI content here
Column {
Text(
modifier = Modifier.showcase(
index = 1,
message = ShowcaseMsg(
"This element is highlighted with TargetShowcaseLayout",
textStyle = TextStyle(color = Color.White)
)
),
text = "Target Showcase Example"
)
}
}| TargetShowcaseLayout with CIRCLE shape | TargetShowcaseLayout with RECTANGLE shape | TargetShowcaseLayout with ROUNDED_RECTANGLE shape |
|---|---|---|
![]() |
![]() |
![]() |
| Feature | TargetShowcaseLayout | ShowcaseLayout |
|---|---|---|
| Visual style | Highlights specific targets with shapes | Full-screen overlay with cutouts |
| Target shapes | Circle, Rectangle, Rounded Rectangle | Circle, Rectangle, Rounded Rectangle |
| Animations | Smooth transitions between targets | Fade transitions |
| Pulsing effect | Yes | No |
| Use cases | Focused UI tours, precise element highlighting | General app tours, feature introductions |
In addition to the parameters shared with ShowcaseLayout, TargetShowcaseLayout offers:
TargetShowcaseLayout(
// Common parameters (same as ShowcaseLayout)
isShowcasing = isShowcasing,
colors = ShowcaseLayoutDefaults.colors(),
initIndex = 0,
animationDuration = 1000,
onFinish = { isShowcasing = false },
greeting = ShowcaseMsg(
"Welcome to TargetShowcaseLayout!",
textStyle = TextStyle(color = Color.White)
),
lineThickness = 5.dp,
// TargetShowcaseLayout specific parameters
targetShape = TargetShape.CIRCLE, // CIRCLE, RECTANGLE, or ROUNDED_RECTANGLE
cornerRadius = 8.dp, // Only used with ROUNDED_RECTANGLE
animateToNextTarget = true // Smooth animation between targets, otherwise shrink and expand on each target
) {
// Your UI content here
}The same banking screen on TargetShowcaseLayout (left) and ShowcaseLayout (right):
Controls the color and alpha of the dimming overlay (and, for TargetShowcaseLayout, the color of
the pulsing ring). Build a ShowcaseLayoutDefaults.Colors with ShowcaseLayoutDefaults.colors(...).
The overlay color's own alpha is honored, so you have full control over transparency. The default is
Color.Black.copy(alpha = 0.9f); for a dark UI, pass a light overlay color.
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
colors = ShowcaseLayoutDefaults.colors(
overlayColor = Color.White.copy(alpha = 0.9f), // light overlay for a dark UI
pulseColor = Color.White // TargetShowcaseLayout only
)
)Migrating from
isDarkLayout: theisDarkLayoutparameter is deprecated and will be removed in a future release. ReplaceisDarkLayout = truewithcolors = ShowcaseLayoutDefaults.colors(overlayColor = Color.White.copy(alpha = 0.9f)). Leavingcolorsat its default reproduces the previousisDarkLayout = falselook.
overlayColor = Color.White.copy(alpha = 0.9f)
A customizable greeting message of type showcaseMsg()
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
greeting = ShowcaseMsg(
"Welcome to my app, lets take you on a quick tour!, tap anywhere to continue",
textStyle = TextStyle(color = Color.White)
)
)
the initial value of what index will showcase first.
total animation time taken when switching from current to next target in milliseconds(default is 1000ms).
val greetingString = buildAnnotatedString {
append("Welcome to ")
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append("My App")
pop()
append(", let's take you on a quick tour!")
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append("\n Tap anywhere")
pop()
append(" to continue")
}
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
greeting = ShowcaseMsg(
text = greetingString, // You can use an annotated string or a normal string here
textStyle = TextStyle(color = Color.White, textAlign = TextAlign.Center)
),
animationDuration = 1000
) By default a tap anywhere on the screen advances to the next target (or dismisses the showcase). Set
advanceOnTargetTapOnly = true to only advance when the user taps inside the highlighted target
shape — taps elsewhere are ignored. Useful when you want the user to actually interact with (or
notice) the highlighted element before moving on. The greeting/initial screen still advances on any
tap. Defaults to false. Available on both ShowcaseLayout and TargetShowcaseLayout.
ShowcaseLayout(
isShowcasing = isShowcasing,
onFinish = { isShowcasing = false },
advanceOnTargetTapOnly = true
)Starting from version 1.2.0 a step can be locked until the user does something: taps a specific
button, types a valid value, toggles a switch, ... Pass canProceed = false to Modifier.showcase()
(or Showcase()) and the step behaves like this:
canProceed becomes true the showcase advances by itself (or dismisses the item when
showing a single item with showcaseItem()).canProceed is a plain Boolean, so read it from snapshot state and let recomposition do the rest.
Pair it with onProceed, which is called once when the showcase leaves the step, to clean up after
it, for example to hide the soft keyboard the step opened so users (iOS in particular) are not left
with it on screen:
var typed by remember { mutableStateOf("") }
val keyboard = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
OutlinedTextField(
value = typed,
onValueChange = { typed = it },
modifier = Modifier.showcase(
index = 2,
message = ShowcaseMsg("Type your name to continue"),
canProceed = typed.isNotBlank(),
onProceed = {
focusManager.clearFocus()
keyboard?.hide()
}
)
)Notes:
canProceed is already true when it is reached behaves like any other step. Only a
step that is locked when reached waits for the condition and then advances on its own.TargetShape.CIRCLE the visible
cutout can be a bit larger than that; touches in the extra area are swallowed.next(), previous() and skip() from a custom composable message
still work while a step is locked, so you can keep a "Skip" escape hatch. If you don't want a
"Next" button to bypass the lock, disable it with enabled = canProceed.onProceed is not tied to locked steps: any step can use it. It fires whatever ends the step: a
tap, next()/previous()/skip(), the condition being met, the last step finishing, or a single
item being dismissed.ShowcaseLayout and TargetShowcaseLayout.Use ShowcaseMsg() to add a message and customize it with arrow, background and more.
ShowcaseMsg(
// the message text to be displayed
"Track your phone usage from here",
// text style for the message text
textStyle = TextStyle(color = Color(0xFF827717)),
// a background color for the text
msgBackground = MaterialTheme.colors.primary,
// control corner radius of msgBackground
roundedCorner = 15.dp,
// determine if the message will be displayed above or below the target composable
gravity = Gravity.Bottom,
// adds an arrow to be displayed with the message
arrow = Arrow(color = MaterialTheme.colors.primary),
// starting from version 1.0.3 ShowcaseMsg will have an enter and exit animation of FadeInOut by default you can disable it by using MsgAnimation.None
enterAnim = MsgAnimation.FadeInOut(),
exitAnim = MsgAnimation.FadeInOut()
)
| ShowcaseMsg | without ShowcaseMsg |
|---|---|
![]() |
![]() |
Starting from version 1.2.0 a message can be any composable instead of text: an image, a card with an icon, a message with buttons, ... Use the trailing-lambda constructor of ShowcaseMsg:
Modifier.showcase(
index = 2,
message = ShowcaseMsg(
gravity = Gravity.Bottom,
arrow = Arrow(color = Color.White)
) {
Card {
Column(Modifier.padding(16.dp)) {
Row {
Icon(Icons.Default.Info, contentDescription = null)
Text("Tap here to open the menu")
}
Row {
TextButton(onClick = { previous() }, enabled = hasPrevious) { Text("Back") }
TextButton(onClick = { skip() }) { Text("Skip") }
Button(onClick = { next() }) { Text("Next") }
}
}
}
}
)The same works for the greeting parameter and for showGreeting(ShowcaseMsg { ... }).
text, textStyle and roundedCorner are ignored. msgBackground is ignored by ShowcaseLayout; in TargetShowcaseLayout it still acts as the overlay color of that step.gravity, arrow, enterAnim and exitAnim keep working. The arrow starts from the edge of your composable that faces the target, and if Gravity.Auto has to move the composable to the other side of the target a Side.Top/Side.Bottom arrow points at the edge facing it instead of crossing the target. The composable is always kept on screen.ShowcaseMsgScope so buttons inside it can drive the showcase: next() advances (or dismisses a single item / greeting), previous() goes back when hasPrevious is true, and skip() ends the whole showcase and calls onFinish. next() is not affected by advanceOnTargetTapOnly or by a locked step.onClick, ...) handle their own taps.UIKitView) inside the content are not supported.Used with ShowcaseMsg to add an arrow pointing to the target
arrow = Arrow(
// From where the arrow will point at the target, can be: Top, Bottom, Right or Left
targetFrom = Side.Top,
// animates a curved arrow from the message to the target(if true targetFrom is ignored)
// might not work properly depending on the location of the target on the screen
curved = true,
// if false then just draw a line (an arrow without head :P)
hasHead = false,
// color of the arrow
color = MaterialTheme.colors.primary
)| Default Arrow | curved = true |
hasHead = false |
|---|---|---|
![]() |
![]() |
![]() |
By default, an Arrow will have a triangle as the head to change this, set head in the arrow to one of these options
TRIANGLE |
CIRCLE |
SQUARE |
ROUND_SQUARE |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
You can also animate the arrow head and change the size, see the example below.
showcase(
index = 5,
message = ShowcaseMsg(
"A Circle !",
textStyle = TextStyle(
color = Color(0xFF827717),
fontSize = 18.sp
),
msgBackground = MaterialTheme.colors.primary,
gravity = Gravity.Top,
arrow = Arrow(
color = MaterialTheme.colors.primary,
targetFrom = Side.Top,
head = Head.CIRCLE, // head style
headSize = 30f, // the size of the circle
animSize = true // animates the arrow head size
)
)
)In recent releases logs have been disabled by default, to print log statement of the current actions taken by compose layout register a listener in your ShowcaseLayout
registerEventListener(object: ShowcaseEventListener {
override fun onEvent(level: Level, event: String) {
println("$level: $event")
}
})For a complete example of the original ShowcaseLayout, check out MainScreen.kt.
For an example of the new TargetShowcaseLayout, check out the App.kt file in the shared module.
You can also clone/download this repository and run the demo app to see both layouts in action.
Contributions are always welcome!
Showcase Layout is used by:
Contact me on LinkedIn or open an issue if you used ShowcaseLayout in your app, and you want it added to this list