Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Insets] How to use navigationBarsWithImePadding with Column verticalScroll? #210

Closed
radzio opened this issue Feb 23, 2021 · 11 comments
Closed
Labels
stale Stale issues which are marked for closure

Comments

@radzio
Copy link

radzio commented Feb 23, 2021

Hello!

What is the best way to handle Column verticalScroll and navigationBarsWithImePadding?

I have layout like this:

When keyboard is shown it looks like this:

What I want to achieve (automatically scroll to given or focused composable when IME is shown) :

I was trying to play with scrollState.smoothScrollTo but without any reliable success :(. @chrisbanes do you have any recommendations or best ways of doing so?

Code:

class ExampleFragment: Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View = ComposeView(requireContext()).apply {
        layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )

        val windowInsets = ViewWindowInsetObserver(this).start(consumeWindowInsets = false)

        setContent {
            Providers(
                LocalWindowInsets provides windowInsets
            ) {

                val scrollState = rememberScrollState()
                var textValue = remember { TextFieldValue() }

                Column(
                    modifier = Modifier.fillMaxSize(1F)
                        .statusBarsPadding()
                        .navigationBarsWithImePadding()
                        .verticalScroll(scrollState)
                        .padding(horizontal = 16.dp),
                    verticalArrangement = Arrangement.Top,
                ) {

                    Text(
                        text = "Hi there ❤",
                        fontSize = 46.sp,
                        style = TextStyle(
                            fontWeight = FontWeight.Bold,
                            textAlign = TextAlign.Center
                        ),
                        modifier = Modifier.fillMaxWidth(1F)
                    )

                    Spacer(modifier = Modifier.height(250.dp))

                    Text(
                        text = "More content",
                        fontSize = 20.sp,
                        style = TextStyle(
                            fontWeight = FontWeight.Bold,
                            textAlign = TextAlign.Center
                        ),
                        modifier = Modifier.fillMaxWidth(1F)
                    )

                    Text(
                        text = "More content #2",
                        fontSize = 20.sp,
                        style = TextStyle(
                            fontWeight = FontWeight.Bold,
                            textAlign = TextAlign.Center
                        ),
                        modifier = Modifier.fillMaxWidth(1F)
                    )

                    TextField(value = textValue, onValueChange = { textValue = it })
                }
            }
        }
    }
}
@chrisbanes
Copy link
Contributor

Hmmm, using smoothScrollToItem() is probably the right thing here. I don't think this will ever be automatic, since the Modifier has no idea about any other composables around it.

I think we might be missing a callback from the modifier to know when to call smoothScrollToItem though.

FYI, I think you'll have better luck with laying out from the bottom:

Column(
    modifier = Modifier
        // other modifiers
        .verticalScroll(scrollState, reverseScrolling = true),
    verticalArrangement = Arrangement.Bottom,
)

Laying out from the bottom means that the scroll position should remain the same as the Column changes size.

@qrezet
Copy link

qrezet commented Mar 3, 2021

Hi, I have an even problematic scenario about managing the same thing.

The problem is that, my TextFields are inside Column which is inside LazyColumn's item().

I could call LazyState.animateScrollToItem() but I don't know how to get the correct Offset to properly position the focused TextField.

Do you guys have any idea how to do this?

@chrisbanes
Copy link
Contributor

I don't think this is supported yet (but I'll ask the team later). I just saw that someone raised this bug which is very related.

@qrezet
Copy link

qrezet commented Mar 3, 2021

Thanks @chrisbanes

@radzio
Copy link
Author

radzio commented Mar 18, 2021

@qrezet have you find any solution or workaround to your problem

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the stale Stale issues which are marked for closure label May 19, 2021
@chrisbanes chrisbanes reopened this Jul 20, 2021
@chrisbanes chrisbanes removed the stale Stale issues which are marked for closure label Jul 20, 2021
@chrisbanes chrisbanes changed the title [Help][Example needed] How to use navigationBarsWithImePadding with Column verticalScroll? [Insets] How to use navigationBarsWithImePadding with Column verticalScroll? Jul 20, 2021
@chrisbanes
Copy link
Contributor

chrisbanes commented Jul 20, 2021

The recent RelocationRequester API should allow us to do this. Just spent 10 mins and come up with this:

@ExperimentalComposeUiApi
fun Modifier.bringIntoViewAfterImeAnimation(): Modifier = composed {
    val imeInsets = LocalWindowInsets.current.ime
    var focusState by remember { mutableStateOf<FocusState?>(null) }
    val relocationRequester = remember { RelocationRequester() }

    LaunchedEffect(
        imeInsets.isVisible,
        imeInsets.animationInProgress,
        focusState,
        relocationRequester
    ) {
        if (imeInsets.isVisible &&
            !imeInsets.animationInProgress &&
            focusState?.isFocused == true) {
            relocationRequester.bringIntoView()
        }
    }

    relocationRequester(relocationRequester)
        .onFocusChanged { focusState = it }
}

var textField by remember { mutableStateOf(TextFieldValue()) }
TextField(
    value = textField,
    onValueChange = { textField = it },
    modifier = Modifier.bringIntoViewAfterImeAnimation()
)

It's not perfect, but it should form the basis of a solution. The idea is that each focusable item needs to be responsible for requesting that it is visible when necessary. The only difference from the usual logic is that we need to wait for any IME animations to finish before requesting.

Note: RelocationRequester seems to currently only work with scrolling Column or Row (i.e. not LazyColumn, etc)

@burhankhanzada
Copy link

it doesn't work for me

@b95505017
Copy link

@chrisbanes It works with latest 1.0.0, but no effect with latest snapshot version. 😢

@chrisbanes
Copy link
Contributor

I think something is currently broken with RelocationRequester & LazyColumn. Not much we can do at this point.

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the stale Stale issues which are marked for closure label Sep 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale Stale issues which are marked for closure
Projects
None yet
Development

No branches or pull requests

5 participants