Jetpack compose code to scroll down to the position of a specific UI element on clicking a Text

17.7k views Asked by At

I am trying to scroll down to the position of a specific UI element on clicking a Text.

The code for my Text is:

                Text(
                "What is autosaving?",
                color = colorResource(id = R.color.text_highlight),
                fontSize = with(LocalDensity.current) {
                    dimensionResource(id = R.dimen._11ssp).toSp()
                },
                fontFamily = FontFamily(
                    Font(R.font.poppins_regular)
                ),
                modifier = Modifier.constrainAs(whatIsAutosaving) {
                    top.linkTo(glWhatIsAutoSaving)
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                },
            )

On clicking this Text my screen should scroll to the beginning position of another Text. The code for this another Text is:

        Text(
        stringResource(id = R.string.autosave_info),
        color = colorResource(id = R.color.bright_green),
        fontSize = with(LocalDensity.current) {
            dimensionResource(id = R.dimen._11ssp).toSp()
        },                fontFamily = FontFamily(
            Font(R.font.poppins_regular)
        ),
        modifier = Modifier.constrainAs(autoSaveInfo) {
            top.linkTo(glAutoSaveInfo)
            start.linkTo(glLeft)
            end.linkTo(glRight)
            width = Dimension.fillToConstraints
        },
    )

How do I achieve this?

EDIT:

The complete code for my screen is:

