Remove extraneous window inset call on IME animation (#21213)
diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
index c11d264..62abf76 100644
--- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
+++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
@@ -199,7 +199,17 @@
private View view;
private WindowInsets lastWindowInsets;
- private boolean started = false;
+ // True when an animation that matches deferredInsetTypes is active.
+ //
+ // While this is active, this class will capture the initial window inset
+ // sent into lastWindowInsets by flagging needsSave to true, and will hold
+ // onto the intitial inset until the animation is completed, when it will
+ // re-dispatch the inset change.
+ private boolean animating = false;
+ // When an animation begins, android sends a WindowInset with the final
+ // state of the animation. When needsSave is true, we know to capture this
+ // initial WindowInset.
+ private boolean needsSave = false;
ImeSyncDeferringInsetsCallback(
@NonNull View view, int overlayInsetTypes, int deferredInsetTypes) {
@@ -212,34 +222,38 @@
@Override
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
this.view = view;
- if (started) {
+ if (needsSave) {
+ // Store the view and insets for us in onEnd() below. This captured inset
+ // is not part of the animation and instead, represents the final state
+ // of the inset after the animation is completed. Thus, we defer the processing
+ // of this WindowInset until the animation completes.
+ lastWindowInsets = windowInsets;
+ needsSave = false;
+ }
+ if (animating) {
// While animation is running, we consume the insets to prevent disrupting
// the animation, which skips this implementation and calls the view's
// onApplyWindowInsets directly to avoid being consumed here.
return WindowInsets.CONSUMED;
}
- // Store the view and insets for us in onEnd() below
- lastWindowInsets = windowInsets;
-
// If no animation is happening, pass the insets on to the view's own
// inset handling.
return view.onApplyWindowInsets(windowInsets);
}
@Override
- public WindowInsetsAnimation.Bounds onStart(
- WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
+ public void onPrepare(WindowInsetsAnimation animation) {
if ((animation.getTypeMask() & deferredInsetTypes) != 0) {
- started = true;
+ animating = true;
+ needsSave = true;
}
- return bounds;
}
@Override
public WindowInsets onProgress(
WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
- if (!started) {
+ if (!animating || needsSave) {
return insets;
}
boolean matching = false;
@@ -280,10 +294,10 @@
@Override
public void onEnd(WindowInsetsAnimation animation) {
- if (started && (animation.getTypeMask() & deferredInsetTypes) != 0) {
+ if (animating && (animation.getTypeMask() & deferredInsetTypes) != 0) {
// If we deferred the IME insets and an IME animation has finished, we need to reset
// the flags
- started = false;
+ animating = false;
// And finally dispatch the deferred insets to the view now.
// Ideally we would just call view.requestApplyInsets() and let the normal dispatch
diff --git a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java
index 562f1f5..b038eb5 100644
--- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java
+++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java
@@ -669,6 +669,8 @@
WindowInsets.Builder builder = new WindowInsets.Builder();
WindowInsets noneInsets = builder.build();
+ // imeInsets0, 1, and 2 contain unique IME bottom insets, and are used
+ // to distinguish which insets were sent at each stage.
builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 100));
builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(10, 10, 10, 40));
WindowInsets imeInsets0 = builder.build();
@@ -677,6 +679,10 @@
builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(10, 10, 10, 40));
WindowInsets imeInsets1 = builder.build();
+ builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 50));
+ builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(10, 10, 10, 40));
+ WindowInsets imeInsets2 = builder.build();
+
builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 200));
builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(10, 10, 10, 0));
WindowInsets deferredInsets = builder.build();
@@ -696,6 +702,8 @@
imeSyncCallback.onPrepare(animation);
imeSyncCallback.onApplyWindowInsets(testView, deferredInsets);
imeSyncCallback.onStart(animation, null);
+ // Only the final state call is saved, extra calls are passed on.
+ imeSyncCallback.onApplyWindowInsets(testView, imeInsets2);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
// No change, as deferredInset is stored to be passed in onEnd()
@@ -723,7 +731,7 @@
imeSyncCallback.onEnd(animation);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
- // Values should be of deferredInsets
+ // Values should be of deferredInsets, not imeInsets2
assertEquals(0, viewportMetricsCaptor.getValue().paddingBottom);
assertEquals(10, viewportMetricsCaptor.getValue().paddingTop);
assertEquals(200, viewportMetricsCaptor.getValue().viewInsetBottom);