Removed animated keyboard resize transitions on Android. Now it matches iOS and is also instant.
💡 Motivation and Context
Problem description
First of all I'd like to give a little bit context:
transition prior to Android 11 are simulated (compat library listens to onAppyWindowInsets and dispatches the animation that looks kind of similar to keyboard show/hide);
when keyboard is resized -> compat library still dispatches an animated transition (but in fact keyboard frame is changed instantly);
after Android 11 onStart/onMove/onEnd events are not dispatching if keyboard is resized;
instead we should listen to onApplyWindowInsets and do a corresponding reactions on our own;
Initially for me it looked like a correct option. But devil is in details and this approach brought new issues:
1️⃣ Incorrect progress value when keyboard gets resized
The progress value should be only between 0 (closed) and 1 (opened). If we do an animated keyboard resize, then we need to change progress value accordingly. It would be strange to see progress as 1.3 if keyboard gets bigger, so we always keep it in range of [0, 1].
[!WARNING]
The only one exception is when keyboard gets smaller, in this case for first frame we increased instantly progress to >1 value (let's say 1.2) and then changed a progress to 1 (finish of animation), thus when keyboard is not animated it'll always have a progress as 0 or 1.
it wasn't exported from the package (I knew I'm going to remove it eventually) and it wasn't documented.
So animated transition for resize is really headache 🤯
2️⃣ Strange animation with KeyboardStickyView + switching between different TextInput fields with different input types (i. e. text to numeric)
The undesired animated resize effect comes into play when we have various simultaneous animations. For example using KeyboardToolbar when you press next button and keyboard changes it size from text to numeric (becomes smaller). If everything wrapped in KeyboardAwareScrollView, then we have several animations:
keyboard toolbar is moving because keyboard was resized;
KeyboardAwareScrollView is scrolling because a new input got a focus.
The solution is quite simple - react on keyboard resize instantly (i. e. it shouldn't be an animated transition). In this case we can assure that we match iOS behavior and we reduce API fragmentation across platforms.
So in this PR I reworked code and made resize transition instant one:
first of all I deleted an animated transition from onKeyboardResized, but it helps only for Android 11+;
secondly I re-wrote onStart method for Android < 11 (I detect if keyboard was resized and keep animation reference that should be ignored - in onEnd I reset that value);
it worked, but I discovered, that on Android < 11 we may have a situation, where we dispatch many onStart animations (go to emoji -> start to search them). If we keep only last reference - then other (previously) started animations affect position and transition is very janky:
That's the purpose behind animationsToSkip HashSet.
2️⃣ new onResize handler
Initially I though to add additional onResize handler, but after some research I thought that it could make API of library more complex without explicit benefits. So I decided to use existing methods. However it's a bit challenging, because by default on Android we listen only to onMove events. Theoretically we can add a listener and update a value on onEnd - I did it and on one of my projects it resulted to jumpy behavior.
So for sake of backward compatibility I'm dispatching 3 events on Android (onStart/onMove/onEnd) and two events on iOS (onStart/onEnd).
3️⃣ One frame lag
After implementation I discovered that there is a one frame dealy between actual keyboard size changes and the emitted event. After digging into native code I found that it suffers from the same problem:
1st frame
2nd frame
3rd frame
4th frame
And backward transition:
1st frame
2nd frame
3rd frame
4️⃣ An ability to have an animated transitions
If for some reasons you still prefer to have an animated transition, then you still can achieve it using useKeyboardHandler hook:
📜 Description
Removed animated keyboard resize transitions on Android. Now it matches iOS and is also instant.
💡 Motivation and Context
Problem description
First of all I'd like to give a little bit context:
onAppyWindowInsets
and dispatches the animation that looks kind of similar to keyboard show/hide);onStart
/onMove
/onEnd
events are not dispatching if keyboard is resized;onApplyWindowInsets
and do a corresponding reactions on our own;Initially for me it looked like a correct option. But devil is in details and this approach brought new issues:
1️⃣ Incorrect
progress
value when keyboard gets resizedThe
progress
value should be only between 0 (closed) and 1 (opened). If we do an animated keyboard resize, then we need to changeprogress
value accordingly. It would be strange to seeprogress
as1.3
if keyboard gets bigger, so we always keep it in range of [0, 1].In https://github.com/kirillzyusko/react-native-keyboard-controller/pull/316 I fixed the problem by introducing additional hook, but solution wasn't perfect, because:
So animated transition for resize is really headache 🤯
2️⃣ Strange animation with
KeyboardStickyView
+ switching between different TextInput fields with different input types (i. e. text to numeric)The undesired animated resize effect comes into play when we have various simultaneous animations. For example using
KeyboardToolbar
when you pressnext
button and keyboard changes it size fromtext
tonumeric
(becomes smaller). If everything wrapped inKeyboardAwareScrollView
, then we have several animations:KeyboardAwareScrollView
is scrolling because a new input got a focus.And it looks very strange. You can see it in https://github.com/kirillzyusko/react-native-keyboard-controller/issues/374
Solution
1️⃣ Overview
The solution is quite simple - react on keyboard resize instantly (i. e. it shouldn't be an animated transition). In this case we can assure that we match iOS behavior and we reduce API fragmentation across platforms.
So in this PR I reworked code and made resize transition instant one:
onKeyboardResized
, but it helps only for Android 11+;onStart
method for Android < 11 (I detect if keyboard was resized and keep animation reference that should be ignored - inonEnd
I reset that value);onStart
animations (go to emoji -> start to search them). If we keep only last reference - then other (previously) started animations affect position and transition is very janky:https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/9c311ed7-6196-4aed-957a-bd04e3c73ebf
If we take only latest value - we'll e always one frame behind. It's not perfect, but still better than race conditions:
https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/add753e7-42f9-41bb-a019-3659e07ecfca
That's the purpose behind
animationsToSkip
HashSet.2️⃣ new
onResize
handlerInitially I though to add additional
onResize
handler, but after some research I thought that it could make API of library more complex without explicit benefits. So I decided to use existing methods. However it's a bit challenging, because by default on Android we listen only toonMove
events. Theoretically we can add a listener and update a value ononEnd
- I did it and on one of my projects it resulted to jumpy behavior.So for sake of backward compatibility I'm dispatching 3 events on Android (onStart/onMove/onEnd) and two events on iOS (onStart/onEnd).
3️⃣ One frame lag
After implementation I discovered that there is a one frame dealy between actual keyboard size changes and the emitted event. After digging into native code I found that it suffers from the same problem:
And backward transition:
4️⃣ An ability to have an animated transitions
If for some reasons you still prefer to have an animated transition, then you still can achieve it using
useKeyboardHandler
hook:Closes https://github.com/kirillzyusko/react-native-keyboard-controller/issues/374
📢 Changelog
JS
useKeyboardInterpolation
hook;progress
based interpolation inKeyboardStickyView
/KeyboardAvoidingView
;Android
DEFAULT_ANIMATION_TIME
;HashSet
(animationsToSkip
);animationsToSkip
HashSet;onStart
/onMove
/onEnd
events if keyboard was resized;🤔 How Has This Been Tested?
Tested manually on:
📸 Screenshots (if appropriate):
Keyboard toolbar
Keyboard Avoiding View
📝 Checklist