Flutter iOS输入法 Bug Fix 记录

前言

该文章只记录与工作中碰到的问题,Flutter 版本为 1.2.6 。有更多解决办法欢迎讨论学习。

背景

产品想要在商品搜索界面实现实时搜索。用户在输入商品名称的时候 就自动搜索商品信息。

问题

ios机型在原生键盘中文场景下,在输入字母拼音时onChanged:方法在iOS上会实时回调,拼音也会被搜索。如果输入过快,由于接口异步回调导致内容展示异常。

错误解决办法

  1. 输入中文时限制字母拼音在输入框内的显示,待选择中文后再显示,此时回调onChanged(不推荐使用,商品并非都是中文)
  2. 使用dart自带属性isComposingRangeValid (听说 Flutter 2.0可用)
#state.dart

TextEditingController _controller;

_controller = TextEditingController.fromValue(TextEditingValue(
          text: searchText,
          // 保持光标在最后
          selection: TextSelection.fromPosition(TextPosition(
              affinity: TextAffinity.downstream, offset: searchText.length))))
#View.dart

Expanded(
  flex: 1,
  child: TextField(
    decoration: new InputDecoration(
      hintText: '搜索商品名称、原料、规格、属性',
      border: InputBorder.none,
      isDense: true,
      isCollapsed: false,
    ),
    autofocus: true,
    style: TextStyleMs.ff_333333_16,
    controller: state.controller,
    onChanged: (value) {
      //使用controller 判断
      if (state.controller.value.isComposingRangeValid) {
        return;
      }
      //退出软键盘
      FocusScope.of(viewService.context)
          .requestFocus(state.blankNode);
      dispatch(
          GoodsSearchActionCreator.onUpdateSearchText(
              value));
      dispatch(GoodsSearchActionCreator.onSearchFoods(
          value));
    },
  ),
),

实测: 该方法返回的isComposingRangeValid 针对ios判断依然是 true,没有效果

正确解决思路

/// Builds [TextSpan] from current editing value.
  ///
  /// By default makes text in composing range appear as underlined. Descendants
  /// can override this method to customize appearance of text.
  TextSpan buildTextSpan({TextStyle style , bool withComposing}) {
    assert(!value.composing.isValid || !withComposing || value.isComposingRangeValid);
    // If the composing range is out of range for the current text, ignore it to
    // preserve the tree integrity, otherwise in release mode a RangeError will
    // be thrown and this EditableText will be built with a broken subtree.
    // 注释1
    if (!value.isComposingRangeValid || !withComposing) {
      return TextSpan(style: style, text: text);
    }
    final TextStyle composingStyle = style.merge(
      const TextStyle(decoration: TextDecoration.underline),
    );
        // 注释2
    return TextSpan(
      style: style,
      children: [
        TextSpan(text: value.composing.textBefore(value.text)),
        TextSpan(
          style: composingStyle,
          text: value.composing.textInside(value.text),
        ),
        TextSpan(text: value.composing.textAfter(value.text)),
    ]);
  }

通过查看TextEditingController源码,看到源码已经对输入内容做了一定的判断

  1. 通过来判断 value.composing ,第一个判断是直接输入的,那就直接返回一个普通样式
  2. value.composing.textInside(value.text)获取到的就是输入未完成的字符,默认是添加了一个下划线的样式(composingStyle)。根据注释,google提示我们可以重写此方法改写样式!

正确解决办法

新建一个controller继承自TextEditingController,重写buildTextSpan方法

class ChinaTextEditController extends TextEditingController{
  ///拼音输入完成后的文字
  var completeText = '';

  @override
  TextSpan buildTextSpan({TextStyle style, bool withComposing}) {
    ///拼音输入完成
    if (!value.composing.isValid || !withComposing) {
      if(completeText!=value.text){
        completeText = value.text;
        WidgetsBinding.instance.addPostFrameCallback((_){
          notifyListeners();
        });
      }
      return TextSpan(style: style, text: text);
    }

    ///返回输入样式,可自定义样式
    final TextStyle composingStyle = style.merge(
      const TextStyle(decoration: TextDecoration.underline),
    );
    return TextSpan(
        style: style,
        children: [
          TextSpan(text: value.composing.textBefore(value.text)),
          TextSpan(
            style: composingStyle,
            text:
            value.composing.isValid && !value.composing.isCollapsed?
            value.composing.textInside(value.text):"",
          ),
          TextSpan(text: value.composing.textAfter(value.text)),
        ]);
  }

}

redux 的view 中使用

Expanded(
  flex: 1,
  child: TextField(
    decoration: new InputDecoration(
      hintText: '搜索商品名称、原料、规格、属性',
      border: InputBorder.none,
      isDense: true,
      isCollapsed: false,
    ),
    autofocus: true,
    style: TextStyleMs.ff_333333_16,
    controller: state.controller,
  ),
),

redux state中绑定

class GoodsSearchState implements Cloneable {

  ...

  ChinaTextEditController _controller;

  ChinaTextEditController get controller => _controller;

  set controller(TextEditingController value) {
    _controller = value;
  }

  ///空白焦点 用来隐藏软键盘
  FocusNode blankNode;

  @override
  GoodsSearchState clone() {
    return GoodsSearchState()
      ..searchText = searchText
      .._controller = _controller
      ...
            ;
  }
}

GoodsSearchState initState(Map args) {
  GoodsSearchState state = GoodsSearchState();
  state.searchText = '';
  state.blankNode = FocusNode();
  state.controller = ChinaTextEditController();
  state.controller.text = state.searchText;
    //输入下标在文字之后
  state.controller.selection = TextSelection.fromPosition(TextPosition(
      affinity: TextAffinity.downstream, offset: state.searchText.length));
  ...
  return state;
}

你可能感兴趣的:(Flutter iOS输入法 Bug Fix 记录)