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