簡體   English   中英

Flutter TextField 輸入驗證日期

[英]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 上非常簡潔和輕量級。 要求是;

  1. 驗證非 31 天月份和閏年的日期。 模組非常簡單。

  2. 防止用戶在不希望的地方輸入“/”,這會使算法偏離軌道。 最簡單的解決方案是使

鍵盤類型: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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM