Flutter input time in hh:mm:ss format in TextFormField or TextField without using pickers
Solution 1
I used TextInputFormatter in the text field instead of onChanged. This allows to access old value and format the new input depending on if the new character was added or deleted. It also allows to use RegExp to accept only numbers. This would be the solution:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;
class TimeInputField extends StatefulWidget {
TimeInputField({Key key}) : super(key: key);
@override
_TimeInputFieldState createState() => _TimeInputFieldState();
}
class _TimeInputFieldState extends State<TimeInputField> {
TextEditingController _txtTimeController = TextEditingController();
@override
Widget build(BuildContext context) {
return TextFormField(
controller: _txtTimeController,
keyboardType: TextInputType.numberWithOptions(decimal: false),
decoration: InputDecoration(
hintText: '00:00:00',
),
inputFormatters: <TextInputFormatter>[
TimeTextInputFormatter() // This input formatter will do the job
],
);
}
}
class TimeTextInputFormatter extends TextInputFormatter {
RegExp _exp;
TimeTextInputFormatter() {
_exp = RegExp(r'^[0-9:]+$');
}
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
if (_exp.hasMatch(newValue.text)) {
TextSelection newSelection = newValue.selection;
String value = newValue.text;
String newText;
String leftChunk = '';
String rightChunk = '';
if (value.length >= 8) {
if (value.substring(0, 7) == '00:00:0') {
leftChunk = '00:00:';
rightChunk = value.substring(leftChunk.length + 1, value.length);
} else if (value.substring(0, 6) == '00:00:') {
leftChunk = '00:0';
rightChunk = value.substring(6, 7) + ":" + value.substring(7);
} else if (value.substring(0, 4) == '00:0') {
leftChunk = '00:';
rightChunk = value.substring(4, 5) +
value.substring(6, 7) +
":" +
value.substring(7);
} else if (value.substring(0, 3) == '00:') {
leftChunk = '0';
rightChunk = value.substring(3, 4) +
":" +
value.substring(4, 5) +
value.substring(6, 7) +
":" +
value.substring(7, 8) +
value.substring(8);
} else {
leftChunk = '';
rightChunk = value.substring(1, 2) +
value.substring(3, 4) +
":" +
value.substring(4, 5) +
value.substring(6, 7) +
":" +
value.substring(7);
}
} else if (value.length == 7) {
if (value.substring(0, 7) == '00:00:0') {
leftChunk = '';
rightChunk = '';
} else if (value.substring(0, 6) == '00:00:') {
leftChunk = '00:00:0';
rightChunk = value.substring(6, 7);
} else if (value.substring(0, 1) == '0') {
leftChunk = '00:';
rightChunk = value.substring(1, 2) +
value.substring(3, 4) +
":" +
value.substring(4, 5) +
value.substring(6, 7);
} else {
leftChunk = '';
rightChunk = value.substring(1, 2) +
value.substring(3, 4) +
":" +
value.substring(4, 5) +
value.substring(6, 7) +
":" +
value.substring(7);
}
} else {
leftChunk = '00:00:0';
rightChunk = value;
}
if (oldValue.text.isNotEmpty && oldValue.text.substring(0, 1) != '0') {
if (value.length > 7) {
return oldValue;
} else {
leftChunk = '0';
rightChunk = value.substring(0, 1) +
":" +
value.substring(1, 2) +
value.substring(3, 4) +
":" +
value.substring(4, 5) +
value.substring(6, 7);
}
}
newText = leftChunk + rightChunk;
newSelection = newValue.selection.copyWith(
baseOffset: math.min(newText.length, newText.length),
extentOffset: math.min(newText.length, newText.length),
);
return TextEditingValue(
text: newText,
selection: newSelection,
composing: TextRange.empty,
);
}
return oldValue;
}
}
Solution 2
Flutter time input formatter with hh:mm format in TextFormField or TextField without using pickers, with max hours and max minutes :
import 'dart:math' as math;
import 'package:flutter/services.dart';
class TimeTextInputFormatter extends TextInputFormatter {
TimeTextInputFormatter(
{required this.hourMaxValue, required this.minuteMaxValue}) {
_exp = RegExp(r'^$|[0-9:]+$');
}
late RegExp _exp;
final int hourMaxValue;
final int minuteMaxValue;
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
if (_exp.hasMatch(newValue.text)) {
TextSelection newSelection = newValue.selection;
final String value = newValue.text;
String newText;
String leftChunk = '';
String rightChunk = '';
if (value.length > 1 &&
(int.tryParse(value.substring(0, 2)) ?? 0) == hourMaxValue)
//this logic is to restrict value more than max hour
{
if (oldValue.text.contains(':')) {
leftChunk = value.substring(0, 1);
} else {
leftChunk = '${value.substring(0, 2)}:';
rightChunk = '00';
}
} else if (value.length > 5) {
//this logic is to not allow more value
leftChunk = oldValue.text;
} else if (value.length == 5) {
if ((int.tryParse(value.substring(3)) ?? 0) > minuteMaxValue) {
//this logic is to restrict value more than max minute
leftChunk = oldValue.text;
} else {
leftChunk = value;
}
} else if (value.length == 2) {
if (oldValue.text.contains(':')) {
//this logic is to delete : & value before : ,when backspacing
leftChunk = value.substring(0, 1);
} else {
if ((int.tryParse(value) ?? 0) > hourMaxValue) {
//this logic is to restrict value more than max hour
leftChunk = oldValue.text;
} else {
//this logic is to add : with second letter
leftChunk = '${value.substring(0, 2)}:';
rightChunk = value.substring(2);
}
}
} else {
leftChunk = value;
}
newText = leftChunk + rightChunk;
newSelection = newValue.selection.copyWith(
baseOffset: math.min(newText.length, newText.length),
extentOffset: math.min(newText.length, newText.length),
);
return TextEditingValue(
text: newText,
selection: newSelection,
); }
return oldValue;
}
}
Andrey Sorokin
Updated on January 02, 2023Comments
-
Andrey Sorokin over 1 year
Can anyone help to figure out how to set up a TextField or TextFormField widget for time input in hh:mm:ss format?
The formatting should be displayed in the field live, so the leading zeroes will be replaced while the user is typing.
For example, if we need to enter 1 hour 59 minutes and 27 seconds, it would need to work as follows:
- 00:00:00 - text hint before typing
- 00:00:01 - starts typing and typed 1
- 00:00:15 - typed 5
- 00:01:59 - typed 9
- 00:15:92 - typed 2 (92 seconds is acceptable here, it can be converted after the input is complete)
- 01:59:27 - typed 7
It is working in the similar manner on the timer in the Android's built in Clock app.
I tried to use mask_text_input_formatter package, but it does not work as I need it to. Also, I do not wish to use time pickers.
import 'package:flutter/services.dart'; import 'package:mask_text_input_formatter/mask_text_input_formatter.dart'; class TimeInputField extends StatefulWidget { TimeInputField({Key key}) : super(key: key); @override _TimeInputFieldState createState() => _TimeInputFieldState(); } class _TimeInputFieldState extends State<TimeInputField> { TextEditingController _txtTimeController = TextEditingController(); final MaskTextInputFormatter timeMaskFormatter = MaskTextInputFormatter(mask: '##:##:##', filter: {"#": RegExp(r'[0-9]')}); @override Widget build(BuildContext context) { return TextFormField( controller: _txtTimeController, keyboardType: TextInputType.numberWithOptions(decimal: false), decoration: InputDecoration( hintText: '00:00:00', ), inputFormatters: <TextInputFormatter>[ timeMaskFormatter // Not sure if it can be done with RegExp or a custom class here instead ], ); } }
Any help is greatly appreciated!
-
Andrey Sorokin over 2 yearsThank you for your help. It would not work for me though. I will not have a label for my field. Because the controller text is updated after the 6th digit case in the provided code, it would only update the text editing value after typing 6 digits. I need it updated after each digit. Updating the controller text value after each case in the switch would not let me type anything after the first digit.
-
dartKnightRises over 2 yearsTry the latest one.
-
Andrey Sorokin over 2 yearsThat one will not work. If using this code, the controller value length will always be either 1 or 6 (when not empty). I will post my own solution. Thanks for your input again.
-
Jeremy Caney over 2 yearsWelcome to Stack Overflow, and thank you for returning to your question to share your solution with the community. Would you kindly edit your answer to include an explanation of your code? That will help future readers better understand what is going on, and especially those members of the community who are new to the language and struggling to understand the concepts.