@Composable
fun Autosave(navController: NavController) {
    val query = remember { mutableStateOf("") }
    val errorMsg = remember { mutableStateOf(false) }

    Box(
        modifier = Modifier
            .background(color = MaterialTheme.colors.background)
            .fillMaxSize()
    ) {
        ConstraintLayout(
            modifier = Modifier.verticalScroll(rememberScrollState())

        ) {

            val (logo, illustration, title, triangle, slider, percent, maxLimitTxt,
                maxLimitTextField, buttonSave, whatIsAutosaving, autoSaveInfo, progressBar,
                detailsRow, iconUp, spacer, error) = createRefs()
            val glLogo = createGuidelineFromTop(0.0075f)
            val glIllustrationTop = createGuidelineFromTop(0.0235f)
            val glIllustrationBottom = createGuidelineFromTop(0.045f)
            val glIllustrationLeft = createGuidelineFromStart(0.27f)
            val glIllustrationRight = createGuidelineFromEnd(0.27f)
            val glTitle = createGuidelineFromTop(0.053f)
            val glSlider = createGuidelineFromTop(0.062f)
            val glMaxLimitTxt = createGuidelineFromTop(0.086f)
            val glMaxLimitTextField = createGuidelineFromTop(0.09f)
            val glButtonSaveTop = createGuidelineFromTop(0.11f)
            val glButtonSaveBottom = createGuidelineFromTop(0.12f)
            val glWhatIsAutoSaving = createGuidelineFromTop(0.125f)
            val glAutoSaveInfo = createGuidelineFromTop(0.175f)
            val glSpacer = createGuidelineFromTop(0.99f)
            val glLeft = createGuidelineFromStart(0.1f)
            val glRight = createGuidelineFromEnd(0.1f)
            val glRightIcon = createGuidelineFromEnd(0.825f)
            val glLeftTextField = createGuidelineFromStart(0.3f)
            val glRightTextField = createGuidelineFromEnd(0.3f)

            val coroutineScope = rememberCoroutineScope()
            val scrollState = rememberScrollState()
            var scrollToPosition  by remember { mutableStateOf(0F) }


            Image(
                painter = painterResource(id = R.drawable.effect_app_bg_720),
                contentDescription = "effect top",
                modifier = Modifier
                    .fillMaxSize()
                    .scale(1.325f)
            )

            Image(
                painter = painterResource(id = R.drawable.logo_voodlee),
                contentDescription = "logo", modifier = Modifier
                    .constrainAs(logo) {
                        top.linkTo(glLogo)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                    }
            )


            Image(
                painter = painterResource(id = R.drawable.img_autosaving),
                contentDescription = "autosave image",
                modifier = Modifier.constrainAs(illustration) {
                    top.linkTo(glIllustrationTop)
                    bottom.linkTo(glIllustrationBottom)
                    start.linkTo(glIllustrationLeft)
                    end.linkTo(glIllustrationRight)
                    width = Dimension.fillToConstraints
                    height = Dimension.fillToConstraints
                }
            )


            Text(
                "Set the percentage for autosaving",
                color = colorResource(id = R.color.bright_green),
                fontSize = with(LocalDensity.current) {
                    dimensionResource(id = R.dimen._13ssp).toSp()
                }, fontFamily = FontFamily(
                    Font(R.font.poppins_regular)
                ),
                modifier = Modifier.constrainAs(title) {
                    top.linkTo(glTitle)
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                }
            )


            Image(
                painter = painterResource(id = R.drawable.ic_triangle_dn),
                modifier = Modifier
                    .height(39.dp)
                    .width(29.dp)
                    .constrainAs(triangle) {
                        top.linkTo(title.bottom)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                    },
                contentDescription = "triangle down"
            )


            Column(modifier = Modifier.constrainAs(slider) {
                top.linkTo(glSlider)
                start.linkTo(parent.start)
                end.linkTo(parent.end)

            }) {
                val context = LocalContext.current
                val customView = remember { com.shawnlin.numberpicker.NumberPicker(context) }
                // Adds view to Compose
                AndroidView({ customView }) { view ->
                    // View's been inflated - add logic here if necessary
                    with(view) {
                        orientation = HORIZONTAL
                        //dividerDrawable = ResourcesCompat.getDrawable(resources, R.drawable.bg_blue, null)
                        textColor =
                            ResourcesCompat.getColor(resources, R.color.slider_num_color, null)
                        selectedTextColor =
                            ResourcesCompat.getColor(resources, R.color.slider_num_color, null)
                        selectedTextSize = 120f
                        wheelItemCount = 6
                        value = 10
                        minValue = 0
                        maxValue = 99
                        layoutParams.width = MATCH_PARENT
                        setDividerColorResource(R.color.fade_green)
                        setDividerDistance(180)
                        setDividerThickness(10)

                    }
                }


                Text(
                    "%",
                    color = colorResource(id = R.color.bright_green),
                    fontSize = with(LocalDensity.current) {
                        dimensionResource(id = R.dimen._18ssp).toSp()
                    }, fontFamily = FontFamily(
                        Font(R.font.poppins_regular)
                    ),
                    modifier = Modifier
                        .align(CenterHorizontally)
                        .offset(y = (-5).dp)
                )

            }
            Text(
                "Max Limit per autosaving",
                color = colorResource(id = R.color.bright_green),
                fontSize = with(LocalDensity.current) {
                    dimensionResource(id = R.dimen._13ssp).toSp()
                },

                fontFamily = FontFamily(
                    Font(R.font.poppins_regular)
                ),
                modifier = Modifier.constrainAs(maxLimitTxt) {
                    top.linkTo(glMaxLimitTxt)
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                }
            )


            TextField(
                value = query.value,
                onValueChange = { newValue -> query.value = newValue
                                  if (newValue != "")
                                  errorMsg.value = newValue.toInt() > 1500
                                },
                label = {
                    Text("    Amount",
                        color = colorResource(id = R.color.bright_green),
                        fontSize = with(LocalDensity.current) {
                            dimensionResource(id = R.dimen._15ssp).toSp()
                        },
                        textAlign = TextAlign.Center
                    )
                },
                textStyle = TextStyle(
                    textAlign = TextAlign.Center,
                    color = colorResource(id = R.color.bright_green),
                    fontFamily = FontFamily(Font(R.font.poppins_regular)),
                    fontSize = with(LocalDensity.current) {
                        dimensionResource(id = R.dimen._15ssp).toSp()
                    },
                ),
                modifier = Modifier.constrainAs(maxLimitTextField) {
                    top.linkTo(glMaxLimitTextField)
                    start.linkTo(glLeftTextField)
                    end.linkTo(glRightTextField)
                    width = Dimension.fillToConstraints
                },
                colors = TextFieldDefaults.textFieldColors(

                    backgroundColor = Color.Transparent,
                    unfocusedIndicatorColor = colorResource(id = R.color.bright_green),
                    focusedIndicatorColor = colorResource(id = R.color.bright_green)
                )
            )

             Text(
                    text =
                        "*Please enter amount less than Rs.1500",
                    fontSize = with(LocalDensity.current) {
                        dimensionResource(id = R.dimen._8ssp).toSp()
                    },
                    color = colorResource(id = R.color.voodlee_red),
                    modifier = Modifier
                        .padding(top = 8.dp)
                        .alpha(
                            if (errorMsg.value) {
                                1f
                            } else 0f
                        )
                        .constrainAs(error) {
                            top.linkTo(maxLimitTextField.bottom)
                            start.linkTo(parent.start)
                            end.linkTo(parent.end)
                        },
                )

            Button(
                onClick = {
                    navController.navigate("fourth_screen")
                },
                modifier = Modifier.constrainAs(buttonSave) {
                    top.linkTo(glButtonSaveTop)
                    bottom.linkTo(glButtonSaveBottom)
                    start.linkTo(glLeft)
                    end.linkTo(glRight)
                    width = Dimension.fillToConstraints
                    height = Dimension.fillToConstraints

                },
                colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(id = R.color.voodlee_red))
            ) {
                Text(
                    "Save", color = colorResource(id = R.color.dark_blue),
                    fontSize = with(LocalDensity.current) {
                        dimensionResource(id = R.dimen._16ssp).toSp()
                    },
                )
            }

            Text(
                "What is autosaving?",
                color = colorResource(id = R.color.text_highlight),
                fontSize = with(LocalDensity.current) {
                    dimensionResource(id = R.dimen._11ssp).toSp()
                },
                fontFamily = FontFamily(
                    Font(R.font.poppins_regular)
                ),
                modifier = Modifier
                    .constrainAs(whatIsAutosaving) {
                        top.linkTo(glWhatIsAutoSaving)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                    }
                    .clickable {
                        coroutineScope.launch {
                            scrollState.animateScrollTo(scrollToPosition.roundToInt())
                        }
                    },
            )

            Text(
                stringResource(id = R.string.autosave_info),
                color = colorResource(id = R.color.bright_green),
                fontSize = with(LocalDensity.current) {
                    dimensionResource(id = R.dimen._11ssp).toSp()
                },                fontFamily = FontFamily(
                    Font(R.font.poppins_regular)
                ),
                modifier = Modifier
                    .constrainAs(autoSaveInfo) {
                        top.linkTo(glAutoSaveInfo)
                        start.linkTo(glLeft)
                        end.linkTo(glRight)
                        width = Dimension.fillToConstraints
                    }
                    .onGloballyPositioned { coordinates ->
                        scrollToPosition = coordinates.positionInParent().y
                    },
            )

            Row(
                modifier = Modifier
                    .padding(top = 40.dp, bottom = 50.dp)
                    .constrainAs(detailsRow) {
                        top.linkTo(autoSaveInfo.bottom)
                        start.linkTo(glLeft)
                        end.linkTo(glRight)
                        width = Dimension.fillToConstraints
                    },
            ) {
                Text(
                    text = "For more details",
                    color = colorResource(id = R.color.bright_green),
                    fontSize = with(LocalDensity.current) {
                        dimensionResource(id = R.dimen._11ssp).toSp()
                    },                     fontFamily = FontFamily(
                        Font(R.font.poppins_regular)
                    ),
                )

                Spacer(modifier = Modifier.padding(5.dp))
                Text(
                    text = "Click here",
                    color = colorResource(id = R.color.text_highlight),
                    fontSize = with(LocalDensity.current) {
                        dimensionResource(id = R.dimen._11ssp).toSp()
                    },
                    fontFamily = FontFamily(
                        Font(R.font.poppins_regular)
                    ),
                )
            }
            Image(
                painter = painterResource(id = R.drawable.ic_btn_upward),
                modifier = Modifier
                    .height(32.dp)
                    .constrainAs(iconUp) {
                        top.linkTo(detailsRow.bottom)
                        start.linkTo(glLeft)
                        end.linkTo(glRightIcon)
                        width = Dimension.fillToConstraints
                    },
                contentDescription = ""
            )
        
        Spacer(modifier = Modifier
            .padding(bottom = 50.dp)
            .constrainAs(spacer) {
                top.linkTo(glSpacer)
                start.linkTo(parent.start)
                end.linkTo(parent.end)
            },)
        
        }
        
        Card(
            Modifier
                .align(BottomCenter)
                .fillMaxWidth()
                .alpha(if (query.value == "") 1f else 0f),
                 backgroundColor  = MaterialTheme.colors.secondaryVariant

        ) {
            ProgressBar5UI(
                Modifier
                    .padding(start = 40.dp, end = 40.dp, top = 10.dp)

            )
        }

        Card(
            Modifier
                .align(BottomCenter)
                .fillMaxWidth()
                .alpha(if (errorMsg.value) 1f else 0f),
            backgroundColor  = MaterialTheme.colors.secondaryVariant

        ) {
            ProgressBar6UI(
                Modifier
                    .padding(start = 40.dp, end = 40.dp, top = 10.dp)

            )
        }

        Card(
            Modifier
                .align(BottomCenter)
                .fillMaxWidth()
                .alpha(if (query.value != "" && !errorMsg.value) 1f else 0f),
        backgroundColor  = MaterialTheme.colors.secondaryVariant

        ) {
            ProgressBar7UI(
                Modifier
                    .padding(start = 40.dp, end = 40.dp, top = 10.dp)

            )
        }
    }
}

Is there possibly any special way to scroll on clicking an element while using Constraint Layout?

2

There are 2 answers

4
Gabriele Mariotti On BEST ANSWER

You can use the onGloballyPositioned modifier to retrieve the position of a composable and then use the method scrollState.animateScrollTo to scroll to that position.

Something like:

val coroutineScope = rememberCoroutineScope()
val scrollState = rememberScrollState()
var scrollToPosition  by remember { mutableStateOf(0F) }

Column(Modifier.verticalScroll(scrollState)) {
    Text(
        "Click here to scroll",
        modifier = Modifier.clickable {
            coroutineScope.launch {
                scrollState.animateScrollTo(scrollToPosition.roundToInt())
            }
        }
    )

    //...

    Text(
        "Target",
        modifier = Modifier.onGloballyPositioned { coordinates ->
            scrollToPosition = coordinates.positionInRoot().y
        }
    )
   
}

enter image description here

1
cumpatomas On

Is there away to scroll to a position without using a click listener? – Marty Miller Dec 9, 2022 at 0:03

Yes, it worked for me outside a listener pointing to a resource like:

if (landscapeActive) {
    coroutineScope.launch {
        scrollState.animateScrollTo(R.drawable.your_image)
    }
}

I suppose it would work with a string res also