Flutter 之 输入框TextField

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 之 输入框TextField相关的知识,希望对你有一定的参考价值。

参考技术A Material组件库中提供了输入框组件TextField

TextField用于文本输入,它提供了很多属性,我们先简单介绍一下主要属性的作用,然后通过几个示例来演示一下关键属性的用法。

prefix和prefixText 以及suffix和suffixText 不能同时存在

获取输入内容

通过onChange回调
通过controller监听
_controller .addListener(()
print(_controller .text);
);

两种方式相比,onChanged是专门用于监听文本变化,而controller的功能却多一些,除了能监听文本变化外,它还可以设置默认值、选择文本

这里我们就用到了focusNode

焦点可以通过FocusNode和FocusScopeNode来控制,默认情况下,焦点由FocusScope来管理,它代表焦点控制范围,可以在这个范围内可以通过FocusScopeNode在输入框之间移动焦点、设置默认焦点等。我们可以通过FocusScope.of(context) 来获取Widget树中默认的FocusScopeNode。

Flutter实战
https://blog.csdn.net/yechaoa/article/details/90906689

Flutter 专题65 图解基本 TextField 文本输入框 #yyds干货盘点#

      小菜刚学习了 TextField 的基本用法,今天特意学习一下 TextField InputDecoration 文本框装饰器的相关内容;

InputDecoration 源码分析

const InputDecoration(
    this.icon,                  // 装饰器外小图标
    this.labelText,             // 文本框描述标签
    this.labelStyle,            // 文本框描述标签样式
    this.helperText,            // 文本框辅助标签
    this.helperStyle,           // 文本框辅助标签样式
    this.hintText,              // 文本框默认提示信息
    this.hintStyle,             // 文本框默认提示信息样式
    this.hintMaxLines,          // 文本框默认提示信息最大行数
    this.errorText,             // 文本框错误提示信息
    this.errorStyle,            // 文本框错误提示信息样式
    this.errorMaxLines,         // 文本框错误提示信息最大行数
    this.hasFloatingPlaceholder = true,     // 文本框获取焦点后 labelText 是否向上浮动
    this.isDense,               // 是否问紧凑型文本框
    this.contentPadding,        // 文本内边距
    this.prefixIcon,            // 前置图标
    this.prefix,                // 前置预填充 Widget
    this.prefixText,            // 前置预填充文本
    this.prefixStyle,           // 前置预填充样式
    this.suffixIcon,            // 后置图标
    this.suffix,                // 后置预填充 Widget
    this.suffixText,            // 后置预填充文本
    this.suffixStyle,           // 后置预填充样式
    this.counter,               // 输入框右下角 Widget
    this.counterText,           // 输入框右下角文本
    this.counterStyle,          // 输入框右下角样式
    this.filled,                // 是否颜色填充文本框
    this.fillColor,             // 填充颜色
    this.errorBorder,           // errorText 存在时未获取焦点边框
    this.focusedBorder,         // 获取焦点时边框
    this.focusedErrorBorder,    // errorText 存在时获取焦点边框
    this.disabledBorder,        // 不可用时边框
    this.enabledBorder,         // 可用时边框
    this.border,                // 边框
    this.enabled = true,        // 输入框是否可用
    this.semanticCounterText,
    this.alignLabelWithHint,    // 覆盖将标签与 TextField 的中心对齐
)

const InputDecoration.collapsed(
    @required this.hintText,
    this.hasFloatingPlaceholder = true,
    this.hintStyle,
    this.filled = false,
    this.fillColor,
    this.border = InputBorder.none,
    this.enabled = true,
)

      分析源码可知,Flutter 不仅提供了全面的构建装饰器的方式,还提供了简单便利的构建方式 collapsed 默认是无边框的,且无法设置标签等其他属性;

案例尝试

  1. icon 为装饰器外小图标,可灵活设置图标或其他 Widget,默认距输入框 16dp,主题可通过 IconTheme 设置;

    return TextField(decoration: InputDecoration(icon: Image.asset(images/ic_launcher.png)));
    return TextField(decoration: InputDecoration(icon: Icon(Icons.android)));

  2. labelText 为文本框描述标签,为 String 类型,直接编辑内容即可;labelStyle 为标签样式属性;TextField 获取焦点之后描述标签上移;

    return TextField(decoration: InputDecoration(
    labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple)));

  3. helperText 为文本框辅助标签,一般在文本框底部,提示性内容;helperStyle 为文本框辅助标签样式属性;与 TextField 是否获取焦点无变化;

    return TextField(decoration: InputDecoration(
    labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
    helperText: 请输入手机号或邮箱!, helperStyle: TextStyle(color: Colors.teal)));

  4. hintText 为文本框默认提示信息,若设置 labelText,则 TextField 在未获取焦点时优先展示 labelTexthintStyle 为文本框提示信息样式属性;hintMaxLines 为提示信息过长时允许展示的最大行数;
    
    return TextField(decoration: InputDecoration(
    hintStyle: TextStyle(color: Colors.brown), hintMaxLines: 1, hintText: 请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!));

