Skip to content

Commit ac58fdd

Browse files
authored
fix(Android, FormSheet): Prioritize keyboard animation over content resize animation (#3924)
## Description This PR fixes a snap back that occurs in FormSheet for the following combination: - fitToContents - TextInput - SafeAreaView - sheetResizeAnimationEnabled: true There was a race condition between the keyboard insets animation (moving the FormSheet above the keyboard) and the sheet's content resize animation (caused by ignoring the bottom inset when the IME is present, because the FormSheet will be moved above the navigation bar). When the keyboard slides in, RN will recalculate the layout (sheet's height changed, because of the ignored bottom inset). This layout change triggers `updateSheetContentHeightWithAnimation` in the SheetAnimationCoordinator, and two animations try to update properties at the same time. I think that the simplest solution is to let the keyboard animation set the sheet position without interference from the content resize animator by the time the keyboard slides in. Closes: software-mansion/react-native-screens-labs#1170 ## Changes - Introduced an `isKeyboardAnimationInProgress` flag. - Added `onPrepare` and `onEnd` overrides to notify the keyboard animation state. - Updated `updateSheetContentHeightWithAnimation` - if a keyboard animation is in progress, it now silently updates the bottom sheet metrics and layout without firing a competing ValueAnimator. ## Before & after - visual documentation | Before | After | | --- | --- | | <video src="https://github.com/user-attachments/assets/1adfe76c-ff68-4fd8-b97b-13da8488d4ff" /> | <video src="https://github.com/user-attachments/assets/c7c1b77b-085a-4d78-a68a-458def1ece6e" /> | ## Test plan Test3336 - FormSheetWithFitToContentsWithTextInput case ## Checklist - [ ] Included code example that can be used to test this change. - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [ ] For API changes, updated relevant public types. - [ ] Ensured that CI passes
1 parent 49bbc28 commit ac58fdd

3 files changed

Lines changed: 31 additions & 3 deletions

File tree

android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,14 @@ class ScreenStackFragment :
276276
object : WindowInsetsAnimationCompat.Callback(
277277
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
278278
) {
279+
override fun onPrepare(animation: WindowInsetsAnimationCompat) {
280+
super.onPrepare(animation)
281+
282+
if ((animation.typeMask and WindowInsetsCompat.Type.ime()) != 0) {
283+
sheetDelegate.notifyKeyboardAnimationStart()
284+
}
285+
}
286+
279287
// Replace InsetsAnimationCallback created by BottomSheetBehavior
280288
// to avoid interfering with custom animations.
281289
// See: https://github.com/software-mansion/react-native-screens/pull/2909
@@ -294,6 +302,10 @@ class ScreenStackFragment :
294302
override fun onEnd(animation: WindowInsetsAnimationCompat) {
295303
super.onEnd(animation)
296304

305+
if ((animation.typeMask and WindowInsetsCompat.Type.ime()) != 0) {
306+
sheetDelegate.notifyKeyboardAnimationEnd()
307+
}
308+
297309
screen.onSheetYTranslationChanged()
298310
}
299311
}

android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetAnimationCoordinator.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ internal class SheetAnimationCoordinator(
2323
checkNotNull(screenRef.get()) {
2424
"[RNScreens] Screen has been destroyed and shouldn't be the subject of any animations"
2525
}
26+
private var activeKeyboardAnimationsCount: Int = 0
27+
private val isKeyboardAnimationInProgress: Boolean
28+
get() = activeKeyboardAnimationsCount > 0
2629
private var isSheetAnimationInProgress: Boolean = false
2730
private var currentContentAnimator: ValueAnimator? = null
2831

@@ -96,10 +99,11 @@ internal class SheetAnimationCoordinator(
9699
val clampedOldHeight = screen.resolveClampedHeight(oldHeight, currentTranslationY)
97100
val clampedNewHeight = screen.resolveClampedHeight(newHeight, currentTranslationY)
98101

99-
// If isSheetAnimationInProgress is set, the entry/exit animator already owns translationY writes.
100-
// Silently update behavior metrics and re-layout so the ongoing slide animation
102+
// If an entry/exit animation or a keyboard animation is in progress - it owns
103+
// translationY writes. Then when the content size is changing, we silently
104+
// update behavior metrics and re-layout so the ongoing slide animation
101105
// lands at the correct final geometry, without firing a competing animation.
102-
if (isSheetAnimationInProgress) {
106+
if (isSheetAnimationInProgress || isKeyboardAnimationInProgress) {
103107
behavior.updateMetrics(clampedNewHeight)
104108
screen.layoutBottomSheetAtHeight(clampedNewHeight)
105109
screen.finalizeBottomSheetLayoutUpdates()
@@ -207,6 +211,14 @@ internal class SheetAnimationCoordinator(
207211
}
208212
}
209213

214+
internal fun notifyKeyboardAnimationStart() {
215+
activeKeyboardAnimationsCount++
216+
}
217+
218+
internal fun notifyKeyboardAnimationEnd() {
219+
activeKeyboardAnimationsCount = maxOf(0, activeKeyboardAnimationsCount - 1)
220+
}
221+
210222
internal fun handleKeyboardInsetsProgress(insets: WindowInsetsCompat) {
211223
lastKeyboardBottomOffset = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
212224
// Prioritize enter/exit animations over direct keyboard inset reactions.

android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,10 @@ class SheetDelegate(
443443
internal fun handleKeyboardInsetsProgress(insets: WindowInsetsCompat) =
444444
screen.sheetAnimationCoordinator.handleKeyboardInsetsProgress(insets)
445445

446+
internal fun notifyKeyboardAnimationStart() = screen.sheetAnimationCoordinator.notifyKeyboardAnimationStart()
447+
448+
internal fun notifyKeyboardAnimationEnd() = screen.sheetAnimationCoordinator.notifyKeyboardAnimationEnd()
449+
446450
private inner class KeyboardHandler : BottomSheetBehavior.BottomSheetCallback() {
447451
override fun onStateChanged(
448452
bottomSheet: View,

0 commit comments

Comments
 (0)