[Native] Snap Touchable state on sub-frame animations#4181
Conversation
There was a problem hiding this comment.
Pull request overview
Adjusts native Touchable press animations to avoid “stale start value” glitches when an animation’s duration is shorter than a display frame, ensuring state updates land immediately instead of being deferred to the next frame.
Changes:
- iOS/macOS: snap opacity/scale immediately for sub-frame durations and only read from the presentation layer when an animation is actually in flight.
- Android: snap opacity/scale/underlay immediately for sub-frame durations to avoid
ObjectAnimatordeferring property writes. - Android: add a
Display.minimumFrameTimehelper (and switch the press animation interpolator).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm | Adds per-screen frame-duration threshold and snaps Touchable state updates for sub-frame animations; presentation-layer reads only when animating. |
| packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt | Snaps updates for sub-frame durations to avoid deferred ObjectAnimator writes; changes interpolator. |
| packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt | Introduces Display.minimumFrameTime utility used to determine sub-frame thresholds. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
m-bert
left a comment
There was a problem hiding this comment.
Sadly, I can't see difference on Android - on emulator it is still like in "before" video and on my physical device it works fine in both cases 😞
| if (@available(macOS 12.0, *)) { | ||
| maxFps = screen.maximumFramesPerSecond; | ||
| } |
There was a problem hiding this comment.
12.0 seems pretty old, can't we assume that it's true?
There was a problem hiding this comment.
We have 10.15 in podspec as minimum. I think we can change that, but outside of the scope of this PR.
Description
Updates how
Touchablehandles animations:ObjectAnimatorwould run the animation on the next frame, and if a new animation was started immediately after (like a quick tap in a scroll view), it would read the "resting" state instead of the one just applied.LinearOutSlowInInterpolatortoFastOutSlowInInterpolatorto better match the animation curve from web and iOSTest plan
Screen.Recording.2026-05-19.at.08.22.53.mov
Screen.Recording.2026-05-19.at.08.20.50.mov
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-05-19.at.08.22.14.mov
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-05-19.at.08.21.09.mov
(Yes, iOS before is being pressed on constantly, it's just animating default -> default)