Scroll To Textfield In Column On Jetpack Compose

1 minute read

Published:

  1. You can either use BringIntoViewRequester read about it here
    Read the documentation here

  2. Or, use Modifier.onGloballyPositioned { coordinates -> //save the value }

create an object that stores your textfield state (position) say textFieldState.

  class TextFieldState {
    ...

    fun onGloballyPositioned(coordinates: LayoutCoordinates) {
        scrollPosition = coordinates.positionInWindow().y.toInt()
    }
    ...
  }

next, getKeyboardVisibility

@Composable
fun getKeyboardVisibility(): State<Boolean> {
    val keyboardState = remember { mutableStateOf(false) }
    val view = LocalView.current
    DisposableEffect(view) {
        val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
            val rect = Rect()
            view.getWindowVisibleDisplayFrame(rect)
            val screenHeight = view.rootView.height
            val keypadHeight = screenHeight - rect.bottom
            keyboardState.value = keypadHeight > screenHeight * 0.15
        }
        view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)

        onDispose {
            view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
        }
    }
    return keyboardState
}

then, handle it, every textfield on your screen.

@ExperimentalComposeUiApi
@Composable
fun HandleBottomTextField(
    isKeyboardVisible: Boolean,
    scrollState: ScrollState,
    vararg textFieldStateList: BaseTextFieldState,
    bottomInset: Int,
    isTheLastScrollToBottom: Boolean = true,
    coroutineScope: CoroutineScope,
) {
    textFieldStateList.forEachIndexed { _, textFieldState ->
        if (textFieldState.isFocused.value && isKeyboardVisible) {
            LaunchedEffect(
                textFieldState.isFocused.value,
                isKeyboardVisible
            ) {
                coroutineScope.launch {
                    delay(CommonUtil.DEFAULT_DELAY_SCROLL_IN_MILLISECONDS)
                    if(textFieldState == textFieldStateList.last() ||
                        textFieldState == textFieldStateList[textFieldStateList.lastIndex-1]){
                        scrollState.animateScrollTo(scrollState.maxValue)
                    } else {
                        scrollState.animateScrollBy(getScrollByValue(textFieldState.scrollPosition > 450, textFieldState.scrollPosition))
                    }
                }
            }
        }
    }
}

you can adjust yourself regarding the value

fun getScrollByValue(isPositionBelowCurrentScrollState: Boolean,
                     textFieldScrollPosition: Int) : Float {
    if(isPositionBelowCurrentScrollState){
        val scrollByValue = textFieldScrollPosition - 450
        return scrollByValue.toFloat()
    } else {
        val scrollByValue = 450 - textFieldScrollPosition
        return -scrollByValue.toFloat()
    }
}

there it goes

Action