2025-06-16
Flutter
00
请注意,本文编写于 36 天前,最后修改于 36 天前,其中某些信息可能已经过时。

目录

代码
使用

自定义TextField实现加减数量输入框,数字步进器效果,可以根据自己的需求改动

效果如下:

动画.gif

代码

dart
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class MyNumberPicker extends StatefulWidget { final double height; final double contentWidth; final double iconWidth; double num; final double min; final double max; final double step; final ValueChanged<double> onValueChanged; final bool disabled; final bool readOnly; final bool signed; MyNumberPicker( {super.key, this.height = 36.0, this.contentWidth = 80.0, this.iconWidth = 36.0, this.num = 0, this.min = 0, this.max = 0, this.step = 1, this.disabled = false, this.readOnly = false, this.signed = false, required this.onValueChanged}); @override State<MyNumberPicker> createState() { return _MyNumberPickerState(); } } class _MyNumberPickerState extends State<MyNumberPicker> { final TextEditingController _numController = TextEditingController(); FocusNode focusNode = FocusNode(); // step是否为整数 bool _isInt = true; @override void initState() { super.initState(); _isInt = widget.step % 1 == 0; _numController.addListener(_onNumChange); focusNode.addListener(_focusNodeListener); } void _onNumChange() { String text = _numController.text; if (text.isNotEmpty) { String result = text.replaceAll(_isInt ? RegExp(r'^0+') : RegExp(r'^(0(?!\.)|(?<!\d)\.)'), ''); // 去掉首位0的正则替换 if (result != '') { widget.num = double.parse(result); widget.onValueChanged(widget.num); } if (result != text) { _numController.selection = TextSelection.fromPosition(TextPosition(offset: result.length + 1)); } } } _focusNodeListener() { if (!focusNode.hasFocus) { // 焦点失去时的操作 String text = _numController.text; if (text.isNotEmpty) { double num = double.parse(text); if (num > widget.max && widget.max != 0) { num = widget.max; } else if (num < widget.min) { num = widget.min; } if (int.parse(num.toStringAsFixed(0)) - num == 0) { _numController.text = num.toStringAsFixed(0); } else { _numController.text = num.toString(); } setState(() { widget.num = num; }); widget.onValueChanged(num); } else { double num = widget.min; if (int.parse(num.toStringAsFixed(0)) - num == 0) { _numController.text = num.toStringAsFixed(0); } else { _numController.text = num.toString(); } widget.onValueChanged(num); } } } @override Widget build(BuildContext context) { //_numController.text = widget.num.toString(); if (int.parse(widget.num.toStringAsFixed(0)) - widget.num == 0) { _numController.text = widget.num.toStringAsFixed(0); } else { _numController.text = widget.num.toString(); } return Container( height: widget.height, decoration: const BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(2.0)), color: Color(0xEFEFEFef)), child: Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ GestureDetector( onTap: _minusNum, child: Container( width: widget.iconWidth, height: widget.height, alignment: Alignment.center, decoration: BoxDecoration( color: widget.num <= widget.min || widget.disabled ? Theme.of(context).colorScheme.tertiary.withOpacity(0.5) : Theme.of(context).colorScheme.tertiary, borderRadius: BorderRadius.circular(5), ), child: const Icon(Icons.horizontal_rule_outlined, color: Colors.white), ), ), Container( width: 2, color: Colors.white, ), Container( width: widget.contentWidth, height: widget.height, alignment: Alignment.center, child: TextField( controller: _numController, focusNode: focusNode, cursorHeight: widget.height - 6, //TextEditingController,用于获取文本值 keyboardType: widget.signed ? TextInputType.text : TextInputType.number, //设置键盘为数字 textAlign: TextAlign.center, textAlignVertical: TextAlignVertical.center, // 内容左右居中 maxLines: 1, decoration: const InputDecoration( border: OutlineInputBorder(borderSide: BorderSide.none), contentPadding: EdgeInsets.all(0), ), inputFormatters: [ _isInt ? FilteringTextInputFormatter.allow(RegExp(r'[-0-9]+')) //设置只允许输入整数 : FilteringTextInputFormatter.allow(RegExp(r'^[-0-9]+(\.[0-9]*)?$')) // 允许输入整数 ], style: const TextStyle(fontSize: 16, color: Colors.black), readOnly: widget.readOnly, onEditingComplete: () { focusNode.unfocus(); }, onTapOutside: (e) { focusNode.unfocus(); }, ), ), Container( width: 2, color: Colors.white, ), GestureDetector( onTap: _addNum, child: Container( width: widget.iconWidth, height: widget.height, alignment: Alignment.center, decoration: BoxDecoration( color: (widget.num >= widget.max && widget.max != 0) || widget.disabled ? Theme.of(context).colorScheme.tertiary.withOpacity(0.5) : Theme.of(context).colorScheme.tertiary, borderRadius: BorderRadius.circular(5), ), child: const Icon(Icons.add_outlined, color: Colors.white), // 设计图 ), ), ], ), ); } void _minusNum() { if (widget.num <= widget.min || widget.disabled) { return; } setState(() { widget.num = ((widget.num * 100).round() - (widget.step * 100).round()) / 100; if (widget.num > widget.max && widget.max != 0) { widget.num = widget.max; } else if (widget.num < widget.min) { widget.num = widget.min; } }); if (int.parse(widget.num.toStringAsFixed(0)) - widget.num == 0) { widget.onValueChanged(double.parse(widget.num.toStringAsFixed(0))); } else { widget.onValueChanged(widget.num); } } void _addNum() { if ((widget.num >= widget.max && widget.max != 0) || widget.disabled) { return; } setState(() { widget.num = ((widget.num * 100).round() + (widget.step * 100).round()) / 100; if (widget.num > widget.max && widget.max != 0) { widget.num = widget.max; } else if (widget.num < widget.min) { widget.num = widget.min; } }); if (int.parse(widget.num.toStringAsFixed(0)) - widget.num == 0) { widget.onValueChanged(double.parse(widget.num.toStringAsFixed(0))); } else { widget.onValueChanged(widget.num); } } @override void dispose() { focusNode.removeListener(_focusNodeListener); focusNode.dispose(); _numController.removeListener(_onNumChange); _numController.dispose(); super.dispose(); } }

使用

dart
MyNumberPicker( onValueChanged: (double v) { print(v); }, contentWidth: 60, min: 1, max: 24, num: 1, step: 1, ),

如果使用Getx管理num的状态的话,在更新num状态时需要使用Future.delayed包裹,否则首次渲染会因为在渲染时setState()导致报错;

dart
Obx(() => MyNumberPicker( onValueChanged: (double v) { Future.delayed(Duration.zero, () { state.num.value = v; }); }, contentWidth: 60, min: 1, max: 24, num: state.num.value, step: 1, )),

本文作者:哈希喵

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!