return TextField(decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
helperText: 请输入手机号或邮箱!, helperStyle: TextStyle(color: Colors.teal),
hintText: 请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!,
hintStyle: TextStyle(color: Colors.brown), hintMaxLines: 2));

![6504.gif](https://s2.51cto.com/images/20220215/1644884832651018.gif)

5. **errorText** 为文本框错误提示信息,一般在文本框底部,当设置 **errorText** 时不展示 **helperText**,整体默认为红色;**errorStyle** 为错误提示信息样式属性;**errorMaxLines** 为错误信息过长时允许展示的最大行数;与 **hintText** 类似;

return TextField(onChanged: (text) setState(() _textLength = text.length; ); ,
decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
helperText: 请输入手机号或邮箱!, helperStyle: TextStyle(color: Colors.teal),
hintText: 请输入用户名信息!, hintStyle: TextStyle(color: Colors.brown),
errorText: _textLength > 11 ? 请勿超过 11 位用户名! : null, errorStyle: TextStyle(color: Colors.pink)));

![6505.gif](https://s2.51cto.com/images/20220215/1644884845646171.gif)

6. **hasFloatingPlaceholder** 设置 **TextField** 获取焦点时 **labelText** 是否向上浮动;设置为 **false** 时,获取焦点后 **labelText** 隐藏,不会向上浮动;

return TextField(decoration: InputDecoration(labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
hasFloatingPlaceholder: false));

![6506.gif](https://s2.51cto.com/images/20220215/1644884860546292.gif)

7. **isDense** 是否为紧凑型文本框,**true** 为紧凑型文本框,图标等与输入框边距较小;

return TextField(decoration: InputDecoration(icon: Icon(Icons.android), isDense: false));
return TextField(decoration: InputDecoration(icon: Icon(Icons.android), isDense: true));

![6507.gif](https://s2.51cto.com/images/20220215/1644884874968260.gif)

8. **contentPadding** 为编辑内容与文本框内边距;

returnTextField(decoration: InputDecoration(contentPadding: EdgeInsets.all(20.0)));

![6508.gif](https://s2.51cto.com/images/20220215/1644884890271323.gif)

9. **prefix...** 是文本框前置组件,**prefixIcon** 为前置图标,固定在文本输入框前边,与 **icon** 位置不同,其样式通过 **IconTheme** 调整;**prefixText** 为前置预填充文本,例如手机号前(+86) 之类;**prefix** 为前置预填充组件,可自由设置,更为灵活,但不能与 **prefixText** 同时使用;**prefixStyle** 为预填充组件样式;

return TextField(decoration: InputDecoration(
prefixIcon: Icon(Icons.supervised_user_circle), prefixText: (+86), prefixStyle: TextStyle(color: Colors.purple.withOpacity(0.4))));

return TextField(decoration: InputDecoration(
prefixIcon: Icon(Icons.supervised_user_circle), prefixStyle: TextStyle(color: Colors.purple.withOpacity(0.4)),
prefix: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[Icon(Icons.phone), Text((+86))])));

![6509.gif](https://s2.51cto.com/images/20220215/1644884904983151.gif)

10. **suffix...** 为文本框后置组件系列;与 **prefix...** 用法一致;

return TextField(decoration: InputDecoration(
suffixIcon: Icon(Icons.close), suffixText: 关闭, suffixStyle: TextStyle(color: Colors.purple.withOpacity(0.4))));

return TextField(decoration: InputDecoration(
suffixIcon: Icon(Icons.close), suffixStyle: TextStyle(color: Colors.purple.withOpacity(0.4)),
suffix: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[Text(关闭), Icon(Icons.remove_red_eye)])));

![6510.gif](https://s2.51cto.com/images/20220215/1644884919146297.gif)

11. **counter** 系列为文本框右下角计数器,当设置 **maxLengths** 时通常会在右下角展示编辑字符数与整体数量比,可通过 **counter** 系列组件调整;**counterText** 为计数器展示内容;**counterStyle** 为计数器样式属性;

return TextField(maxLength: 20,
decoration: InputDecoration(counterText: 最大长度不超过20, counterStyle: TextStyle(color: Colors.blueAccent)));

![6511.gif](https://s2.51cto.com/images/20220215/1644884933781987.gif)

12. **filled** 为文本框是否颜色填充,只有 **true** 时,**filledColor** 才生效;

return TextField(decoration: InputDecoration(fillColor: Colors.green.withOpacity(0.4), filled: true));

![6512.jpeg](https://s2.51cto.com/images/20220215/1644884944292433.jpeg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

13. **enabled** 为文本框是否可用,**false** 为不可用,无法获取焦点;

return TextField(decoration: InputDecoration(enabled: false));

![6513.jpeg](https://s2.51cto.com/images/20220215/1644884956964864.jpeg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

14. **alignLabelWithHint** 用于 **TextField** 设置多行时,**true** 时覆盖将标签与 **TextField** 的中心对齐的默认行为,小菜尝试了多种情况下 **true** 和 **false** 状态,发现效果并不明显,有待继续研究;

return TextField(maxLines: null, decoration: InputDecoration(
alignLabelWithHint: true, labelText: 用户名:,
hintMaxLines: null, helperText: 请输入手机号或邮箱!,
hintText: 请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!请输入用户名信息!,
));

15. **border** 为一个系列,包括各种环境下边框;默认 **border** 为正常状态下边框;边框基本包括三类:

a. **InputBorder** 一般设置为无边框样式;

return TextField(decoration: InputDecoration(border: InputBorder.none));

b. **UnderlineInputBorder** 一般设置为底部一条直线边框样式;小菜测试时设置边框圆角为 **10dp** 加上背景色效果更明显;

return TextField(decoration: InputDecoration(
filled: true, fillColor: Colors.green.withOpacity(0.4),
border: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.purple, width: 4.0, style: BorderStyle.solid))));

c. **OutlineInputBorder** 一般设置为包围的圆角边框;相较于 **UnderlineInputBorder** 多了 **gapPadding** 属性,用于浮动的 **labelText** 与边框的间距;

return TextField(decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
border: OutlineInputBorder(
gapPadding: 10.0, borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.purple, width: 4.0, style: BorderStyle.solid))));

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小菜测试发现 **UnderlineInputBorder** 和 **OutlineInputBorder** 对于设置 **border** 边框颜色无效,需要通过 **ThemeData** 来更改属性;

![6514.gif](https://s2.51cto.com/images/20220215/1644884971581314.gif)

16. **enabledBorder** 为可用时边框样式,**enabled** 为 **true**;

**Tips:**
1. **errorText** 存在时 **enabledBorder** 不生效;
2. 若不设置其他 **border** 属性,获取焦点默认是 **ThemeData** 中焦点边框,设置 **border** 或 **focusedBorder** 等生效;

// UnderlineInputBorder 类型且只设置 enabledBorder
return TextField(decoration: InputDecoration(filled: true,fillColor: Colors.green.withOpacity(0.4),
enabledBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple, width: 4.0))));
// UnderlineInputBorder 类型且设置 enabledBorder 和 border
return TextField(decoration: InputDecoration(filled: true, fillColor: Colors.green.withOpacity(0.4),
enabledBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple, width: 4.0)),
border: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)))));
// UnderlineInputBorder 类型且 errorText 不为空
return TextField(decoration: InputDecoration(filled: true, fillColor: Colors.green.withOpacity(0.4),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
enabledBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple, width: 4.0)),
border: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)))));
// OutlineInputBorder 类型且只设置 enabledBorder
return TextField(decoration: InputDecoration(labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple.withOpacity(0.4), width: 3.0)),
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)))));
// OutlineInputBorder 类型设置 enabledBorder,且 errorText 不为空
return TextField(decoration: InputDecoration(labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple.withOpacity(0.4), width: 3.0))));
// OutlineInputBorder 类,设置 enabledBorder 和 border 且 errorText 不为空
return TextField(decoration: InputDecoration(labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple.withOpacity(0.4), width: 3.0)),
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)))));

