|
1 | 1 | package com.reactnativekeyboardcontroller
|
2 | 2 |
|
| 3 | +import android.animation.ValueAnimator |
3 | 4 | import android.content.Context
|
| 5 | +import android.os.Build |
4 | 6 | import android.util.Log
|
5 | 7 | import android.view.View
|
| 8 | +import androidx.core.animation.doOnEnd |
6 | 9 | import androidx.core.graphics.Insets
|
7 | 10 | import androidx.core.view.OnApplyWindowInsetsListener
|
8 | 11 | import androidx.core.view.ViewCompat
|
@@ -45,42 +48,40 @@ class KeyboardAnimationCallback(
|
45 | 48 | }
|
46 | 49 |
|
47 | 50 | /**
|
48 |
| - * This method is called everytime when keyboard appears or hides (*) |
49 |
| - * and the call happens before `onStart` (in `onStart` we update `this.isKeyboardVisible` field) |
50 |
| - * |
51 |
| - * *in fact it's getting called much more times, but here for the simplicity we are talking only about keyboard |
| 51 | + * When keyboard changes its size we have different behavior per APIs. |
| 52 | + * On 21<=API<30 - WindowInsetsAnimationCompat dispatches onStart/onProgress/onEnd events. |
| 53 | + * On API>=30 - WindowInsetsAnimationCompat doesn't dispatch anything. As a result behavior |
| 54 | + * between different Android versions is not consistent. On old Android versions we have a |
| 55 | + * reaction, on newer versions - not. In my understanding it's a bug in core library and the |
| 56 | + * behavior should be consistent across all versions of platform. To level the difference we |
| 57 | + * have to implement `onApplyWindowInsets` listener and simulate onStart/onProgress/onEnd |
| 58 | + * events when keyboard changes its size. |
| 59 | + * In the method below we fully recreate the logic that is implemented on old android versions: |
| 60 | + * - we dispatch `keyboardWillShow` (onStart); |
| 61 | + * - we dispatch change height/progress as animated values (onProgress); |
| 62 | + * - we dispatch `keyboardDidShow` (onEnd). |
52 | 63 | */
|
53 |
| - override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat): WindowInsetsCompat { |
| 64 | + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { |
54 | 65 | // when keyboard appears values will be (false && true)
|
55 | 66 | // when keyboard disappears values will be (true && false)
|
56 | 67 | // having such check allows us not to dispatch unnecessary incorrect events
|
57 | 68 | // the condition will be executed only when keyboard is opened and changes its size
|
58 | 69 | // (for example it happens when user changes keyboard type from 'text' to 'emoji' input
|
59 |
| - if (isKeyboardVisible && isKeyboardVisible()) { |
| 70 | + if (isKeyboardVisible && isKeyboardVisible() && Build.VERSION.SDK_INT >= 30) { |
60 | 71 | val keyboardHeight = getCurrentKeyboardHeight()
|
61 |
| - /** |
62 |
| - * By default it's up to OS whether to animate keyboard changes or not. |
63 |
| - * For example my Xiaomi Redmi Note 5 Pro (Android 9) applies layout animation |
64 |
| - * whereas Pixel 3 (Android 12) is not applying layout animation and view changes |
65 |
| - * its position instantly. We stick to the default behavior and rely on it. |
66 |
| - * Though if we decide to animate always (any animation looks better than instant transition) |
67 |
| - * we can use the code below: |
68 |
| - * |
69 |
| - * <pre> |
70 |
| - * val animation = ValueAnimator.ofInt(-this.persistentKeyboardHeight, -keyboardHeight) |
71 |
| - * |
72 |
| - * animation.addUpdateListener { animator -> |
73 |
| - * val toValue = animator.animatedValue as Int |
74 |
| - * this.sendEventToJS(KeyboardTransitionEvent(view.id, toValue, 1.0)) |
75 |
| - * } |
76 |
| - * animation.setDuration(250).startDelay = 0 |
77 |
| - * animation.start() |
78 |
| - * </pre> |
79 |
| - * |
80 |
| - * But for now let's rely on OS preferences. |
81 |
| - */ |
82 |
| - this.sendEventToJS(KeyboardTransitionEvent(view.id, -keyboardHeight, 1.0)) |
83 |
| - this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight)) |
| 72 | + |
| 73 | + this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight)) |
| 74 | + |
| 75 | + val animation = ValueAnimator.ofInt(-this.persistentKeyboardHeight, -keyboardHeight) |
| 76 | + animation.addUpdateListener { animator -> |
| 77 | + val toValue = animator.animatedValue as Int |
| 78 | + this.sendEventToJS(KeyboardTransitionEvent(view.id, toValue, -toValue.toDouble() / keyboardHeight)) |
| 79 | + } |
| 80 | + animation.doOnEnd { |
| 81 | + this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight)) |
| 82 | + } |
| 83 | + animation.setDuration(250).startDelay = 0 |
| 84 | + animation.start() |
84 | 85 |
|
85 | 86 | this.persistentKeyboardHeight = keyboardHeight
|
86 | 87 | }
|
@@ -132,7 +133,7 @@ class KeyboardAnimationCallback(
|
132 | 133 | } catch (e: ArithmeticException) {
|
133 | 134 | // do nothing, send progress as 0
|
134 | 135 | }
|
135 |
| - Log.i(TAG, "DiffY: $diffY $height") |
| 136 | + Log.i(TAG, "DiffY: $diffY $height $progress") |
136 | 137 |
|
137 | 138 | this.sendEventToJS(KeyboardTransitionEvent(view.id, height, progress))
|
138 | 139 |
|
|
0 commit comments