Read the keyboard's inset in the layout phase again, not in composition
Reported: opening the keyboard lags more than it used to, and the scroll area lags behind the rest of the UI vertically until the keyboard is fully up. Both come from the shape of the previous commit's fix rather than from what it was fixing. Coercing the stuck-open animated inset to zero is right, but it was written as a bottom padding computed in `SessionScreen`'s body -- `padding(bottom = ... + imeInsets.getBottom(this).toDp())` -- and reading the inset there subscribes the whole composable to a value the platform rewrites every frame of the keyboard's animation. That is exactly what the comment above the box says the arrangement exists to avoid: the transcript box was meant to be the whole of what a keyboard frame re-measures, with nothing recomposed at all. Measured on the emulator with the debug button's counters, over one keyboard open on an idle session: `session screen recomposed` 16 before, 1 after -- the one being `isImeVisible` flipping, which is the recomposition the guard actually needs. The per-frame layout work either side is unchanged (17 measures of the transcript, ~0.6ms each), because that is the work the keyboard is supposed to cost. The second symptom is the same cause seen from the other end. The composer is moved by a `graphicsLayer` block, which re-reads the inset in the draw phase of the frame it changed; the transcript's padding was reading it in composition, so the two only stayed together while that recomposition kept landing inside the frame. `imePadding` reads it in the layout phase of the same frame, which is where it was before and where the composer can be followed from by construction. `isImeVisible` still does the correcting -- the modifier is dropped rather than the inset zeroed, which is the same coercion by a different route, so a callback starved of its `onEnd` still cannot leave the composer floating. Verified by tracking the two against each other frame by frame, from a screen recording rather than from uiautomator, whose bounds do not update per frame for a layer translation: the purple outline of the message field and the last message bubble both move -820px over the ~150ms the keyboard takes, and are within the 2px measurement floor of each other on every one of the ten frames in between. Format, compile and lint are clean.
This commit is contained in:
1 parent
aaf475fb1f
commit
28902ac834
2 files changed
+30
-13
No files matched your search
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.isImeVisible
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -1220,19 +1221,20 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
// message -- and then the keyboard's, per frame of its animation. This modifier
|
||||
// is the whole of what the keyboard re-measures: the box's own size never
|
||||
// changes, so nothing above it is touched.
|
||||
.padding(
|
||||
bottom =
|
||||
with(LocalDensity.current) { composerHeight.toDp() } +
|
||||
// Not the `imePadding()` modifier: it trusts the same animated
|
||||
// value `imeVisible` exists to correct, so it is exactly as prone
|
||||
// to sticking open. Coerced to zero the moment the platform says
|
||||
// the keyboard is gone, whatever the animation still claims.
|
||||
if (imeVisible) {
|
||||
with(LocalDensity.current) { imeInsets.getBottom(this).toDp() }
|
||||
} else {
|
||||
0.dp
|
||||
}
|
||||
)
|
||||
.padding(bottom = with(LocalDensity.current) { composerHeight.toDp() })
|
||||
// The keyboard's room, and only while the platform says there is a keyboard --
|
||||
// dropping the modifier is what coerces the stuck-open animated value to zero,
|
||||
// the same guard the composer's translation applies below. It has to stay a
|
||||
// *modifier* rather than a padding computed here: `imePadding` reads the inset
|
||||
// in the layout phase, so a keyboard frame re-measures this box and nothing
|
||||
// else, while reading `imeInsets` in this composable body subscribes the whole
|
||||
// of `SessionScreen` to a value that changes every frame of the animation.
|
||||
// That cost 16 full recompositions of this screen per keyboard open, against
|
||||
// one, and it made the transcript's position depend on a recomposition landing
|
||||
// inside the frame that the inset changed -- which the composer's does not,
|
||||
// since its translation is re-read in that frame's draw phase. When the
|
||||
// recomposition misses, the transcript trails the composer up the screen.
|
||||
.then(if (imeVisible) Modifier.imePadding() else Modifier)
|
||||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
TranscriptList(
|
||||
|
||||
Reference in new issue
Block a user