【Flutter】 TextField限制长度时, 第三方手写输入法、ios原始拼音输入法输入被吞问题

问题描述

TextField限制长度时, 当你的输入字符长度已经到了最大值-1时,使用第三方手写输入法或者ios原生拼音输入法输入liang(什么拼音都行,这里只是举例),输到i那么li都会消失。

原因分析

这是因为第三方手写输入法或者ios原生拼音输入法,虽然还没选中哪个汉子,但是输入的拼音字母已经显示在输入框了,那么这个字符串就会算作已经输入了,再计算TextField字符串长度的时候会加上未选中的拼音,跟最大长度作对比,字符数比最大值还要大的时候就会因为TextField的判定机制使未选中的字符消失。

解决方法

TextField除了使用maxLength设置最大长度外,还可以使用inputFormatters限制:

Dart 复制代码
            TextField(
              controller: _controller,
              inputFormatters: [
                LengthLimitingTextInputFormatter(maxLength),
              ],
               ......

源码分析

LengthLimitingTextInputFormatter是flutter原生的类,代码如下:

Dart 复制代码
class LengthLimitingTextInputFormatter extends TextInputFormatter {
  LengthLimitingTextInputFormatter(
    this.maxLength, {
    this.maxLengthEnforcement,
  }) : assert(maxLength == null || maxLength == -1 || maxLength > 0);


  final int? maxLength;  //传入的最大长度
  //确定应如何强制执行 maxLength 限制,默认为 MaxLengthEnforcement.enforced
  final MaxLengthEnforcement? maxLengthEnforcement; 

  //获取默认的maxLengthEnforcement,总共有三种,
  //有兴趣可以看文章最后或者看源码注释研究一下,这个问题的解决方法不需要这个方法
  static MaxLengthEnforcement getDefaultMaxLengthEnforcement([
    TargetPlatform? platform,
  ]) {
    if (kIsWeb) {
      return MaxLengthEnforcement.truncateAfterCompositionEnds;
    } else {
      switch (platform ?? defaultTargetPlatform) {
        case TargetPlatform.android:
        case TargetPlatform.windows:
          return MaxLengthEnforcement.enforced;
        case TargetPlatform.iOS:
        case TargetPlatform.macOS:
        case TargetPlatform.linux:
        case TargetPlatform.fuchsia:
          return MaxLengthEnforcement.truncateAfterCompositionEnds;
      }
    }
  }

 //截断字符串
  @visibleForTesting
  static TextEditingValue truncate(TextEditingValue value, int maxLength) {
    final CharacterRange iterator = CharacterRange(value.text);
    if (value.text.characters.length > maxLength) {
      iterator.expandNext(maxLength);
    }
    final String truncated = iterator.current;

    return TextEditingValue(
      text: truncated,
      selection: value.selection.copyWith(
        baseOffset: math.min(value.selection.start, truncated.length),
        extentOffset: math.min(value.selection.end, truncated.length),
      ),
      composing: !value.composing.isCollapsed && truncated.length > value.composing.start
        ? TextRange(
            start: value.composing.start,
            end: math.min(value.composing.end, truncated.length),
          )
        : TextRange.empty,
    );
  }

  //顾名思义,更新字符串,每次输入的值发生改变时都会调用。
  //根据字符串最大值以及maxLengthEnforcement判断返回的是什么。
  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    final int? maxLength = this.maxLength;

    if (maxLength == null ||
      maxLength == -1 ||
      newValue.text.characters.length <= maxLength) {
      return newValue;
    }

    assert(maxLength > 0);

    switch (maxLengthEnforcement ?? getDefaultMaxLengthEnforcement()) {
      case MaxLengthEnforcement.none:
        return newValue;
      case MaxLengthEnforcement.enforced:

        if (oldValue.text.characters.length == maxLength && oldValue.selection.isCollapsed) {
          return oldValue;
        }


        return truncate(newValue, maxLength);
      case MaxLengthEnforcement.truncateAfterCompositionEnds:

        if (oldValue.text.characters.length == maxLength &&
          !oldValue.composing.isValid) {
          return oldValue;
        }

        if (newValue.composing.isValid) {
          return newValue;
        }

        return truncate(newValue, maxLength);
    }
  }
}

