kotlinandroid-jetpack-compose

How to put a Row in foreground of my composable


Hello in my project id like to create a pager that slides to show the user some informations before being able to go throught the app. A bit like an onboarding. the issue is that i discovered while coding that the part 5 the buttons is always the same in evry pages so i want to code it only once on the pager. Any ideas on how to do that ? code of the pager:

@Composable
fun TTPOAOnboardingView(
onSkip: () -> Unit,
onContinue: () -> Unit,
) {

var selectedIndex by remember { mutableIntStateOf(1) }
val tabs = 5

val pagerState = rememberPagerState(
    initialPage = 0, pageCount = { 5 })

val listState = rememberLazyListState()

LaunchedEffect(selectedIndex) {
    pagerState.animateScrollToPage(selectedIndex)
    listState.animateScrollToItem(selectedIndex)
}

LaunchedEffect(pagerState.currentPage) {
    selectedIndex = pagerState.currentPage
    listState.animateScrollToItem(pagerState.currentPage)
}
Box(
    Modifier.fillMaxSize()
) {

    Image(
        painter = painterResource(R.drawable.ttpoa_onboarding_bg),
        contentDescription = null,
        contentScale = ContentScale.Crop,
        modifier = Modifier.fillMaxSize()
    )



    Column(
        modifier = Modifier.fillMaxSize(),
    ) {
        LazyRow(
            state = listState,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 6.dp, vertical = 8.dp)
                .background(Color.Transparent),
            horizontalArrangement = Arrangement.spacedBy(1.dp)
        ) {
            items(tabs) { index ->

                val backgroundColor =
                    if (index <= selectedIndex) StancerColor.TextBlue.color else StancerColor.TextRed.color

                Box(
                    modifier = Modifier
                        .background(backgroundColor)
                        .padding(horizontal = 10.dp),
                    contentAlignment = Alignment.Center
                ) {
                    Box(
                        Modifier
                            .height(3.dp)
                            .width(70.dp)
                            .background(backgroundColor)
                    )
                }
            }
        }


        HorizontalPager(
            state = pagerState, modifier = Modifier.weight(1f)
        ) { pageIndex ->

            when (pageIndex) {
                0 -> {

                    TTPOAOnboardingFirstScreen(
                        onSkip = {}) { }
                }

                1 -> {
                    TTPOAOnboardingSecondScreen(
                        onSkip = { /*TODO()*/ },
                        onContinue = { /*TODO()*/ })
                }

                2 -> {
                    TTPOAOnboardingThirdScreen(
                        onSkip = { /*TODO()*/ },
                        onContinue = { /*TODO()*/ })
                }

                3 -> {

                }

                4 -> {

                }
            }
        }
    }
 }

here is the code of one of my pages:

@Composable
fun TTPOAOnboardingThirdScreen(
onSkip: () -> Unit,
onContinue: () -> Unit,
) {
Box(modifier = Modifier.fillMaxSize()) {

    /* ---------- 2) MOCK-UP TELEPHONE ---------- */
    Image(
        painter = painterResource(R.drawable.ttpoa_tap_android),
        contentDescription = "Démonstration Tap to Pay",
        modifier = Modifier
            .align(Alignment.BottomCenter)
            .aspectRatio(0.30f).padding(top = 130.dp)
    )

    /* ---------- 3) VOILE BLANC DÉGRADÉ ---------- */
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(250.dp)                     // ajuste la hauteur du fondu
            .align(Alignment.BottomCenter)
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        Color.Transparent,
                        Color.White,
                        Color.White
                    )
                )
            )
    )

    /* ---------- 4) CONTENU (TITRE) ---------- */
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(horizontal = 24.dp, vertical = 32.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Choisissez Tap to Pay et présente votre Android au client",
            style = Title1,
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(top = 40.dp)
        )
    /* ---------- 5) CONTENU (Buttons) ---------- */
        Spacer(Modifier.weight(1f))

        Row(
            modifier = Modifier
                .fillMaxWidth()
                .height(56.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically
        ) {
            TextButton(onClick = onSkip) {
                Text("Passer", style = Callout, color = StancerColor.Grey900.color)
            }

            StancerButton(
                title = "Continuer",
                modifier = Modifier.width(150.dp),
                isLoading = false,
                isEnabled = true,
                backgroundColor = StancerColor.TextBlue
            ) { onContinue() }
        }
    }}

enter image description here


Solution

  • You can move the Buttons outside of the HorizontalPager:

    @Preview
    @Composable
    fun HorizontalPagerDemo(
        onSkip: () -> Unit = {},  // skip introduction
        onContinue: () -> Unit = {}  // continue after introduction is finished
    ) {
    
        val coroutineScope = rememberCoroutineScope()
        val pagerState = rememberPagerState { 5 }
    
        Column(
            modifier = Modifier
                .safeContentPadding()
                .fillMaxSize(),
        ) {
            // LazyRow...
            HorizontalPager(
                modifier = Modifier.weight(1f),
                state = pagerState
            ) { pageIndex ->
                Box(
                    modifier = Modifier.fillMaxSize(),
                    contentAlignment = Alignment.Center
                ) {
                    Text(
                        text = "Page $pageIndex",
                        fontSize = 25.sp
                    )
                }
            }
            Row(
                modifier = Modifier.fillMaxWidth()
            ) {
                Button(
                    modifier = Modifier.weight(1f),
                    onClick = onSkip
                ) {
                    Text("PASSER")
                }
                Spacer(modifier = Modifier.size(8.dp))
                Button(
                    modifier = Modifier.weight(1f),
                    onClick = {
                        if (pagerState.currentPage < pagerState.pageCount - 1) {
                            // navigate to next page
                            coroutineScope.launch {
                                pagerState.animateScrollToPage(pagerState.currentPage + 1)
                            }
                        } else {
                            // navigate to next screen
                            onContinue()
                        }
                    }
                ) {
                    Text("CONTINUER")
                }
            }
        }
    }
    

    Alternatively, if you want the Buttons to really be in front of the content, you can use a Box Composable with the following structure:

    Box {
        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            LazyRow()
            HorizontalPager()
        }
        Row(
            modifier = Modifier.align(Alignment.BottomCenter)
        ) {
            Button()
            Button()
        }
    }
    

    Output:

    Screen Recording