Skip to content

Commit 3379c7f

Browse files
authored
Keyboard Animation Fix (flutter#176418)
Fixing Keyboard Animation Issue Before: https://github.com/user-attachments/assets/2b863ca1-20e3-47e2-94f1-5cb2083c1e28 After: https://github.com/user-attachments/assets/914efc84-13aa-4bde-8c44-d1761f1fe79a Fixes: flutter#168768 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing.
1 parent 7100df2 commit 3379c7f

File tree

2 files changed

+105
-7
lines changed

2 files changed

+105
-7
lines changed

engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import android.annotation.SuppressLint;
1010
import android.graphics.Insets;
11+
import android.os.Build;
1112
import android.view.View;
1213
import android.view.WindowInsets;
1314
import android.view.WindowInsetsAnimation;
@@ -146,15 +147,17 @@ public WindowInsets onProgress(
146147
return insets;
147148
}
148149

149-
// The IME insets include the height of the navigation bar. If the app isn't laid out behind
150-
// the navigation bar, this causes the IME insets to be too large during the animation.
151-
// To fix this, we subtract the navigationBars bottom inset if the system UI flags for laying
152-
// out behind the navigation bar aren't present.
150+
// Pre 15, the IME insets include the height of the navigation bar. If the app
151+
// isn't laid out behind the navigation bar, this causes the IME insets to be too large during
152+
// the animation. To fix this, we subtract the navigationBars bottom inset if the system UI
153+
// flags for laying out behind the navigation bar aren't present.
153154
int excludedInsets = 0;
154155
int systemUiFlags = view.getWindowSystemUiVisibility();
155-
if ((systemUiFlags & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
156-
&& (systemUiFlags & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
157-
excludedInsets = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
156+
if (Build.VERSION.SDK_INT < API_LEVELS.API_35) {
157+
if ((systemUiFlags & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
158+
&& (systemUiFlags & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
159+
excludedInsets = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
160+
}
158161
}
159162

160163
WindowInsets.Builder builder = new WindowInsets.Builder(lastWindowInsets);

engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2707,6 +2707,101 @@ public void ime_windowInsetsSync_notLaidOutBehindNavigation_excludesNavigationBa
27072707
}
27082708
}
27092709

2710+
@Test
2711+
@TargetApi(API_LEVELS.API_35)
2712+
@Config(minSdk = API_LEVELS.API_35)
2713+
@SuppressWarnings("deprecation")
2714+
// getWindowSystemUiVisibility, SYSTEM_UI_FLAG_LAYOUT_STABLE.
2715+
// flutter#133074 tracks migration work.
2716+
public void ime_windowInsetsSync_notLaidOutBehindNavigation_post15_includesNavigationBars() {
2717+
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
2718+
scenario.onActivity(
2719+
activity -> {
2720+
FlutterView testView = spy(new FlutterView(activity));
2721+
when(testView.getWindowSystemUiVisibility())
2722+
.thenReturn(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
2723+
2724+
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
2725+
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
2726+
TextInputPlugin textInputPlugin =
2727+
new TextInputPlugin(
2728+
testView,
2729+
textInputChannel,
2730+
scribeChannel,
2731+
mock(PlatformViewsController.class),
2732+
mock(PlatformViewsController2.class));
2733+
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
2734+
FlutterEngine flutterEngine =
2735+
spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
2736+
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
2737+
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
2738+
testView.attachToFlutterEngine(flutterEngine);
2739+
2740+
WindowInsetsAnimation animation = mock(WindowInsetsAnimation.class);
2741+
when(animation.getTypeMask()).thenReturn(WindowInsets.Type.ime());
2742+
2743+
List<WindowInsetsAnimation> animationList = new ArrayList();
2744+
animationList.add(animation);
2745+
2746+
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
2747+
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
2748+
2749+
WindowInsets.Builder builder = new WindowInsets.Builder();
2750+
2751+
// Set the initial insets and verify that they were set and the bottom view inset is
2752+
// correct
2753+
imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2754+
2755+
verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2756+
assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2757+
2758+
// Call onPrepare and set the lastWindowInsets - these should be stored for the end of
2759+
// the
2760+
// animation instead of being applied immediately
2761+
imeSyncCallback.getAnimationCallback().onPrepare(animation);
2762+
builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 100));
2763+
builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 0));
2764+
imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2765+
2766+
verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2767+
assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2768+
2769+
// Call onStart and apply new insets - these should be ignored completely
2770+
imeSyncCallback.getAnimationCallback().onStart(animation, null);
2771+
builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 50));
2772+
builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2773+
imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2774+
2775+
verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2776+
assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2777+
2778+
// Progress the animation and ensure that the navigation bar insets have not been
2779+
// subtracted from the IME insets
2780+
builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 25));
2781+
builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2782+
imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2783+
2784+
verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2785+
assertEquals(25, viewportMetricsCaptor.getValue().viewInsetBottom);
2786+
2787+
builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 50));
2788+
builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2789+
imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2790+
2791+
verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2792+
assertEquals(50, viewportMetricsCaptor.getValue().viewInsetBottom);
2793+
2794+
// End the animation and ensure that the bottom insets match the lastWindowInsets that
2795+
// we set
2796+
// during onPrepare
2797+
imeSyncCallback.getAnimationCallback().onEnd(animation);
2798+
2799+
verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2800+
assertEquals(100, viewportMetricsCaptor.getValue().viewInsetBottom);
2801+
});
2802+
}
2803+
}
2804+
27102805
@Test
27112806
@TargetApi(API_LEVELS.API_30)
27122807
@Config(sdk = API_LEVELS.API_30)

0 commit comments

Comments
 (0)