Flutter TextFiled的哪些坑

3,305 阅读3分钟

在Flutter中输入框是重度交互使用场景,但其中很多坑爹体验问题一直官方都没给出好的解决方案,本文将介绍一些Flutter输入框的坑爹问题;

1. Android未输入字符时光标高度小于输入字符时高度

具体表现行为如下: image.png

image.png

此小细节问题在稀土掘金/B站漫画/青藤之恋等使用Flutter的APP都存在,主要原因使用官方在使用定义iOS/Android两个平台的光标有不同实现机制;核心实现类在Editable.dart中

  void _computeCaretPrototype() {
    assert(defaultTargetPlatform != null);
    switch (defaultTargetPlatform) {
      case TargetPlatform.iOS:
      case TargetPlatform.macOS:
        _caretPrototype = Rect.fromLTWH(0.0, 0.0, cursorWidth, cursorHeight + 2);
        break;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
      case TargetPlatform.linux:
      case TargetPlatform.windows:
        _caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, cursorHeight - 2.0 * _kCaretHeightOffset);
        break;
    }
  }

iOS平台

实验测试iOS为输入字符和输入字符高度变化如下:

image20220106142630048

image20220106142822319

因此光标区域高度

未输入字符时:cursorHeight = 21.0 - 0.0 = 21.0 ;

输入字符时:cursorHeight = 20.0 - (-1.0) = 21.0 ;

高度未发生变化,可以发现其实iOS也存在一个像素偏差,只是肉眼不易发现而言;

Android平台

在Android平台下测试未输入字符高度和输入字符高度变化如下:

image20220106143806996

image20220106143916413

未输入字符时:cursorHeight = 17.0 - 2.0 = 15.0 ;

输入字符时: cursorHeight = 19.0 - 0.3 = 18.7 ;

因此可以明显看出Android平台下输入字符后光标突然变大了一些;

修复方案

可以参考之前提过的PR:github.com/flutter/flu…

核心修复就是将Android平台计算光标高度和iOS保持一致,FIX-ADD 代码如下:


  void paintRegularCursor(Canvas canvas, RenderEditable renderEditable, Color caretColor, TextPosition textPosition) {
    ...

    caretRect = caretRect.shift(renderEditable._paintOffset);


    ///FIX-ADD
    caretRect = Rect.fromLTRB(caretRect.left, 0.0, caretRect.right, renderEditable.cursorHeight + 2.0);


    final Rect integralRect = caretRect.shift(renderEditable._snapToPhysicalPixel(caretRect.topLeft));
    ...
  }

2. 输入框自动读取粘贴板

这个问题在一些Flutter2.x版本存在(在高版本3.0后应该已经修复),特别是在小米手机上因为照明弹很容易引起监管问题突出,核心也是这个TextFiled的基类text_selection.dart中:

github.com/flutter/flu…

github.com/flutter/flu…

  /// Check the [Clipboard] and update [value] if needed.
  Future<void> update() async {
    switch (defaultTargetPlatform) {
      // Android 12 introduces a toast message that appears when an app reads
      // the content on the clipboard. To avoid unintended access, both the
      // Flutter engine and the Flutter framework need to be updated to use the
      // appropriate API to check whether the clipboard is empty.
      // As a short-term workaround, always show the paste button.
      // TODO(justinmc): Expose `hasStrings` in `Clipboard` and use that instead
      // https://github.com/flutter/flutter/issues/74139
      case TargetPlatform.android:
      // Intentionally fall through.
      // iOS 14 added a notification that appears when an app accesses the
      // clipboard. To avoid the notification, don't access the clipboard on iOS,
      // and instead always show the paste button, even when the clipboard is
      // empty.
      // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
      // won't trigger the notification.
      // https://github.com/flutter/flutter/issues/60145
      case TargetPlatform.iOS:
        value = ClipboardStatus.pasteable;
        return;
      case TargetPlatform.fuchsia:
      case TargetPlatform.linux:
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
        break;
    }

    ClipboardData? data;
    try {
      data = await Clipboard.getData(Clipboard.kTextPlain);
    } catch (stacktrace) {
      // In the case of an error from the Clipboard API, set the value to
      // unknown so that it will try to update again later.
      if (_disposed || value == ClipboardStatus.unknown) {
        return;
      }
      value = ClipboardStatus.unknown;
      return;
    }
    ...
  }

这里的在Android/iOS平台下提前return不走下面读取系统粘贴板方法了;

3. TextFiled的最小高度设置

此问题怎么说呢,是个问题也不算什么大问题,主要场景在类似微信输入框自生长高度时候场景会出现 image.png

image.png

在实践这个微信键盘输入生长案例时候会发现TextFiled无法设置最小高度,这个主要由于TextFiled内部计算最小高度大概是48,因此当输入一行的最小高度<48时候,TextFiled是无法满足需求的,此时你可以换CupertinoTextField实现类似效果,不单独展诉此问题~

4. 监听系统键盘弹出收缩状态

目前Flutter在这个API没有提供,一些第三方插件实现思路大多数监听viewInsets.bottom,

具体核心代码如下所示:

MediaQuery.of(context).viewInsets.bottom > 0 ? '键盘弹出''键盘收起'

正常场景下此方法没有任何问题,但是就怕骚操作场景会出现一些异常现象,如果各位有更好的方法麻烦告知下我,谢谢~

5. 焦点控制和输入框顶起

焦点控制类是FocusNode使用,核心代码如下:

      ///获取焦点      
      FocusScope.of(context).requestFocus(focusNode);

      ///释放焦点
      focusNode?.unfocus();

输入框因键盘顶起核心设置 resizeToAvoidBottomPadding

总括

从使用来看,确实坑有点多,目前来看Flutter体验小细节还需要持续打磨打磨,其他它逐步完善吧~