Flutter 中 TextField 组件必然会遇到的问题
TextField 组件几乎是开发中必然会用到的一个组件,在使用的过程中会遇到两个非常棘手的问题:
字数统计异常。 设置高度,文字无法居中。
字数统计异常
一般情况下,实现字数统计方法如下:
TextField(
onChanged: (value){
setState(() {
_textFieldValue = value;
});
},
decoration: InputDecoration(
counterText: '${_textFieldValue.length}/32'
),
),
大部分情况下是没有问题的,但是在 IOS 简体拼音输入法下有问题(可能其他输入法也有类似的问题),效果如下:
中文输入法统计正在编辑中文的过程中会统计英文,假如限制5个中文,当输入4个中文后,最后一个中文输入2个及以上英文时,会触发最大字数限制,这当然不是我们想要的效果。
❝在去年的时候,这个Bug解决了很久都没有解决,最终产品妥协去掉了这个功能,直到最近查看源码的时候,无意中发现了这个Bug的解决方案。
❞
下面说下如何修复这个问题,关键是 TextField 中 「controller.value.composing」 这个属性,官方文档说明:
❝The range of text that is still being composed.
仍在编写的文本范围。
❞
就是上面GIF中出现下划线的部分,字数统计计算:
TextEditingController _controller = TextEditingController();
int _wordLength = 0;
/// 计算字数,不算正在编辑的文字
void _computeWordCount() {
var valueLength = _controller.value.text.length;
var composingLength =
_controller.value.composing.end - _controller.value.composing.start;
setState(() {
_wordLength = valueLength - composingLength;
});
}
TextField(
controller: _controller,
onChanged: (value){
_computeWordCount();
},
decoration: InputDecoration(
counterText: '$_wordLength/32'
),
),
文字无法居中
首先我们写一个 「TextField」 的基本用法,为了方便定位文字是否居中,给 「TextField」 加上边框:
TextField(
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFDCDFE6)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFF409EFF)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
),
),
此时文字是正好居中的,下面改变 TextField 的高度:
Container(
height: 30,
child: TextField(
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFDCDFE6)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFF409EFF)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
),
),
),
发现此时文字已经不居中了,当然网上有很多解决办法,比如设置 contentPadding: EdgeInsets.symmetric(vertical: 0,horizontal: 12):
Container(
height: 30,
child: TextField(
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFDCDFE6)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFF409EFF)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
contentPadding: EdgeInsets.symmetric(vertical: 0,horizontal: 12),
),
),
),
其实这种方式并没有严格居中对齐,只不过偏差较小,勉强可以接受。
看下面的例子,设置高度为150:
Container(
height: 150,
color: Colors.green.withOpacity(.5),
child: TextField(
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFDCDFE6)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFF409EFF)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
),
),
)
发现 TextField 的高度不是150,在 「maxLines = 1」 的情况下,通过设置 「contentPadding」 改变其高度,为了方便验证是否居中,在中间绘制一条对齐线:
Container(
height: 150,
color: Colors.green.withOpacity(.5),
child: Stack(
children: [
TextField(
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFDCDFE6)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFF409EFF)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 75),
),
),
Positioned.fill(
child: Divider(
height: 1,
color: Colors.red,
)),
],
),
),
发现其未居中,调整 「contentPadding」,
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 67.5)
我们改变文字的大小:
Container(
height: 150,
color: Colors.green.withOpacity(.5),
child: Stack(
children: [
TextField(
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFDCDFE6)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFF409EFF)),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 67.5),
),
style: TextStyle(fontSize: 30),
),
Positioned.fill(
child: Divider(
height: 1,
color: Colors.red,
)),
],
),
),
此时又不居中了,contentPadding** 需要设置的值是根据 TextField的高度 和 文字高度共同决定的,公式是:
❝「( TextField的高度 - 文字高度)/2」
❞
我们需要计算出文字的高度:
TextStyle _style = const TextStyle(fontSize: 30);
var textPainter = TextPainter(
text: TextSpan(
text: '',
style: _style,
),
textDirection: TextDirection.ltr,
textWidthBasis: TextWidthBasis.longestLine,
)..layout();
「textPainter.height」 表示文字的高度。
设置 contentPadding:
contentPadding: EdgeInsets.symmetric(
horizontal: 12,
vertical: (150 - textPainter.height) / 2),
以后再也不需要根据不同的高度和字体进行微调了。