输入框显示的是什么是formatEditUpdate可以决定的,使用这次的更改主要是formatEditUpdate。

解决代码

参考LengthLimitingTextInputFormatter写一个新的TextInputFormatter。

这里只改了formatEditUpdate,以及不需要判定maxLengthEnforcement,所以把相关代码也删了。

Dart 复制代码
class MyLengthLimitingTextInputFormatter extends TextInputFormatter{

  final int? maxLength;

  MyLengthLimitingTextInputFormatter(this.maxLength) : assert(maxLength == null || maxLength == -1 || maxLength > 0);

  @visibleForTesting
  static TextEditingValue truncate(TextEditingValue value, int maxLength) {
    final CharacterRange iterator = CharacterRange(value.text);
    if (StringUtil.getTextLength(value.text) > maxLength) {
      iterator.expandNext(maxLength);
    }
    final String truncated = iterator.current;

    return TextEditingValue(
      text: truncated,
      selection: value.selection.copyWith(
        baseOffset: math.min(value.selection.start, truncated.length),
        extentOffset: math.min(value.selection.end, truncated.length),
      ),
      composing: !value.composing.isCollapsed && truncated.length > value.composing.start
          ? TextRange(
        start: value.composing.start,
        end: math.min(value.composing.end, truncated.length),
      )
          : TextRange.empty,
    );
  }

  //oldValue就是显示在输入框上的旧值,newValue就是显示在输入框上的旧值加你新输入的字符串
  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue,
      TextEditingValue newValue,
      ) {
    final int? maxLength = this.maxLength;

    //加上你新输入的字符串都没大于限制的最大值就直接在输入框显示新字符串
    if (maxLength == null ||
        maxLength == -1 ||
        newValue.text.characters.length <= maxLength) {
      return newValue;
    }

    assert(maxLength > 0);

    //已经达到最大值且新输入的字符串不是正在输入的状态
    //(真正输入存活状态:拼音拼出的字还没选中(下面会有下划线)或者手写的字还没在输入法选中(下面会有下划线);
    //存活状态就是反过来,选中了或者输入英文的时候就是打出的每个字母都直接到输入框,下面也不会有下划线) ,直接返回旧的值
    if (oldValue.text.characters.length == maxLength && !newValue.composing.isValid) {
      return oldValue;
    }
    //已经达到最大值且新输入的字符串是正在输入的状态,显示旧值+你新输入的值=newValue,
    //就是这句解决了拼音会被吞的问题
    if (oldValue.text.characters.length == maxLength && newValue.composing.isValid){
      return newValue;
    }

    // Enforced to return a truncated value.
    return truncate(newValue, maxLength);
  }

}

使用

将LengthLimitingTextInputFormatter换成MyLengthLimitingTextInputFormatter就行。

Dart 复制代码
            TextField(
              controller: _controller,
              inputFormatters: [
                MyLengthLimitingTextInputFormatter(maxLength),
              ],
               ......

缺陷

但是这样的解决方法还有一个问题:只要你英文打的够快,然后快速选择还在输入法上没消失的待选择的字母,它就能吞了前面的字。

附上这个问题没用到的:maxLengthEnforcement的区别

源码:

Dart 复制代码
enum MaxLengthEnforcement {
  /// No enforcement applied to the editing value. It's possible to exceed the
  /// max length.
  none,

  /// Keep the length of the text input from exceeding the max length even when
  /// the text has an unfinished composing region.
  enforced,

  /// Users can still input text if the current value is composing even after
  /// reaching the max length limit. After composing ends, the value will be
  /// truncated.
  truncateAfterCompositionEnds,
}

none:限制长度后还是可以随便输入 ,设了等于没设;

enforced:当字符长度达到限制长度时,正在输入的字符停止输入后会强制消失,例如输入英文字符停手后就会从输入法消失

truncateAfterCompositionEnds:当字符长度达到限制长度时,但正在输入的字符强制不会消失,也就是输入法里面预选的字不会消失

另一个方法

网上找到的另一个我还没验证的方法:

https://juejin.cn/post/6844904096407765005

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
茶底世界之下4 天前
为什么预览、录制、离线导出不能共享同一个背压策略?
ios·swift
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas