前言
该文章只记录与工作中碰到的问题,Flutter 版本为 1.2.6 。有更多解决办法欢迎讨论学习。
背景
产品想要在商品搜索界面实现实时搜索。用户在输入商品名称的时候 就自动搜索商品信息。
问题
ios机型在原生键盘中文场景下,在输入字母拼音时onChanged:方法在iOS上会实时回调,拼音也会被搜索。如果输入过快,由于接口异步回调导致内容展示异常。
错误解决办法
- 输入中文时限制字母拼音在输入框内的显示,待选择中文后再显示,此时回调onChanged(不推荐使用,商品并非都是中文)
- 使用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源码,看到源码已经对输入内容做了一定的判断
- 通过来判断 value.composing ,第一个判断是直接输入的,那就直接返回一个普通样式
- 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;
}