【Flutter实用组件】自适应宽度的输入框
Flutter组件的TextField默认宽度为撑满容器,有些场景需要文本靠右,并且使用前缀,如果不限制宽度,前缀会在最左边,文本在最右边,宽度大了间距太开不好看,宽度设置小了,容易填满,文本就滚动到前缀下面隐藏了。
经Alex大佬和王叔提醒,在TextField外使用 IntrinsicWidth组件可以达到按最小宽度显示的效果,此组件SizedBox可直接替换为IntrinsicWidth,省去文本宽度计算
经过一翻调试,封装了个组件,实现在文本变化时自动调整输入框容器的宽度。
实现原理就是在输入框内容变化时,使用TextPainter绘制出来文本并获取绘制出来的尺寸,然后赋给TextField外的SizedBox。为了保证尺寸准确,TextPainter和TextField使用了相同的style。
效果如下:
以下为组件代码
class AmountInput extends StatefulWidget { final double? value; final String symbol; final bool isDecimal; final bool autoFocus; final FocusNode? focusNode; final double? spacer; final String? hintText; final TextStyle? textStyle; final void Function(double? value)? onChanged; const AmountInput({ Key? key, this.value, this.symbol = r'$', this.hintText, this.textStyle, this.focusNode, this.onChanged, this.spacer, this.autoFocus = false, this.isDecimal = true, }) : super(key: key); @override State<AmountInput> createState() => _AmountInputState(); } class _AmountInputState extends State<AmountInput> { String amount = ''; late TextEditingController editingController; @override void initState() { super.initState(); amount = widget.value?.toString() ?? ''; editingController = TextEditingController(text: amount); } Size getTextSize(String text, [TextStyle? style]) { TextPainter painter = TextPainter( text: TextSpan(text: text, style: style), textDirection: TextDirection.ltr, maxLines: 1, ellipsis: '...', ); painter.layout(); return painter.size; } @override Widget build(BuildContext context) { final textStyle = widget.textStyle ?? Theme.of(context).textTheme.bodyMedium; return SizedBox( width: getTextSize( '${widget.symbol} ${amount.isEmpty ? (widget.hintText ?? '') : amount}', textStyle, ).width + (widget.spacer ?? 3.w), child: TextField( textAlign: TextAlign.end, controller: editingController, style: textStyle, autofocus: true, focusNode: widget.focusNode, inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(widget.isDecimal ? r'[0-9\.]' : r'[0-9]'), ), ], onChanged: (newValue) { setState(() { amount = newValue; }); widget.onChanged?.call(double.tryParse(newValue)); }, decoration: InputDecoration( prefix: Text(widget.symbol), border: InputBorder.none, ), ), ); } }
另外,由于初始化时宽度比较窄,为了方便操作,建议在组件外层增加一个tap事件来获取焦点
部分代码:
GestureDetector( onTap: () { focusNode.requestFocus(); }, child: Card( child: Padding( padding: EdgeInsets.all(14.w), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('SGD', style: theme.textTheme.titleLarge), const Spacer(), AmountInput( focusNode: focusNode, onChanged:(newValue){ print(newValue); }, ), ], ), Text( 'Last 30 days: S\$0', style: theme.textTheme.titleSmall, ), ], ), ), ), ),