Jetpack Compose - Onboarding

This example demonstrates how to make a Jetpack Compose Onboarding or App intro screen

Onboarding is a virtual unboxing experience that helps users get started with an app.


OnBoarding.kt

package io.material.compose.ui.design

import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.KeyboardArrowLeft
import androidx.compose.material.icons.outlined.KeyboardArrowRight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import io.material.compose.model.OnBoardingItems
import kotlinx.coroutines.launch

@ExperimentalPagerApi
@Preview
@Composable
fun OnBoarding() {
val items = OnBoardingItems.getData()
val scope = rememberCoroutineScope()
val pageState = rememberPagerState()

Column(modifier = Modifier.fillMaxSize()) {
TopSection(
onBackClick = {
if (pageState.currentPage + 1 > 1) scope.launch {
pageState.scrollToPage(pageState.currentPage - 1)
}
},
onSkipClick = {
if (pageState.currentPage + 1 < items.size) scope.launch {
pageState.scrollToPage(items.size - 1)
}
}
)

HorizontalPager(
count = items.size,
state = pageState,
modifier = Modifier
.fillMaxHeight(0.9f)
.fillMaxWidth()
) { page ->
OnBoardingItem(items = items[page])
}
BottomSection(size = items.size, index = pageState.currentPage) {
if (pageState.currentPage + 1 < items.size) scope.launch {
pageState.scrollToPage(pageState.currentPage + 1)
}
}
}
}

@ExperimentalPagerApi
@Composable
fun TopSection(onBackClick: () -> Unit = {}, onSkipClick: () -> Unit = {}) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
{
// Back button
IconButton(onClick = onBackClick, modifier = Modifier.align(Alignment.CenterStart)) {
Icon(imageVector = Icons.Outlined.KeyboardArrowLeft, contentDescription = null)
}

// Skip Button
TextButton(
onClick = onSkipClick,
modifier = Modifier.align(Alignment.CenterEnd),
contentPadding = PaddingValues(0.dp)
)
{
Text(text = "Skip", color = MaterialTheme.colors.onBackground)
}
}
}

@Composable
fun BottomSection(size: Int, index: Int, onButtonClick: () -> Unit = {}) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
{
// Indicators
Indicators(size, index)

// FAB Next
FloatingActionButton(
onClick = onButtonClick,
backgroundColor = MaterialTheme.colors.primary,
contentColor = MaterialTheme.colors.onPrimary,
modifier = Modifier.align(Alignment.CenterEnd)
)
{
Icon(imageVector = Icons.Outlined.KeyboardArrowRight, contentDescription = "Next")
}
}
}

@Composable
fun BoxScope.Indicators(size: Int, index: Int) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.align(Alignment.CenterStart)
)
{
repeat(size) {
Indicator(isSelected = it == index)
}
}
}

@Composable
fun Indicator(isSelected: Boolean) {
val width = animateDpAsState(
targetValue = if (isSelected) 25.dp else 10.dp,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy)
)


Box(
modifier = Modifier
.height(10.dp)
.width(width.value)
.clip(CircleShape)
.background(
color = if (isSelected) MaterialTheme.colors.primary else Color(0XFFF8E2E7)
)
)
{

}
}

@Composable
fun OnBoardingItem(items: OnBoardingItems) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
)
{
Image(painter = painterResource(id = items.image), contentDescription = "Image1")
Text(
text = stringResource(id = items.title),
style = androidx.compose.material3.MaterialTheme.typography.labelLarge,
fontSize = 24.sp,
color = MaterialTheme.colors.onBackground,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)

Text(
text = stringResource(id = items.desc),
style = androidx.compose.material3.MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colors.onBackground,
fontWeight = FontWeight.Light,
textAlign = TextAlign.Center,
modifier = Modifier.padding(10.dp)
)

}
}

OnBoarding.kt

package io.material.compose

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.google.accompanist.pager.ExperimentalPagerApi
import io.material.compose.ui.design.OnBoarding

@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
@Composable
fun OnboardDemo() {
OnBoarding()
}

@ExperimentalPagerApi
@Preview(showBackground = true)
@Composable
fun PreviewFunction(){
Surface(modifier = Modifier.fillMaxSize()) {
OnBoarding()
}
}

..

Comments