Smooth Keyboard Animation with BottomSheetDialog + Jetpack Compose
If you’ve ever tried to make a BottomSheetDialogFragment with an input field animate smoothly alongside the soft keyboard in Jetpack…
Smooth Keyboard Animation with BottomSheetDialog + Jetpack Compose
If you’ve ever tried to make a BottomSheetDialogFragment with an input field animate smoothly alongside the soft keyboard in Jetpack Compose — you know the pain. The bottom sheet either jumps, lags behind, or the content gets clipped.
I spent a fair amount of time fighting this, so here’s the solution that actually works.
The Problem
You have a BottomSheetDialogFragment with Compose content that includes a TextField. When the keyboard opens, you want the entire bottom sheet to slide up in perfect sync — no stuttering, no double-jumps, no gap between the keyboard and your content.
The natural instinct is to reach for Compose’s imePadding() modifier. It works great in regular activities and fragments. But inside a BottomSheetDialog? Not so much — at least not on its own.
Why imePadding() Alone Isn't Enough
A BottomSheetDialog has its own View hierarchy:
Window
└── CoordinatorLayout ← the "coordinator"
└── FrameLayout ← design_bottom_sheet
└── Your ComposeView
imePadding() only affects content inside the ComposeView. It adds bottom padding to push your Compose content up, but the BottomSheetDialog container itself (the CoordinatorLayout and FrameLayout) stays put. The result: your text field might scroll into view inside the sheet, but the sheet doesn't physically move up with the keyboard.
The Solution: Three Layers Working Together
You need all three of these pieces. Drop any one and it breaks.
1. Disable the System’s Default Keyboard Handling
In onCreateDialog, tell the system to stop managing the soft input adjustment on its own:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setOnShowListener {
val d = dialog as BottomSheetDialog
val bottomSheet = d.findViewById<View>(
com.google.android.material.R.id.design_bottom_sheet
) as FrameLayout
BottomSheetBehavior.from(bottomSheet).apply {
state = BottomSheetBehavior.STATE_EXPANDED
isDraggable = false
}
dialog.window?.let { window ->
WindowCompat.setDecorFitsSystemWindows(window, false)
window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
)
}
}
return dialog
}
setDecorFitsSystemWindows(false) allows insets to pass through to your content rather than the system consuming them. SOFT_INPUT_ADJUST_NOTHING prevents the window from auto-resizing or auto-panning — we'll handle the movement ourselves.
2. Animate the Coordinator with the Keyboard
In onStart, set up a WindowInsetsAnimationCompat.Callback on the dialog's decorView. This physically moves the CoordinatorLayout upward as the keyboard slides in:
override fun onStart() {
super.onStart()
val coordinator = dialog?.findViewById<View>(
com.google.android.material.R.id.coordinator
)
dialog?.window?.decorView?.let { decorView ->
ViewCompat.setWindowInsetsAnimationCallback(
decorView,
object : WindowInsetsAnimationCompat.Callback(
DISPATCH_MODE_CONTINUE_ON_SUBTREE
) {
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: List<WindowInsetsAnimationCompat>,
): WindowInsetsCompat {
val imeHeight = insets.getInsets(
WindowInsetsCompat.Type.ime()
).bottom
val navBarHeight = insets.getInsets(
WindowInsetsCompat.Type.navigationBars()
).bottom
coordinator?.translationY =
-(imeHeight - navBarHeight).toFloat().coerceAtLeast(0f)
return insets
}
},
)
}
}
The key detail here: we subtract navBarHeight from imeHeight. Without this, the bottom sheet would over-shoot by the height of the navigation bar, since the keyboard inset already includes it.
3. Add Compose Inset Modifiers
Finally, in your Compose content, add navigationBarsPadding() and imePadding():
ComposeView(requireContext()).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
YourTheme {
YourScreen(
modifier = Modifier
.navigationBarsPadding()
.imePadding(),
// ...
)
}
}
}
Order matters. navigationBarsPadding() goes first so the navigation bar inset is consumed before imePadding() calculates the remaining IME space.
Common Pitfalls
“I only used imePadding() and the sheet doesn't move." — That's expected. imePadding() works inside the Compose tree. It can't move the BottomSheetDialog's CoordinatorLayout. You need the WindowInsetsAnimationCompat.Callback.
“I only used translationY and the content overlaps." — The translationY approach moves the whole container but doesn't adjust the internal layout. imePadding() handles the Compose side.
“The sheet jumps when the keyboard appears.” — You probably still have SOFT_INPUT_ADJUST_RESIZE or the default mode. Make sure you set SOFT_INPUT_ADJUST_NOTHING.
“There’s a gap between the keyboard and bottom sheet.” — Check that you’re subtracting navBarHeight from imeHeight in the translationY calculation. The navigation bar is already accounted for in the IME inset.
Wrapping Up
The Android keyboard-and-bottom-sheet interaction is one of those things that should be simple but isn’t — especially when mixing Compose with the Material BottomSheetDialogFragment. The key insight is that you need to work at three levels: the window, the dialog container, and the Compose content. Skip any one and something will break.
Hope this saves you the hours I spent on it.
메타데이터
- post_id
- dac75cf1cfbc
- slug
- smooth-keyboard-animation-with-bottomsheetdialog-jetpack-compose-dac75cf1cfbc
- url
- https://medium.com/@zaharinskijvlad/smooth-keyboard-animation-with-bottomsheetdialog-jetpack-compose-dac75cf1cfbc
- canonical_url
- https://medium.com/@zaharinskijvlad/smooth-keyboard-animation-with-bottomsheetdialog-jetpack-compose-dac75cf1cfbc
- author_url
- https://medium.com/@zaharinskijvlad
- status
- ok
- fetched_at
- 2026-09-17 10:54:01