Fix NavBarDialog layout when soft keyboard opens#983
Conversation
Nain57
left a comment
There was a problem hiding this comment.
Hi and thank you for your contributions.
I see you include a commit included in another PR. Please either commit all your fixes on the same branch, or make them atomic.
|
|
||
| private fun setupPortraitViews() { | ||
| dialogCoordinatorLayout?.apply { | ||
| // Add the navigation bar. |
There was a problem hiding this comment.
That's a lot of additions dedicated to ime management, this should be extracted in a separate class
| insets | ||
| } | ||
| decor.viewTreeObserver.addOnGlobalLayoutListener(imeLayoutListener) | ||
| ViewCompat.requestApplyInsets(decor) |
There was a problem hiding this comment.
The code installs three separate listeners that all try to do the same thing:
// (a) WindowInsetsAnimationCompat.Callback → onProgress / onEnd
// (b) setOnApplyWindowInsetsListener → inset dispatch
// (c) OnGlobalLayoutListener → layout fallback
The imeLayoutListener (c) is described as a fallback for accessibility overlays that skip inset animations, but it fires on every layout pass; including those caused by the translation animations in (a). This creates a feedback loop risk: applyImeLift triggers a layout change → imeLayoutListener fires → updateImeLiftAnimated is called → if imeAnimationRunning is false it starts another animator, etc.
The imeAnimationRunning flag partially guards this, but onEnd clears it and then calls updateImeLiftAnimated() synchronously, which starts a new animator. If that animator's addUpdateListener triggers another layout, the flag won't be set (no animation running at that moment), so imeLayoutListener will call updateImeLiftAnimated again; potentially firing a second animator on top of an existing one.
Remove the OnGlobalLayoutListener fallback or scope it strictly (set a flag during the animation window and remove the listener once a first valid inset is received). The three-mechanism approach is the main source of complexity and potential bugs.
| (method.invoke(imm) as? Int) ?: 0 | ||
| } catch (_: Throwable) { | ||
| 0 | ||
| } |
There was a problem hiding this comment.
getInputMethodWindowVisibleHeight is a hidden @hide API that has been removed/renamed across Android versions. It will throw NoSuchMethodException on some devices (silently caught and returning 0), making the fallback unreliable; and the try/catch pattern swallows all exceptions including unexpected ones.
Replace the reflection with ViewCompat.getRootWindowInsets(decor)?.isVisible(WindowInsetsCompat.Type.ime()) as the truth source — you're already using it in updateImeLiftAnimated.
| private fun applyImeLift(coordinator: CoordinatorLayout, imeBottomPx: Int) { | ||
| if (imeBottomPx == lastImeLiftPx) return | ||
| lastImeLiftPx = imeBottomPx | ||
| for (i in 0 until coordinator.childCount) { |
There was a problem hiding this comment.
Translating each child individually means any view added or removed while the IME is up will start at translationY = 0, causing a visual jump. It would be safer to translate the coordinator itself (or its parent), or to set translationY in a post-layout pass.
| ?.bottom | ||
| ?: 0 | ||
| val lift = max(insetIme, inputMethodVisibleHeight()) | ||
| val threshold = (decor.resources.displayMetrics.heightPixels * 0.12f).toInt() |
There was a problem hiding this comment.
12% of screen height is used to distinguish "real IME showing" from noise, but this is undocumented and device-dependent. A 12% threshold on a small phone (~720px) is ~86px, which may suppress legitimate small IME appearances (e.g. number pad on some OEMs). This deserves at minimum a named constant and a comment.
Extract IME lift into NavBarDialogImeLiftController. Drive lift from WindowInsets animations + apply-insets only (no GlobalLayout fallback / hidden IMM API). Translate the coordinator itself and use IME visibility from WindowInsetsCompat.
|
Updated to address the review:
|
1685c5c to
b611759
Compare
|
Needs carefull review and testing, i'll come back to you once everything is OK from my side. |
Summary
NavBarDialoglayouts (e.g. Scenario settings) where opening the soft keyboard could hide the bottom navigation bar and leave a large empty gap in the content area.OverlayDialogs (e.g. trigger/event pages) can useSOFT_INPUT_ADJUST_RESIZE.NavBarDialogcannot: in portrait the bottom bar is attached to theCoordinatorLayout, andADJUST_RESIZEparks the sheet oddly on some devices.NavBarDialogusesSOFT_INPUT_ADJUST_NOTHINGand lifts the sheet plus bottom bar with the IME (viaWindowInsetsAnimationCompat, with a short fallback animation when overlay insets are incomplete).Changes
NavBarDialog.kt: overrideapplySoftInputMode(), add animated IME lift for sheet and bottom bar siblings.BadTokenExceptionfix from Fix crash when opening floating settings (BadTokenException) #982 (OverlayDialogno longer forcesuseWindowContext, restores adjustResize for simple overlay dialogs).Notes
OverlayDialogs keepADJUST_RESIZEand are unchanged by the NavBar-specific lift path.