Daniele Segato
06/30/2021, 4:27 PMTextField
when opening?
I've a big form with TextFields. When i click on one close to the bottom of the screen the IME open up and cover it is there a way to make the view scroll up just enough so that the TextField is visible?
Is this supposed to happen or am I doing something wrong?Ralston Da Silva
06/30/2021, 8:34 PMval relocationRequester = remember { RelocationRequester() }
TextField(
value = "...",
onValueChange = {},
modifier = Modifier.relocationRequester(relocationRequester)
)
You can then use relocationRequester.bringIntoView()
to programmatically scroll the item into view.
This workaround is not perfect because , I don't have recommendation on where you could call bringIntoView()
right now. Here are some suggestions you could try:
You could use it by adding a Modifier.onFocusChanged{ ...}
, but if you do so, it will not work if you dismiss the keyboard and then click on it again, as the focus state isn't changed.
You could use the window inset APIs to figure out when the keyboard is shown, and then call bringIntoView for the TextField that is currently focused: https://developer.android.com/reference/androidx/core/view/WindowInsetsAnimationCompat.Callback#onEnd(androidx.core.view[…]dowInsetsAnimationCompat)Daniele Segato
06/30/2021, 11:07 PMbringIntoView
onFocusChanged
(with an "if focused") today but it didn't consistently work.
I'll try the modifier.
Does the field need to be a direct children of the scrollable view for this to work?
Does it interfere with anything? I've applied the accompanist insets library in my app. Without using nested navigation for the ime.TextField(
modifier = Modifier
// ...
.relocationRequester(remember { RelocationRequester() })
// ...
)
But when I click on a field the keyboard still open covering it.
The same thing happens if I try like this:
val relocationRequester = remember { RelocationRequester() }
TextField(
modifier = Modifier
// ...
.onFocusChanged { if (it.isFocused) relocationRequester.bringIntoView() }
// ...
)
Am I using it incorrectly?Carl Benson
07/01/2021, 7:56 AMval relocationRequester = remember { RelocationRequester() }
val ime = LocalWindowInsets.current.ime
and then on the textfields modifier I added
modifier = Modifier.weight(0.5f)
.relocationRequester(relocationRequester)
.onFocusEvent {
if (it.isFocused && !ime.animationInProgress && ime.isVisible) {
relocationRequester.bringIntoView()
}
}
Daniele Segato
07/01/2021, 8:00 AMimePadding()
modifier + android:windowSoftInputMode="adjustResize"
causing this not to workCarl Benson
07/01/2021, 8:02 AMDaniele Segato
07/01/2021, 10:05 AMval relocationRequester = remember { RelocationRequester() }
var focused by remember { mutableStateOf(false) }
val ime = LocalWindowInsets.current.ime
LaunchedEffect(focused) {
if (focused) {
var done = false
while (!done) {
if (ime.isVisible && !ime.animationInProgress) {
relocationRequester.bringIntoView()
done = true
}
delay(100L)
}
}
}
TextField(
modifier = Modifier
// ...
.onFocusChanged { focused = it }
// ...
)
I'd like some feedback from @Ralston Da Silva on whatever I'm doing something really really bad 🙂Carl Benson
07/01/2021, 2:40 PMDaniele Segato
07/01/2021, 2:42 PMfocused
as key so it relaunch every time that changeCarl Benson
07/02/2021, 11:24 AMRalston Da Silva
07/02/2021, 11:30 PMDaniele Segato
07/04/2021, 9:35 AMModifier.verticalScrollWithRelocation
and replace it to the verticalScroll
modifier?
Also, in your linked example I noticed you placed the Modifier.relocationRequester()
in the box rather than the Button
. I placed it directly in the TextField, should i place it in some outer layer?
thank you very much for your insights!Ralston Da Silva
07/06/2021, 5:01 PMModifier.verticalScrollWithRelocaiton()
until the Relocation API graduates out of Experimental and we can use it in Modifier.VerticalScroll()
.
The relocationRequester uses the coordinates of the item that the Modifier is attached to, and brings those coordinates on screen.In the example I used the Modifier.relocationRequester()
on the green and red boxes which I want to bring into view (Lines 70 and 81). Then in the Button's onClick callback, I call relocationRequester.bringIntoView()
so that I can use the button click to trigger the relocation.
Additional Question:
We are considering adding an extra parameter to allow you to bring a part of an item into view. Something like fun RelocationRequester.bringIntoView(rect: Rect)
If we do this, you would have the ability to specify which part of the item you want to bring into view. Do you think this would be helpful?Daniele Segato
07/06/2021, 5:01 PMrelocationRequester()
could be just placed at an external box of course, just asking.Ralston Da Silva
07/07/2021, 6:32 PMval relocationRequester = remember { RelocationRequester() }
Box(
Modifier
.border(2.dp, blue)
.relocationRequester(relocationRequester)
)
But this would include the border:
val relocationRequester = remember { RelocationRequester() }
Box(
Modifier
.relocationRequester(relocationRequester)
.border(2.dp, blue)
)
And if you want to bring part of the Box into view, you would specify a rect (in the Box's coordinates):
relocationRequester.bringIntoView(Rect(0f, 0f, 10f, 10f)
or use some helper function overloads:
relocationRequester.bringBottomEdgeIntoView()
Daniele Segato
07/08/2021, 9:35 AMCicero
07/09/2021, 8:31 AMRalston Da Silva
07/09/2021, 9:17 AMCicero
07/09/2021, 12:01 PM.onGloballyPositioned {
if (isFocused) {
GlobalScope.async(<http://Dispatchers.IO|Dispatchers.IO>) {
button1Job?.cancel()
button1Job = async(<http://Dispatchers.IO|Dispatchers.IO>) {
var counter = 0
while (counter < 100000 && this.isActive) {
counter++
if (counter % 10000 == 1)
}
if (counter >= 100000 && this.isActive) {
button1Job?.cancel()
relocationRequester.bringIntoView()
}
}
}
}
}
It’s a little rough of an implementation but I realized that onGloballyPositioned changed when you opened the keyboard and stopped when it stopped. This is not the best solution but I wish I could apply something that could rely more on the end of something rather than an arbitrary delayvar isFocused by remember { mutableStateOf(false) }
var requestBringIntoView: Job? = null
.relocationRequester(relocationRequester)
.onFocusChanged {
isFocused = it.isFocused
}
.onGloballyPositioned {
if (isFocused) {
GlobalScope.async() {
requestBringIntoView?.cancel()
requestBringIntoView = async() {
var counter = 0
while (counter < 15 && this.isActive) {
counter++
delay(10)
}
if (this.isActive) {
requestBringIntoView?.cancel()
relocationRequester.bringIntoView()
}
}
}
}
}
This did the trick reliably, I used delay instead of some shady way of trying to implement some sort of deltaTime which delivered awesomely.
It is still arbitrary but at least it’s arbitrary towards the end of the changes on the global position which is a signal that the keyboard stopped animating.
Obviously I’m doing this because of a special situation.
Not only I can’t watch Ime as state, which brings me to some sort of a loop checker anyway, but the Ime values are always true or false.
Don’t quite understand why but I wasn’t able to use them to tell or react to the keyboard changes (for example I can’t watch any changes in isAnimating).
Another characteristic is that this solution is wrapped around the textfield global position.
Ps: I bet there is space to polish the way I’m using coroutines, I know, I would be glad to receive recomendations.Cicero
07/13/2021, 11:35 AMrsktash
08/25/2021, 3:48 PMbatuhan ardor
11/01/2021, 8:21 PMDinesh Gangatharan
07/25/2022, 9:09 PMAndranik Azizbekyan
10/31/2022, 12:55 PMAnuta Vlad Sv
11/23/2022, 9:38 AMDaniele Segato
11/23/2022, 12:47 PM