![6415.gif](https://s2.51cto.com/images/20220215/1644884988689585.gif)

17. **disabledBorder** 为不可用时边框,**enabled** 为 **false**;且 **errorText** 存在时 **disabledBorder** 不生效;

// UnderlineInputBorder 类型
return TextField( decoration: InputDecoration(enabled: false,
filled: true, fillColor: Colors.green.withOpacity(0.4),
disabledBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.pink, width: 4.0))));
// UnderlineInputBorder 类型且设置 errorText
return TextField(decoration: InputDecoration(enabled: false,
filled: true, fillColor: Colors.green.withOpacity(0.4),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
disabledBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.pink, width: 4.0)));
// OutlineInputBorder 类型
return TextField(decoration: InputDecoration(enabled: false,
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
disabledBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.green, width: 4.0))));
// OutlineInputBorder 类型且设置 errorText
return TextField(decoration: InputDecoration(enabled: false,
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
disabledBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.pink, width: 4.0))));

![6516.jpeg](https://s2.51cto.com/images/20220215/1644885000894462.jpeg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

18. **focusedBorder** 为获取焦点时边框,**errorText** 存在时 **focusedBorder** 不生效;

// UnderlineInputBorder 类型
return TextField(decoration: InputDecoration(
filled: true, fillColor: Colors.green.withOpacity(0.4),
focusedBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.pink, width: 4.0)),
enabledBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple, width: 4.0))));
// UnderlineInputBorder 类型且设置 errorText
return TextField(decoration: InputDecoration(
filled: true, fillColor: Colors.green.withOpacity(0.4),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
focusedBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.pink, width: 4.0)),
enabledBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple, width: 4.0))));
// OutlineInputBorder 类型
return TextField(decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple.withOpacity(0.4), width: 3.0)),
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.green, width: 4.0))));
// OutlineInputBorder 类型且设置 errorText
return TextField(decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.pink, width: 4.0)),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple.withOpacity(0.4), width: 3.0))));

