Onboarding Screen using Jetpack Compose
The onboarding screen is an essential part of many mobile applications, providing a user-friendly introduction to the app’s features and functionality. In this tutorial, we will explore how to build an onboarding screen using Jetpack Compose, the modern toolkit for building native Android UIs. We will leverage the power of Compose to create a visually appealing and interactive onboarding experience. So let’s get started!
For full code check the code on github: If its helpful for you, please don`t forget to like the repsoitory.
Prerequisites: Before diving into the implementation, make sure you have the following prerequisites set up:
- Android Studio Arctic Fox (2020.3.1) or later.
- Jetpack Compose version 1.0.0 or later.
As you can see in above image, we will now divide the Screen into small composable components to make it reusable and easy to test.
Data Class to hold the Pages:
data class OnboardPage(
val imageRes: Int, val title: String, val description: String
)
Sample Data to start with:
val onboardPagesList = listOf(
OnboardPage(
imageRes = R.drawable.image1,
title = "Welcome to Onboarding",
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
), OnboardPage(
imageRes = R.drawable.image2,
title = "Explore Exciting Features",
description = "Praesent at semper est, nec consectetur justo."
), OnboardPage(
imageRes = R.drawable.image3,
title = "Get Started Now",
description = "In auctor ultrices turpis at blandit."
)
)
Here’s the explanation for each composable function we will create:
OnboardScreen
: This composable function represents the main onboarding screen. It takes no parameters and sets up the UI for the onboarding flow. It initializes the onboardPages
list with the provided onboardPagesList
. It also maintains the current page index using remember { mutableStateOf(0) }
. The composable contains a column layout with the following children:
OnBoardImageView
: Displays the image of the current onboarding page.
@Composable
fun OnBoardImageView(modifier: Modifier = Modifier, imageRes: Int) {
Box(modifier = modifier) {
Image(
painter = painterResource(id = imageRes),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillWidth
)
Box(modifier = Modifier
.fillMaxSize()
.align(Alignment.BottomCenter)
.graphicsLayer {
// Apply alpha to create the fading effect
alpha = 0.6f
}
.background(
Brush.verticalGradient(
colorStops = arrayOf(
Pair(0.8f, Color.Transparent), Pair(1f, Color.White)
)
)
))
}
}
Image
: This composable displays the image using the providedimageRes
. It uses thepainterResource
function to retrieve the image resource based on theimageRes
ID. ThecontentDescription
is set tonull
to indicate that it is decorative and does not require an accessibility description. Themodifier
is used to fill the available space (fillMaxSize()
) within theBox
.Box
: This composable acts as a container for the image and the fading effect. It uses themodifier
parameter to apply any additional modifiers passed to theOnBoardImageView
composable.- Fading Effect: The fading effect is created using another
Box
composable nested inside the mainBox
. The nestedBox
is aligned to the bottom center of the screen (align(Alignment.BottomCenter)
) and fills the available space (fillMaxSize()
). graphicsLayer
: This modifier is used to apply transformations and effects to the nestedBox
. In this case, thealpha
property is set to0.6f
to control the transparency of the fading effect.background
: This modifier is applied to the nestedBox
and defines the background of the fading effect. It uses aBrush.verticalGradient
to create a gradient that transitions fromColor.Transparent
at 80% of the height toColor.White
at the bottom.
OnBoardDetails
: Displays the title and description of the current onboarding page.
@Composable
fun OnBoardDetails(
modifier: Modifier = Modifier, currentPage: OnboardPage
) {
Column(
modifier = modifier
) {
Text(
text = currentPage.title,
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = currentPage.description,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
OnBoardNavButton
: Provides navigation buttons i.e next button for moving to the next page or completing the onboarding.
@Composable
fun OnBoardNavButton(
modifier: Modifier = Modifier, currentPage: Int, noOfPages: Int, onNextClicked: () -> Unit
) {
Button(
onClick = {
if (currentPage < noOfPages - 1) {
onNextClicked()
} else {
// Handle onboarding completion
}
}, modifier = modifier
) {
Text(text = if (currentPage < noOfPages - 1) "Next" else "Get Started")
}
}
TabSelector
: Displays tabs representing each onboarding page.
@Composable
fun TabSelector(onboardPages: List<OnboardPage>, currentPage: Int, onTabSelected: (Int) -> Unit) {
TabRow(
selectedTabIndex = currentPage,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primary)
) {
onboardPages.forEachIndexed { index, _ ->
Tab(selected = index == currentPage, onClick = {
onTabSelected(index)
}, modifier = Modifier.padding(16.dp), content = {
Box(
modifier = Modifier
.size(8.dp)
.background(
color = if (index == currentPage) MaterialTheme.colorScheme.onPrimary
else Color.LightGray, shape = RoundedCornerShape(4.dp)
)
)
})
}
}
}
Explanation:
TabRow
: This is a built-in Jetpack Compose composable that represents a row of tabs. It takes theselectedTabIndex
parameter, which indicates the index of the currently selected tab. We pass thecurrentPage
parameter toselectedTabIndex
so that the appropriate tab is selected based on the current page index. Themodifier
is used to apply additional modifiers to theTabRow
. In this case, we set the width tofillMaxWidth()
and apply a background color from theMaterialTheme.colorScheme.primary
to theTabRow
.onboardPages.forEachIndexed
: This loops through eachOnboardPage
in theonboardPages
list and creates aTab
composable for each page. Theindex
parameter represents the current index of the loop, and the underscore_
is used to ignore the page object itself since it is not used inside the loop.Tab
: This composable represents an individual tab in theTabRow
. Theselected
parameter is set totrue
when the index of the current tab matches thecurrentPage
. TheonClick
parameter is a callback function that is triggered when the tab is clicked. It callsonTabSelected
with the index of the clicked tab.modifier.padding(16.dp)
: This applies padding to each individual tab, giving it some spacing from neighboring tabs.content
: This is the content of each tab, defined as aBox
composable. Inside theBox
, we create a small box (size(8.dp)
) with a background color. The background color is determined by theMaterialTheme.colorScheme.onPrimary
if the tab is currently selected (index == currentPage
), andColor.LightGray
otherwise. We also apply aRoundedCornerShape(4.dp)
to give the box a rounded appearance.
Now we have all the components needed to display our Onboarding Screen. We will now add them into the Column sequence in our OnboardScreen composable function.
@Composable
fun OnboardScreen() {
val onboardPages = onboardPagesList
val currentPage = remember { mutableStateOf(0) }
Column(
modifier = Modifier.fillMaxSize()
) {
OnBoardImageView(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
imageRes = onboardPages[currentPage.value].imageRes
)
OnBoardDetails(
modifier = Modifier
.weight(1f)
.padding(16.dp),
currentPage = onboardPages[currentPage.value]
)
OnBoardNavButton(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = 16.dp),
currentPage = currentPage.value,
noOfPages = onboardPages.size
) {
currentPage.value++
}
TabSelector(
onboardPages = onboardPages,
currentPage = currentPage.value
) { index ->
currentPage.value = index
}
}
}
For full code check the code on github: If its helpful for you, please don`t forget to like the repsoitory and this blog 👏.