Skip to content

Instantly share code, notes, and snippets.

@ChrisMarxDev
Last active October 30, 2024 07:55
Show Gist options
  • Save ChrisMarxDev/59e3aa047b9a949202f902b72f1c6597 to your computer and use it in GitHub Desktop.
Save ChrisMarxDev/59e3aa047b9a949202f902b72f1c6597 to your computer and use it in GitHub Desktop.
Flutter component for a TextField that displays a number with a plus and minus button on each side
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CountSelector extends StatefulWidget {
const CountSelector({
super.key,
this.value = 0,
this.onChanged,
this.displayFunction,
this.min = 0,
this.max = 999999,
this.step = 1,
this.width = 108,
});
final double value;
final void Function(double value)? onChanged;
final String Function(double value)? displayFunction;
final double min;
final double max;
/// The step to increment or decrement by.
final double step;
final double width;
@override
State<CountSelector> createState() => _CountSelectorState();
}
class _CountSelectorState extends State<CountSelector> {
late double value;
late TextEditingController controller;
late FocusNode focusNode;
String displayFunction(double value) =>
widget.displayFunction?.call(value) ?? '$value';
@override
void initState() {
super.initState();
value = widget.value;
controller = TextEditingController(text: displayFunction(value));
focusNode = FocusNode();
}
@override
void didUpdateWidget(covariant CountSelector oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.value != widget.value &&
widget.value != double.tryParse(controller.text)) {
setState(() {
value = widget.value;
controller.text = displayFunction(value);
});
}
}
@override
Widget build(BuildContext context) {
final stepSize = widget.step;
return Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
if (widget.value - stepSize >= widget.min) {
widget.onChanged?.call(widget.value - stepSize);
}
},
icon: const Icon(Icons.remove),
),
const SizedBox(width: 8),
Container(
width: widget.width,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Theme.of(context).primaryColor),
),
child: TextField(
key: Key('counter_$value'),
focusNode: focusNode,
controller: controller,
keyboardType: const TextInputType.numberWithOptions(signed: true),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
textInputAction: TextInputAction.done,
decoration: const InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
onChanged: (value) {
widget.onChanged?.call(double.tryParse(value) ?? 0);
},
onSubmitted: (value) {
widget.onChanged?.call(double.tryParse(value) ?? 0);
},
textAlign: TextAlign.center,
textAlignVertical: TextAlignVertical.top,
// displayFunction(widget.value),
style: Theme.of(context).textTheme.bodyLarge,
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
if (widget.value + stepSize <= widget.max) {
widget.onChanged?.call(widget.value + stepSize);
}
},
icon: const Icon(Icons.add),
),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment