Flutter TextField 输入验证日期

问题描述 投票:0回答:5

我正在尝试编写一个日期输入控件,它接受像 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 中可能有更简单的方法来做到这一点?

date validation flutter dart textfield
5个回答
12
投票

我找到了解决日期验证输入所需的内容。它并不完美,但对于我想做的事情来说已经足够好了。我所需要的只是查看 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() 可以按照您的预期进行操作。

WhitelistingTextInputFormatter 已删除,请使用 FilteringTextInputFormatter.allow(RegExp("[0-9-]")), 替换,如果您想更改分割符号(当前为 "/"),请添加它到 RegExp(....).

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(),
  ],
),

1
投票

我找到了一个解决方案,但不是优化的解决方案,但它几乎涵盖了所有场景,

  1. 添加字段时使用正斜杠
  2. 删除清除字段上的正斜杠
  3. 在编辑处理之间...等

    class CustomDateTextFormatter extends TextInputFormatter {
      @override
      TextEditingValue formatEditUpdate(
          TextEditingValue oldValue, TextEditingValue newValue) {
        var text = _format(newValue.text, '/', oldValue);
        return newValue.copyWith(
            text: text, selection: _updateCursorPosition(text, oldValue));
      }
    }

String _format(String value, String seperator, TextEditingValue old) {
  var finalString = '';
  var dd = '';
  var mm = '';
  var yyy = '';
  var oldVal = old.text;
  print('<------------------------- start---------------------------->');
  print('oldVal -> $oldVal');
  print('value -> $value');
  var temp_oldVal = oldVal;
  var temp_value = value;
  if (!oldVal.contains(seperator) ||
      oldVal.isEmpty ||
      seperator.allMatches(oldVal).length < 2) {
    oldVal += '///';
  }
  if (!value.contains(seperator) || _backSlashCount(value) < 2) {
    value += '///';
  }
  var splitArrOLD = oldVal.split(seperator);
  var splitArrNEW = value.split(seperator);
  print('----> splitArrOLD: $splitArrOLD');
  print('----> splitArrNEW: $splitArrNEW');
  for (var i = 0; i < 3; i++) {
    splitArrOLD[i] = splitArrOLD[i].toString().trim();
    splitArrNEW[i] = splitArrNEW[i].toString().trim();
  }
  // block erasing
  if ((splitArrOLD[0].isNotEmpty &&
      splitArrOLD[2].isNotEmpty &&
      splitArrOLD[1].isEmpty &&
      temp_value.length < temp_oldVal.length &&
      splitArrOLD[0] == splitArrNEW[0] &&
      splitArrOLD[2].toString().trim() ==
          splitArrNEW[1].toString().trim()) ||
      (_backSlashCount(temp_oldVal) > _backSlashCount(temp_value) &&
          splitArrNEW[1].length > 2) ||
      (splitArrNEW[0].length > 2 && _backSlashCount(temp_oldVal) == 1) ||
      (_backSlashCount(temp_oldVal) == 2 &&
          _backSlashCount(temp_value) == 1 &&
          splitArrNEW[0].length > splitArrOLD[0].length)) {
    finalString = temp_oldVal; // making the old date as it is
    print('blocked finalString : $finalString ');
  } else {
    if (splitArrNEW[0].length > splitArrOLD[0].length) {
      if (splitArrNEW[0].length < 3) {
        dd = splitArrNEW[0];
      } else {
        for (var i = 0; i < 2; i++) {
          dd += splitArrNEW[0][i];
        }
      }
      if (dd.length == 2 && !dd.contains(seperator)) {
        dd += seperator;
      }
    } else if (splitArrNEW[0].length == splitArrOLD[0].length) {
      print('splitArrNEW[0].length == 2');
      if (oldVal.length > value.length && splitArrNEW[1].isEmpty) {
        dd = splitArrNEW[0];
      } else {
        dd = splitArrNEW[0] + seperator;
      }
    } else if (splitArrNEW[0].length < splitArrOLD[0].length) {
      print('splitArrNEW[0].length < splitArrOLD[0].length');
      if (oldVal.length > value.length &&
          splitArrNEW[1].isEmpty &&
          splitArrNEW[0].isNotEmpty) {
        dd = splitArrNEW[0];
      } else if (temp_oldVal.length > temp_value.length &&
          splitArrNEW[0].isEmpty &&
          _backSlashCount(temp_value) == 2) {
        dd += seperator;
      } else {
        if (splitArrNEW[0].isNotEmpty) {
          dd = splitArrNEW[0] + seperator;
        }
      }
    }
    print('dd value --> $dd');

    if (dd.isNotEmpty) {
      finalString = dd;
      if (dd.length == 2 &&
          !dd.contains(seperator) &&
          oldVal.length < value.length &&
          splitArrNEW[1].isNotEmpty) {
        if (seperator.allMatches(dd).isEmpty) {
          finalString += seperator;
        }
      } else if (splitArrNEW[2].isNotEmpty &&
          splitArrNEW[1].isEmpty &&
          temp_oldVal.length > temp_value.length) {
        if (seperator.allMatches(dd).isEmpty) {
          finalString += seperator;
        }
      } else if (oldVal.length < value.length &&
          (splitArrNEW[1].isNotEmpty || splitArrNEW[2].isNotEmpty)) {
        if (seperator.allMatches(dd).isEmpty) {
          finalString += seperator;
        }
      }
    } else if (_backSlashCount(temp_oldVal) == 2 && splitArrNEW[1].isNotEmpty) {
      dd += seperator;
    }
    print('finalString after dd=> $finalString');
    if (splitArrNEW[0].length == 3 && splitArrOLD[1].isEmpty) {
      mm = splitArrNEW[0][2];
    }

    if (splitArrNEW[1].length > splitArrOLD[1].length) {
      print('splitArrNEW[1].length > splitArrOLD[1].length');
      if (splitArrNEW[1].length < 3) {
        mm = splitArrNEW[1];
      } else {
        for (var i = 0; i < 2; i++) {
          mm += splitArrNEW[1][i];
        }
      }
      if (mm.length == 2 && !mm.contains(seperator)) {
        mm += seperator;
      }
    } else if (splitArrNEW[1].length == splitArrOLD[1].length) {
      print('splitArrNEW[1].length = splitArrOLD[1].length');
      if (splitArrNEW[1].isNotEmpty) {
        mm = splitArrNEW[1];
      }
    } else if (splitArrNEW[1].length < splitArrOLD[1].length) {
      print('splitArrNEW[1].length < splitArrOLD[1].length');
      if (splitArrNEW[1].isNotEmpty) {
        mm = splitArrNEW[1] + seperator;
      }
    }
    print('mm value --> $mm');

    if (mm.isNotEmpty) {
      finalString += mm;
      if (mm.length == 2 && !mm.contains(seperator)) {
        if (temp_oldVal.length < temp_value.length) {
          finalString += seperator;
        }
      }
    }
    print('finalString after mm=> $finalString');
    if (splitArrNEW[1].length == 3 && splitArrOLD[2].isEmpty) {
      yyy = splitArrNEW[1][2];
    }

    if (splitArrNEW[2].length > splitArrOLD[2].length) {
      print('splitArrNEW[2].length > splitArrOLD[2].length');
      if (splitArrNEW[2].length < 5) {
        yyy = splitArrNEW[2];
      } else {
        for (var i = 0; i < 4; i++) {
          yyy += splitArrNEW[2][i];
        }
      }
    } else if (splitArrNEW[2].length == splitArrOLD[2].length) {
      print('splitArrNEW[2].length == splitArrOLD[2].length');
      if (splitArrNEW[2].isNotEmpty) {
        yyy = splitArrNEW[2];
      }
    } else if (splitArrNEW[2].length < splitArrOLD[2].length) {
      print('splitArrNEW[2].length < splitArrOLD[2].length');
      yyy = splitArrNEW[2];
    }
    print('yyy value --> $yyy');

    if (yyy.isNotEmpty) {
      if (_backSlashCount(finalString) < 2) {
        if (splitArrNEW[0].isEmpty && splitArrNEW[1].isEmpty) {
          finalString = seperator + seperator + yyy;
        } else {
          finalString = finalString + seperator + yyy;
        }
      } else {
        finalString += yyy;
      }
    } else {
      if (_backSlashCount(finalString) > 1 && oldVal.length > value.length) {
        var valueUpdate = finalString.split(seperator);
        finalString = valueUpdate[0] + seperator + valueUpdate[1];
      }
    }

    print('finalString after yyyy=> $finalString');
  }

  print('<------------------------- finish---------------------------->');

  return finalString;
}

