Flutter 可能會用到的計數(shù)器葡秒,支持[最小值, 最大值, 初始值]
全部刪除后賦最小值&選中賦值。
image.png
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CounterStep extends StatefulWidget {
const CounterStep({
required this.min,
required this.max,
this.initial,
this.step = 1,
required this.valueChanged,
Key? key,
}) : assert(min < max),
assert(initial == null || (initial >= min && initial <= max)),
assert(step > 0),
super(key: key);
/// 最小值
final int min;
/// 最大值
final int max;
/// 初始值,如果初始值為null或無效奢赂,則初始值為[min]最小值
final int? initial;
/// 步進排霉,每次+/- Value變化數(shù)值,必須是正數(shù)
final int step;
/// Value值改變回調(diào)
final ValueChanged<int> valueChanged;
@override
State<CounterStep> createState() => _CounterStepState();
}
class _CounterStepState extends State<CounterStep> {
late final controller = TextEditingController(text: widget.initial ?? widget.min);
@override
Widget build(BuildContext context) {
return Container(
height: 30,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.outline, width: 0.5),
borderRadius: BorderRadius.circular(4.0),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
buildReduceButton(),
VerticalDivider(
width: 1, color: Theme.of(context).colorScheme.outline),
SizedBox(
width: 100,
child: buildTextFieldInput(),
),
VerticalDivider(
width: 1, color: Theme.of(context).colorScheme.outline),
buildIncreaseButton(),
],
),
);
}
Widget buildTextFieldInput() {
return TextField(
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
controller: controller,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLines: 1,
inputFormatters: [
CounterTextInputFormatter(min: widget.min, max: widget.max),
],
onChanged: (value) {
widget.valueChanged(int.parse(value));
},
);
}
Widget buildReduceButton() {
return InkWell(
child: Icon(Icons.remove, color: Theme.of(context).colorScheme.outline),
onTap: () {
count = max(count - widget.step, widget.min);
String text = count.toString();
controller.value = TextEditingValue(text: text, selection: TextSelection.collapsed(offset: text.length));
},
);
}
Widget buildIncreaseButton() {
return InkWell(
child: Icon(Icons.add, color: Theme.of(context).colorScheme.outline),
onTap: () {
count = min(count + widget.step, widget.max);
String text = count.toString();
controller.value = TextEditingValue(text: text, selection: TextSelection.collapsed(offset: text.length));
},
);
}
}
class CounterTextInputFormatter extends TextInputFormatter {
final int min;
final int max;
CounterTextInputFormatter({required this.min, required this.max});
int get maxLength => '$max'.length;
late final regExp = RegExp("^\\d{0,$maxLength}?\$");
@override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue ) {
String oldText = oldValue.text;
String newText = newValue.text;
if (newText.isEmpty) {
String text = '$min';
return TextEditingValue(
text: text,
selection: TextSelection(baseOffset: 0, extentOffset: text.length),
);
}
// 判定 新輸入值符合輸入預(yù)期
bool isValid = (oldText.length > newText.length) ||
regExp.hasMatch(newText);
if (isValid) {
// 如果以0開頭煮落、轉(zhuǎn)換為有效數(shù)字
if (newText.startsWith('0')) {
String text = int.parse(newText).toString();
return TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
);
}
return newValue;
}
return oldValue;
}
}