[英]Flutter TextField input validation for a date
我正在嘗試編寫一個日期輸入控件,它接受像 23/12/1997 這樣的日期。 我想做的是自動為用戶插入 / 字符。 因此,當他們輸入 23 時,偵聽器返回 23/,然后他們可以輸入 12。此時,偵聽器再次添加 /,讓用戶通過輸入 1997 來完成日期。我的 TextEditingController 代碼一半工作,看起來像這樣:
final _controller = TextEditingController();
_controller.addListener(() {
String text = _controller.text;
if (text.length == 2) {
text += '/';
}
if (text.length == 5) {
text += '/';
}
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
print(_controller.text);
}
所以它工作正常,直到用戶犯錯並需要回溯。 一旦 / 被刪除,它就會立即被替換,從而停止對日期的任何進一步編輯。
為了讓它工作,我需要訪問之前輸入的文本,以確定用戶是否正在退格。 因此,如果text == 23/ && previous_text == 23/1
那么我可以從文本中刪除 / 。
我發現這個問題文本字段必須只接受數字,我認為它可能對我有幫助,但我不確定如何實現現有的小部件並覆蓋它的方法。 當然,在 TextEditingController 中可能有更簡單的方法來執行此操作?
我找到了解決日期驗證輸入所需的內容。 它並不完美,但對於我正在嘗試做的事情來說已經足夠了。 我只需要查看 TextField() 的inputFormatters方法。 這允許對輸入文本進行操作以將其轉換為任意數量的用戶定義格式。 我為任何想嘗試的人提供了一段代碼:
class _DateFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue prevText, TextEditingValue currText) {
int selectionIndex;
// Get the previous and current input strings
String pText = prevText.text;
String cText = currText.text;
// Abbreviate lengths
int cLen = cText.length;
int pLen = pText.length;
if (cLen == 1) {
// Can only be 0, 1, 2 or 3
if (int.parse(cText) > 3) {
// Remove char
cText = '';
}
} else if (cLen == 2 && pLen == 1) {
// Days cannot be greater than 31
int dd = int.parse(cText.substring(0, 2));
if (dd == 0 || dd > 31) {
// Remove char
cText = cText.substring(0, 1);
} else {
// Add a / char
cText += '/';
}
} else if (cLen == 4) {
// Can only be 0 or 1
if (int.parse(cText.substring(3, 4)) > 1) {
// Remove char
cText = cText.substring(0, 3);
}
} else if (cLen == 5 && pLen == 4) {
// Month cannot be greater than 12
int mm = int.parse(cText.substring(3, 5));
if (mm == 0 || mm > 12) {
// Remove char
cText = cText.substring(0, 4);
} else {
// Add a / char
cText += '/';
}
} else if ((cLen == 3 && pLen == 4) || (cLen == 6 && pLen == 7)) {
// Remove / char
cText = cText.substring(0, cText.length - 1);
} else if (cLen == 3 && pLen == 2) {
if (int.parse(cText.substring(2, 3)) > 1) {
// Replace char
cText = cText.substring(0, 2) + '/';
} else {
// Insert / char
cText =
cText.substring(0, pLen) + '/' + cText.substring(pLen, pLen + 1);
}
} else if (cLen == 6 && pLen == 5) {
// Can only be 1 or 2 - if so insert a / char
int y1 = int.parse(cText.substring(5, 6));
if (y1 < 1 || y1 > 2) {
// Replace char
cText = cText.substring(0, 5) + '/';
} else {
// Insert / char
cText = cText.substring(0, 5) + '/' + cText.substring(5, 6);
}
} else if (cLen == 7) {
// Can only be 1 or 2
int y1 = int.parse(cText.substring(6, 7));
if (y1 < 1 || y1 > 2) {
// Remove char
cText = cText.substring(0, 6);
}
} else if (cLen == 8) {
// Can only be 19 or 20
int y2 = int.parse(cText.substring(6, 8));
if (y2 < 19 || y2 > 20) {
// Remove char
cText = cText.substring(0, 7);
}
}
selectionIndex = cText.length;
return TextEditingValue(
text: cText,
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
要使用它,只需從 Textfield() 調用它,如下所示。 我還合並了兩個內置方法。 WhitelistingTextInputFormatter()只允許輸入數字和斜杠 (/) 字符, LengthLimitingTextInputFormatter()限制允許的字符數。 后者可以使用 TextField() 的 maxLength 參數來實現,但此處僅作為示例。 請注意,還有一個BlacklistingTextInputFormatter()可以按照您的預期進行。
TextField(
// maxLength: 10,
keyboardType: TextInputType.datetime,
controller: _controllerDOB,
focusNode: _focusNodeDOB,
decoration: InputDecoration(
hintText: 'DD/MM/YYYY',
counterText: '',
),
inputFormatters: [
WhitelistingTextInputFormatter(RegExp("[0-9/]")),
LengthLimitingTextInputFormatter(10),
_DateFormatter(),
],
),
您可以使用 flutter 制作的日期選擇器對話框。
DateTime _date = DateTime.now()
onPressed: () {
showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(2020),
lastDate: DateTime(2021),
).then((date) {
setState(() {
_date = date;
});
});
},
我發現您的代碼是一個很棒的實用程序,沒有任何依賴關系。 我冒昧地做了一些修改並想把它發回這里,因為我發現你的概念在 UI 上非常簡潔和輕量級。 要求是;
驗證非 31 天月份和閏年的日期。 模組非常簡單。
防止用戶在不希望的地方輸入“/”,這會使算法偏離軌道。 最簡單的解決方案是使
鍵盤類型:TextInputType.number,在TextField中
這非常適用於移動設備。 但是 Flutter 是跨平台的,對於帶有物理鍵盤的設備來說,這個解決方案並不是萬無一失的。 我嘗試了各種檢查和阻止,但只成功了一部分; 即,用戶仍然可以在日和月的數字之間輸入“/”。 我認為 KB 輸入和編程格式化程序之間存在內部延遲。
以下是 _DateFormatter 的修改代碼。 我使用 /// 概念來區分我的評論。 它們應該與原始的 // 評論一起閱讀。
class _DateFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue prevText, TextEditingValue currText) {
int selectionIndex;
String date;
String month;
int year;
// Get the previous and current input strings
String pText = prevText.text;
String cText = currText.text;
cText = cText.replaceAll("//", "/");
// Abbreviate lengths
int cLen = cText.length;
int pLen = pText.length;
/// ENTERING THE DATE
if (cLen == 1) {
/// User enters the first digit of the date. The first digit
// Can only be 0, 1, 2 or 3
if (int.parse(cText) > 3) {
// Remove char
cText = '';
}
} else if (cLen == 2 && pLen == 1) {
/// User has already entered a valid first digit of the date, now he
/// enters the second digit of the date; but
// Days cannot be greater than 31
int dd = int.parse(cText.substring(0, 2));
if (dd == 0 || dd > 31) {
// Remove char
cText = cText.substring(0, 1);
} else {
/// User has entered a valid date (between 1 and 31). So now,
// Add a / char
cText += '/';
}
/// ENTERING THE MONTH
} else if (cLen == 4) {
/// after entering a valid date and programmatic insertion of '/', now User has entered
/// the first digit of the Month. But, it
// Can only be 0 or 1
/// (and, not '/' either)
if (int.parse(cText.substring(3, 4)) > 1 || cText.substring(3, 4) == "/") {
// Remove char
cText = cText.substring(0, 3);
}
} else if (cLen == 5 && pLen == 4) {
int mm = int.parse(cText.substring(3, 5));
int dd = int.parse(cText.substring(0, 2));
/// User has entered the second digit of the Month, but the
// Month cannot be greater than 12
/// Also, that entry cannot be '/'
if ((mm == 0 || mm > 12|| cText.substring(3, 5) == "/") ||
/// If the date is 31, the month cannot be Apr, Jun, Sept or Nov
(dd == 31 && (mm == 02 || mm == 04 || mm == 06 || mm == 09 || mm == 11)) ||
/// If the date is greater than 29, the month cannot be Feb
/// (Leap years will be dealt with, when user enters the Year)
(dd > 29 && (mm == 02))) {
// Remove char
cText = cText.substring(0, 4);
}
else if (cText.length == 5) {
/// the Month entered is valid; so,
// Add a / char
cText += '/';
}
} else if ((cLen == 3 && pLen == 4) || (cLen == 6 && pLen == 7)) {
// Remove / char
cText = cText.substring(0, cText.length - 1);
} else if (cLen == 3 && pLen == 2) {
if (int.parse(cText.substring(2, 3)) > 1) {
// Replace char
cText = cText.substring(0, 2) + '/';
} else {
// Insert / char
cText =
cText.substring(0, pLen) + '/' + cText.substring(pLen, pLen + 1);
}
/// ENTERING THE YEAR
} else if (cLen == 6 && pLen == 5) {
// Can only be 1 or 2 - if so insert a / char
int y1 = int.parse(cText.substring(5, 6));
if (y1 < 1 || y1 > 2) {
// Replace char
/// i.e, add '/' after the 5th position
cText = cText.substring(0, 5) + '/';
} else {
// Insert / char
cText = cText.substring(0, 5) + '/' + cText.substring(5, 6);
}
} else if (cLen == 7) {
/// the first digit of year
// Can only be 1 or 2
int y1 = int.parse(cText.substring(6, 7));
if (y1 < 1 || y1 > 2) {
// Remove char
cText = cText.substring(0, 6);
}
} else if (cLen == 8) {
// Can only be 19 or 20
/// Also, there cannot be / typed by the user
String y2 = cText.substring(6, 8);
if (y2 != "19" && y2 != "20") {
// Remove char
cText = cText.substring(0, 7);
}
} else if (cLen == 9) {
/// There cannot be / typed by the user
if (cText.substring(8, 9) == "/") {
// Remove char
cText = cText.substring(0, 8);
}
} else if (cLen == 10) {
/// There cannot be / typed by the user
if (cText.substring(9, 10) == "/") {
// Remove char
cText = cText.substring(0, 9);
}
/// If the year entered is not a leap year but the date entered is February 29,
/// it will be advanced to the next valid date
date = cText.substring(0, 2);
month = cText.substring(3, 5);
year = int.parse(cText.substring(6, 10));
bool isNotLeapYear = !((year % 4 == 0) && (year % 100 != 0) ||
(year % 400 == 0));
if (isNotLeapYear && month == "02" && date == "29") {
cText = "01/03/$year";
}
}
selectionIndex = cText.length;
return TextEditingValue(
text: cText,
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
} // END OF class _DateFormatter
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.