mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Close connection on keyboard close (flutter/engine#41500)
Fix: - https://github.com/flutter/flutter/issues/123523 - https://github.com/flutter/flutter/issues/124890 ### Before this patch: https://user-images.githubusercontent.com/30322203/228413196-29c57bb0-3220-495b-9e73-f58777de440f.mp4 ### After this patch: https://user-images.githubusercontent.com/30322203/228413249-fc06f49d-6579-4476-9788-90f12a53b8c3.mp4
This commit is contained in:
parent
24a866fe8e
commit
aa95b83a0d
@ -367,6 +367,14 @@ public class TextInputChannel {
|
||||
"TextInputClient.performPrivateCommand", Arrays.asList(inputClientId, json));
|
||||
}
|
||||
|
||||
/** Instructs Flutter to execute a "onConnectionClosed" action. */
|
||||
public void onConnectionClosed(int inputClientId) {
|
||||
Log.v(TAG, "Sending 'onConnectionClosed' message.");
|
||||
channel.invokeMethod(
|
||||
"TextInputClient.onConnectionClosed",
|
||||
Arrays.asList(inputClientId, "TextInputClient.onConnectionClosed"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link TextInputMethodHandler} which receives all events and requests that are parsed
|
||||
* from the underlying platform channel.
|
||||
|
||||
@ -14,6 +14,8 @@ import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import java.util.List;
|
||||
|
||||
// Loosely based off of
|
||||
@ -41,7 +43,9 @@ import java.util.List;
|
||||
// a no-op. When onEnd indicates the end of the animation, the deferred call is
|
||||
// dispatched again, this time avoiding any flicker since the animation is now
|
||||
// complete.
|
||||
@VisibleForTesting
|
||||
|
||||
// This class should have "package private" visibility cause it's called from TextInputPlugin.
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
@TargetApi(30)
|
||||
@RequiresApi(30)
|
||||
@SuppressLint({"NewApi", "Override"})
|
||||
@ -54,6 +58,7 @@ class ImeSyncDeferringInsetsCallback {
|
||||
private WindowInsets lastWindowInsets;
|
||||
private AnimationCallback animationCallback;
|
||||
private InsetsListener insetsListener;
|
||||
private ImeVisibleListener imeVisibleListener;
|
||||
|
||||
// True when an animation that matches deferredInsetTypes is active.
|
||||
//
|
||||
@ -88,6 +93,11 @@ class ImeSyncDeferringInsetsCallback {
|
||||
view.setOnApplyWindowInsetsListener(null);
|
||||
}
|
||||
|
||||
// Set a listener to be notified when the IME visibility changes.
|
||||
void setImeVisibleListener(ImeVisibleListener imeVisibleListener) {
|
||||
this.imeVisibleListener = imeVisibleListener;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
View.OnApplyWindowInsetsListener getInsetsListener() {
|
||||
return insetsListener;
|
||||
@ -98,6 +108,11 @@ class ImeSyncDeferringInsetsCallback {
|
||||
return animationCallback;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ImeVisibleListener getImeVisibleListener() {
|
||||
return imeVisibleListener;
|
||||
}
|
||||
|
||||
// WindowInsetsAnimation.Callback was introduced in API level 30. The callback
|
||||
// subclass is separated into an inner class in order to avoid warnings from
|
||||
// the Android class loader on older platforms.
|
||||
@ -115,6 +130,20 @@ class ImeSyncDeferringInsetsCallback {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsetsAnimation.Bounds onStart(
|
||||
@NonNull WindowInsetsAnimation animation, @NonNull WindowInsetsAnimation.Bounds bounds) {
|
||||
// Observe changes to software keyboard visibility and notify listener when animation start.
|
||||
// See https://developer.android.com/develop/ui/views/layout/sw-keyboard.
|
||||
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view);
|
||||
if (insets != null && imeVisibleListener != null) {
|
||||
boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
|
||||
imeVisibleListener.onImeVisibleChanged(imeVisible);
|
||||
}
|
||||
return super.onStart(animation, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets onProgress(
|
||||
WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
|
||||
@ -199,4 +228,9 @@ class ImeSyncDeferringInsetsCallback {
|
||||
return view.onApplyWindowInsets(windowInsets);
|
||||
}
|
||||
}
|
||||
|
||||
// Listener for IME visibility changes.
|
||||
public interface ImeVisibleListener {
|
||||
void onImeVisibleChanged(boolean visible);
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,6 +94,17 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
WindowInsets.Type.ime() // Deferred, insets that will animate
|
||||
);
|
||||
imeSyncCallback.install();
|
||||
|
||||
// When the IME is hidden, we need to notify the framework that close connection.
|
||||
imeSyncCallback.setImeVisibleListener(
|
||||
new ImeSyncDeferringInsetsCallback.ImeVisibleListener() {
|
||||
@Override
|
||||
public void onImeVisibleChanged(boolean visible) {
|
||||
if (!visible) {
|
||||
onConnectionClosed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.textInputChannel = textInputChannel;
|
||||
@ -838,4 +849,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
textInputChannel.updateEditingStateWithTag(inputTarget.id, editingValues);
|
||||
}
|
||||
// -------- End: Autofill -------
|
||||
|
||||
public void onConnectionClosed() {
|
||||
textInputChannel.onConnectionClosed(inputTarget.id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2118,6 +2118,19 @@ public class TextInputPluginTest {
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().viewInsetTop);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TargetApi(30)
|
||||
@Config(sdk = 30)
|
||||
public void onConnectionClosed_imeInvisible() {
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||
imeSyncCallback.getImeVisibleListener().onImeVisibleChanged(false);
|
||||
verify(textInputChannel, times(1)).onConnectionClosed(anyInt());
|
||||
}
|
||||
|
||||
interface EventHandler {
|
||||
void sendAppPrivateCommand(View view, String action, Bundle data);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user