Flutter 中 TextField 组件必然会遇到的问题

老孟Flutter

共 5788字,需浏览 12分钟

 ·

2021-11-28 09:49

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),

以后再也不需要根据不同的高度和字体进行微调了。

你可能还喜欢

关注「老孟Flutter」
让你每天进步一点点
浏览 156
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报