From 81ba0331f55694959d3f5824f6a7cfa78f8c047b Mon Sep 17 00:00:00 2001 From: justin <noborder@qq.com> Date: Tue, 26 May 2020 17:15:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=8AXTextinputplugin=E6=94=B9=E6=88=90?= =?UTF-8?q?=E5=8D=95=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idlefish/flutterboost/XFlutterView.java | 55 ++---------- .../flutterboost/XInputConnectionAdaptor.java | 2 +- .../flutterboost/XTextInputPlugin.java | 88 ++++++++++++++----- example/lib/simple_page_widgets.dart | 38 +++++--- 4 files changed, 100 insertions(+), 83 deletions(-) diff --git a/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java b/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java index 6bf4465..79e3f50 100644 --- a/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java +++ b/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java @@ -43,27 +43,7 @@ import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.view.AccessibilityBridge; -/** - * Displays a Flutter UI on an Android device. - * <p> - * A {@code FlutterView}'s UI is painted by a corresponding {@link FlutterEngine}. - * <p> - * A {@code FlutterView} can operate in 2 different {@link RenderMode}s: - * <ol> - * <li>{@link RenderMode#surface}, which paints a Flutter UI to a {@link android.view.SurfaceView}. - * This mode has the best performance, but a {@code FlutterView} in this mode cannot be positioned - * between 2 other Android {@code View}s in the z-index, nor can it be animated/transformed. - * Unless the special capabilities of a {@link android.graphics.SurfaceTexture} are required, - * developers should strongly prefer this render mode.</li> - * <li>{@link RenderMode#texture}, which paints a Flutter UI to a {@link android.graphics.SurfaceTexture}. - * This mode is not as performant as {@link RenderMode#surface}, but a {@code FlutterView} in this - * mode can be animated and transformed, as well as positioned in the z-index between 2+ other - * Android {@code Views}. Unless the special capabilities of a {@link android.graphics.SurfaceTexture} - * are required, developers should strongly prefer the {@link RenderMode#surface} render mode.</li> - * </ol> - * See <a>https://source.android.com/devices/graphics/arch-tv#surface_or_texture</a> for more - * information comparing {@link android.view.SurfaceView} and {@link android.view.TextureView}. - */ + public class XFlutterView extends FrameLayout { private static final String TAG = "FlutterView"; @@ -150,26 +130,12 @@ public class XFlutterView extends FrameLayout { this(context, null, null, null); } - /** - * Constructs a {@code FlutterView} programmatically, without any XML attributes, - * and allows selection of a {@link #renderMode}. - * <p> - * {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}. - * <p> - * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} - * to be compatible with {@link PlatformViewsController}. - */ + public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) { this(context, null, renderMode, null); } - /** - * Constructs a {@code FlutterView} programmatically, without any XML attributes, - * assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}. - * <p> - * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} - * to be compatible with {@link PlatformViewsController}. - */ + public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) { this(context, null, FlutterView.RenderMode.surface, transparencyMode); } @@ -609,16 +575,9 @@ public class XFlutterView extends FrameLayout { this.flutterEngine.getPlatformViewsController().attachToView(this); - - if(textInputPlugin==null){ - textInputPlugin = new XTextInputPlugin( - this, - flutterEngine.getTextInputChannel(), - this.flutterEngine.getPlatformViewsController() - ); - } - - textInputPlugin.setTextInputMethodHandler(); + textInputPlugin= XTextInputPlugin.getTextInputPlugin( this.flutterEngine.getDartExecutor(), + this.flutterEngine.getPlatformViewsController()); + textInputPlugin.updateView(this); textInputPlugin.getInputMethodManager().restartInput(this); @@ -717,7 +676,7 @@ public class XFlutterView extends FrameLayout { } public void release(){ if(textInputPlugin!=null){ - textInputPlugin.release(); +// textInputPlugin.release(); } } diff --git a/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java b/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java index 3500c47..68f0f55 100644 --- a/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java +++ b/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java @@ -31,6 +31,7 @@ import io.flutter.plugin.common.ErrorLogResult; import io.flutter.plugin.common.MethodChannel; class XInputConnectionAdaptor extends BaseInputConnection { + private final View mFlutterView; private final int mClient; private final TextInputChannel textInputChannel; @@ -221,7 +222,6 @@ class XInputConnectionAdaptor extends BaseInputConnection { mEditable.delete(selStart, selEnd); mEditable.insert(selStart, String.valueOf((char) character)); setSelection(selStart + 1, selStart + 1); - updateEditingState(); } return true; } diff --git a/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java b/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java index 37c9795..a55f454 100644 --- a/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java +++ b/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java @@ -1,8 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. package com.idlefish.flutterboost; + +import android.annotation.SuppressLint; import android.content.Context; +import android.os.Build; +import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.text.Editable; import android.text.InputType; import android.text.Selection; @@ -11,6 +19,7 @@ import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.systemchannels.TextInputChannel; @@ -21,11 +30,11 @@ import io.flutter.plugin.platform.PlatformViewsController; */ public class XTextInputPlugin { @NonNull - private View mView; + private View mView; @NonNull - private final InputMethodManager mImm; + private InputMethodManager mImm; @NonNull - private final TextInputChannel textInputChannel; + private TextInputChannel textInputChannel; @NonNull private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); @Nullable @@ -37,26 +46,34 @@ public class XTextInputPlugin { private InputConnection lastInputConnection; @NonNull private PlatformViewsController platformViewsController; + private boolean restartAlwaysRequired; // When true following calls to createInputConnection will return the cached lastInputConnection if the input // target is a platform view. See the comments on lockPlatformViewInputConnection for more details. private boolean isInputConnectionLocked; + private static XTextInputPlugin xTextInputPlugin; + + public static XTextInputPlugin getTextInputPlugin( DartExecutor dartExecutor, @NonNull PlatformViewsController platformViewsController){ + if(xTextInputPlugin!=null) return xTextInputPlugin; + xTextInputPlugin =new XTextInputPlugin(dartExecutor,platformViewsController); + return xTextInputPlugin; + } + + public XTextInputPlugin(@NonNull DartExecutor dartExecutor, @NonNull PlatformViewsController platformViewsController) { - public XTextInputPlugin(View view, @NonNull TextInputChannel textInputChannel, @NonNull PlatformViewsController platformViewsController) { - mView = view; - mImm = (InputMethodManager) view.getContext().getSystemService( - Context.INPUT_METHOD_SERVICE); - this.textInputChannel = textInputChannel; + + textInputChannel = new TextInputChannel(dartExecutor); + + + textInputChannel.requestExistingInputState(); this.platformViewsController = platformViewsController; // this.platformViewsController.attachTextInputPlugin(this); } - - public void release() { - mView = null; - } - - public void setTextInputMethodHandler() { + public void updateView(View view){ + mView = view; + mImm = (InputMethodManager) view.getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() { @Override @@ -89,8 +106,12 @@ public class XTextInputPlugin { clearTextInputClient(); } }); + restartAlwaysRequired = isRestartAlwaysRequired(); + } + + @NonNull public InputMethodManager getInputMethodManager() { return mImm; @@ -114,7 +135,7 @@ public class XTextInputPlugin { /** * Unlocks the input connection. - * <p> + * * See also: @{link lockPlatformViewInputConnection}. */ public void unlockPlatformViewInputConnection() { @@ -123,7 +144,7 @@ public class XTextInputPlugin { /** * Detaches the text input plugin from the platform views controller. - * <p> + * * The TextInputPlugin instance should not be used after calling this. */ public void destroy() { @@ -134,6 +155,7 @@ public class XTextInputPlugin { TextInputChannel.InputType type, boolean obscureText, boolean autocorrect, + boolean enableSuggestions, TextInputChannel.TextCapitalization textCapitalization ) { if (type.type == TextInputChannel.TextInputType.DATETIME) { @@ -168,6 +190,7 @@ public class XTextInputPlugin { textType |= InputType.TYPE_TEXT_VARIATION_PASSWORD; } else { if (autocorrect) textType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; + if (!enableSuggestions) textType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; } if (textCapitalization == TextInputChannel.TextCapitalization.CHARACTERS) { @@ -199,6 +222,7 @@ public class XTextInputPlugin { configuration.inputType, configuration.obscureText, configuration.autocorrect, + configuration.enableSuggestions, configuration.textCapitalization ); outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; @@ -238,7 +262,7 @@ public class XTextInputPlugin { /** * Clears a platform view text input client if it is the current input target. - * <p> + * * This is called when a platform view is disposed to make sure we're not hanging to a stale input * connection. */ @@ -264,7 +288,7 @@ public class XTextInputPlugin { mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0); } - private void setTextInputClient(int client, TextInputChannel.Configuration configuration) { + @VisibleForTesting void setTextInputClient(int client, TextInputChannel.Configuration configuration) { inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); this.configuration = configuration; mEditable = Editable.Factory.getInstance().newEditable(""); @@ -296,8 +320,8 @@ public class XTextInputPlugin { } } - private void setTextInputEditingState(View view, TextInputChannel.TextEditState state) { - if (!mRestartInputPending && state.text.equals(mEditable.toString())) { + @VisibleForTesting void setTextInputEditingState(View view, TextInputChannel.TextEditState state) { + if (!restartAlwaysRequired && !mRestartInputPending && state.text.equals(mEditable.toString())) { applyStateToSelection(state); mImm.updateSelection(mView, Math.max(Selection.getSelectionStart(mEditable), 0), Math.max(Selection.getSelectionEnd(mEditable), 0), @@ -311,6 +335,28 @@ public class XTextInputPlugin { } } + // Samsung's Korean keyboard has a bug where it always attempts to combine characters based on + // its internal state, ignoring if and when the cursor is moved programmatically. The same bug + // also causes non-korean keyboards to occasionally duplicate text when tapping in the middle + // of existing text to edit it. + // + // Fully restarting the IMM works around this because it flushes the keyboard's internal state + // and stops it from trying to incorrectly combine characters. However this also has some + // negative performance implications, so we don't want to apply this workaround in every case. + @SuppressLint("NewApi") // New API guard is inline, the linter can't see it. + @SuppressWarnings("deprecation") + private boolean isRestartAlwaysRequired() { + InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype(); + // Impacted devices all shipped with Android Lollipop or newer. + if (subtype == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || !Build.MANUFACTURER.equals("samsung")) { + return false; + } + String keyboardName = Settings.Secure.getString(mView.getContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + // The Samsung keyboard is called "com.sec.android.inputmethod/.SamsungKeypad" but look + // for "Samsung" just in case Samsung changes the name of the keyboard. + return keyboardName.contains("Samsung"); + } + private void clearTextInputClient() { if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { // Focus changes in the framework tree have no guarantees on the order focus nodes are notified. A node @@ -353,4 +399,4 @@ public class XTextInputPlugin { // For platform views this is the platform view's ID. int id; } -} \ No newline at end of file +} diff --git a/example/lib/simple_page_widgets.dart b/example/lib/simple_page_widgets.dart index 5249dd6..407ca82 100644 --- a/example/lib/simple_page_widgets.dart +++ b/example/lib/simple_page_widgets.dart @@ -350,20 +350,32 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { alignment: AlignmentDirectional.center, ), // Expanded(child: Container()), - const CupertinoTextField( - prefix: Icon( - CupertinoIcons.person_solid, - color: CupertinoColors.lightBackgroundGray, - size: 28.0, - ), - padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), - clearButtonMode: OverlayVisibilityMode.editing, - textCapitalization: TextCapitalization.words, + const CupertinoTextField( + prefix: Icon( + CupertinoIcons.person_solid, + color: CupertinoColors.lightBackgroundGray, + size: 28.0, + ), + padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), + clearButtonMode: OverlayVisibilityMode.editing, + textCapitalization: TextCapitalization.words, + ), + new TextField( + enabled: true, + autocorrect: true, + style: const TextStyle( + fontSize: 20.0, + color: const Color(0xFF222222), + fontWeight: FontWeight.w500), + ), new TextField( + controller: new TextEditingController(), + focusNode:FocusNode(), + enabled: true, autocorrect: false, - decoration: BoxDecoration( - border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)), - ), - placeholder: 'Name', + style: const TextStyle( + fontSize: 20.0, + color: const Color(0xFF222222), + fontWeight: FontWeight.w500), ), InkWell( child: Container( -- 2.26.2