![6517.gif](https://s2.51cto.com/images/20220215/1644885013187164.gif)

19. **errorBorder** 为 **errorText** 不为空且未获取焦点时边框;

// UnderlineInputBorder 类型
return TextField(decoration: InputDecoration(
filled: true, fillColor: Colors.green.withOpacity(0.4),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
errorBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.black.withOpacity(0.4), width: 4.0))));
// OutlineInputBorder 类型
return TextField(decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.black.withOpacity(0.4), width: 3.0))));

![6518.gif](https://s2.51cto.com/images/20220215/1644885027777723.gif)

20. **focusedErrorBorder** 为 **errorText** 不为空且获取焦点时边框;

// UnderlineInputBorder 类型
return TextField(decoration: InputDecoration(
filled: true, fillColor: Colors.green.withOpacity(0.4),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
focusedErrorBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.teal, width: 4.0)),
errorBorder: UnderlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.black, width: 4.0))));
// OutlineInputBorder 类型
return TextField(decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink),
focusedErrorBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.pink, width: 4.0)),
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.purple.withOpacity(0.4), width: 3.0))));

![6519.gif](https://s2.51cto.com/images/20220215/1644885041548544.gif)
### 小扩展
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在实际开发中,可能回随时需要关闭键盘,此时我们仅需监听一下即可;小菜监听一个文本输入框,当输入字符长度大于 11 位时即收起键盘;

return TextField(controller: controller,
decoration: InputDecoration(
labelText: 用户名:, labelStyle: TextStyle(color: Colors.purple),
errorText: 请勿超过 11 位!, errorStyle: TextStyle(color: Colors.pink)));

void initState()
super.initState();
controller.addListener(()
setState(()
if (controller.text.length >= 11)
// 收起键盘
FocusScope.of(context).requestFocus(FocusNode());

);
);


![6520.gif](https://s2.51cto.com/images/20220215/1644885053937046.gif)
***
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;文本输入框确实有很多细节需要研究和尝试,小菜仅初步了解,有待深入研究;且小菜建议时常升级 **Flutter** 版本,可能对于同一个 **Widget** 会有或多或少的更新,如有问题请多多指导!

>  来源:  阿策小和尚

以上是关于Flutter 之 输入框TextField的主要内容,如果未能解决你的问题,请参考以下文章

Flutter学习基本组件之基本表单组件

flutter去除Appbar的阴影、键盘遮挡下部输入框

flutter 输入框限制输入 数字小数

flutter 登录框demo

flutter 输入框组件封装

Flutter 完美的验证码输入框(2 种方法)