ARTICLE AD BOX
I'm working on a login screen using Jetpack Compose and I'm trying to implement automatic scrolling to ensure the last button ("Log in without password") is visible when the keyboard appears.
I'm using a Scaffold with a Column containing my input fields and buttons, wrapped in Modifier.verticalScroll(scrollState). I'm using a custom rememberImeState() to detect when the keyboard is active and then calling scrollState.animateScrollTo(scrollState.maxValue) in a LaunchedEffect.
The ImeState code is:
@Composable fun rememberImeState(): State<Boolean> { val imeState = remember { mutableStateOf(false) } val view = LocalView.current DisposableEffect(view) { val listener = ViewTreeObserver.OnGlobalLayoutListener { val isKeyboardOpen = ViewCompat.getRootWindowInsets(view) ?.isVisible(WindowInsetsCompat.Type.ime()) ?: true imeState.value = isKeyboardOpen } view.viewTreeObserver.addOnGlobalLayoutListener(listener) onDispose { view.viewTreeObserver.removeOnGlobalLayoutListener(listener) } } return imeState }The login page
@Composable fun Login() { val imeState = rememberImeState() val scrollState = rememberScrollState() LaunchedEffect(key1 = imeState.value) { if (imeState.value) { scrollState.animateScrollTo(scrollState.maxValue, tween(300)) } } var email by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } Scaffold( topBar = { // Top app bar with a back button and header text TopAppBar( title = {Text("Login")}, navigationIcon = { IconButton( onClick = {}, content = { Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } ) } ) }, ) { innerPadding -> Column( modifier = Modifier .verticalScroll(scrollState) .fillMaxSize() .padding(innerPadding) .padding(16.dp) .focusable(), verticalArrangement = Arrangement.Top, horizontalAlignment = CenterHorizontally ) { Text( modifier = Modifier.fillMaxWidth(), text = "Email or Username", style = MaterialTheme.typography.titleLarge, textAlign = TextAlign.Start, ) Spacer(modifier = Modifier.size(4.dp)) TextField( value = email, onValueChange = { email = it }, ) Spacer(modifier = Modifier.size(32.dp)) Text( modifier = Modifier.fillMaxWidth(), text = "Password", style = MaterialTheme.typography.titleLarge, textAlign = TextAlign.Start, ) Spacer(modifier = Modifier.size(4.dp)) TextField( value = password, onValueChange = { password = it }, ) Spacer(modifier = Modifier.size(4.dp)) Text( text = "errorMessage", fontSize = 12.sp ) Spacer(modifier = Modifier.height(25.dp)) Button( modifier = Modifier.height(43.dp), onClick = {}, content = { Text("Login") } ) Spacer(modifier = Modifier.height(2.dp)) Button(modifier = Modifier.height(43.dp), onClick = {}, content = { Text("Log in without password") } ) } } }
