前语

该文章只记载与工作中碰到的问题,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>[
        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>[
          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<GoodsSearchState> {
  ...
  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<String, dynamic> 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;
}