TextSelection _updateCursorPosition(String text, TextEditingValue oldValue) {
  var endOffset = max(
    oldValue.text.length - oldValue.selection.end,
    0,
  );
  var selectionEnd = text.length - endOffset;
  print('My log ---> $selectionEnd');
  return TextSelection.fromPosition(TextPosition(offset: selectionEnd));
}

int _backSlashCount(String value) {
  return '/'.allMatches(value).length;
}

我们可以使用我们的自定义格式化程序,如下所示的 inputFormatters

TextField(
  // maxLength: 10,
  keyboardType: TextInputType.datetime,
  controller: _controllerDOB,
  focusNode: _focusNodeDOB,
  decoration: InputDecoration(
    hintText: 'DD/MM/YYYY',
    counterText: '',
  ),
  inputFormatters: [
    WhitelistingTextInputFormatter(RegExp("[0-9/]")),
    LengthLimitingTextInputFormatter(10),
    CustomDateTextFormatter(),
  ],
),
    

尝试这个解决方案。


0
投票

我发现您的代码是一个很棒的实用程序,没有任何依赖项。我冒昧地做了一些修改,并考虑将其发布回此处,因为我发现您的概念在用户界面上非常简洁和轻量级。要求是;

  1. 验证非 31 天月份和闰年的日期。这些模组非常简单。

  2. 防止用户在不需要的地方输入“/”,这会使算法偏离轨道。最简单的解决方案是制作

keyboardType: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

0
投票

这将满足您提出的要求

class DateInputFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    String date = newValue.text;

    if (date.length >= oldValue.text.length) {
      // adding slashes while typing
      if (date.length == 2) {
        date += '/';
      } else if (date.length == 5) {
        date += '/';
      }
    }

    return TextEditingValue(
      text: date,
      selection: TextSelection.collapsed(offset: date.length),
    );
  }
}

-1
投票

您可以使用flutter制作的日期选择器对话框。

DateTime _date = DateTime.now()


onPressed: () {
  showDatePicker(
    context: context,
    initialDate: _date,
    firstDate: DateTime(2020),
    lastDate: DateTime(2021),
    ).then((date) {
      setState(() {
    _date = date;
    });
  });
},
© www.soinside.com 2019 - 2024. All rights reserved.