You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何使用Input Formatter将用户输入格式化为MM/DD/YYYY格式

MM/DD/YYYY格式输入实时校验实现方案

需求说明

  • 输入过程中自动将内容格式化为MM/DD/YYYY格式
  • 实时校验月、日、年合法性,非法输入不会被录入,例如输入月份14时,末尾的4会被自动拦截
  • 现有代码月份格式化功能正常,仅需优化日期相关校验逻辑

原始问题代码

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;

    //Date

    if (cLen == 1) {
      // Can only be 0, 1, 2 or 3
      if (int.parse(cText) > 1) {
        // Remove char
        cText = '';
      }
    } else if (cLen == 2 && pLen == 1) {
      // Can only be 0, 1, 2 or 3
      if (int.parse(cText) == 0 || int.parse(cText) > 12) {
        // Remove char
        cText = cText.substring(0, 1);
      } else {
        cText += '/';
      }
    } else if (cLen == 2 && pLen == 3) {
      cText = cText.substring(0, 1);
    } else if (cLen == 2 && pLen == 4) {
      cText += '/';
    } else if (cLen == 3 && pLen == 2) {
      cText = cText.substring(0, 2) + '/' + cText.substring(2, 3);
    }

    // Month
    else if (cLen == 4 && pLen == 2) {
      print('s');
      if (int.parse(cText.substring(3, 4)) > 3) {
        cText = cText;
      }
    } else if (cLen == 4 && pLen == 3) {
      if (int.parse(cText.substring(3, 4)) > 3) {
        cText = cText.substring(0, 3);
      }

      // // Can only be 0, 1, 2 or 3
      // if (int.parse(cText) > 3) {
      //   // Remove char
      //   cText = '
      // }
      // // 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);
      }
    }

    //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
        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;
    print(selectionIndex);
    return TextEditingValue(
      text: cText,
      selection: TextSelection.collapsed(offset: selectionIndex),
    );
  }
}

优化后完整代码

优化点说明:

  • 理清了月份、日期、年份的分段校验逻辑,修正了原代码中日期和月份校验混淆的问题
  • 日期段支持01~31的合法性校验,非法输入自动拦截
  • 优化了退格场景的处理,光标不会错位
  • 年份默认支持1900~2099区间,可自行修改校验规则调整范围
class _DateFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue prevText, TextEditingValue currText) {
    String pText = prevText.text;
    String cText = currText.text.replaceAll('/', '');
    int cLen = cText.length;
    int pLen = pText.replaceAll('/', '').length;
    StringBuffer formatted = StringBuffer();

    // 处理退格场景
    if (cLen < pLen) {
      int selectionOffset = currText.selection.baseOffset;
      if (selectionOffset > 0 && pText[selectionOffset - 1] == '/') {
        selectionOffset--;
      }
      return TextEditingValue(
        text: pText.substring(0, selectionOffset),
        selection: TextSelection.collapsed(offset: selectionOffset),
      );
    }

    // 月份处理
    if (cLen >= 1) {
      int firstMonthDigit = int.parse(cText[0]);
      if (firstMonthDigit > 1) {
        // 月份第一位只能是0或1
        cText = cText.substring(0, 0);
        cLen = 0;
      } else {
        formatted.write(cText[0]);
        if (cLen >= 2) {
          int month = int.parse(cText.substring(0, 2));
          if (month == 0 || month > 12) {
            // 月份非法,只保留第一位
            cText = cText.substring(0, 1);
            cLen = 1;
          } else {
            formatted.write(cText[1]);
            formatted.write('/');
          }
        }
      }
    }

    // 日期处理
    if (cLen >= 3) {
      int firstDayDigit = int.parse(cText[2]);
      if (firstDayDigit > 3) {
        // 日期第一位只能是0、1、2、3
        cText = cText.substring(0, 2);
        cLen = 2;
      } else {
        formatted.write(cText[2]);
        if (cLen >= 4) {
          int day = int.parse(cText.substring(2, 4));
          if (day == 0 || day > 31) {
            // 日期非法,只保留第一位
            cText = cText.substring(0, 3);
            cLen = 3;
          } else {
            formatted.write(cText[3]);
            formatted.write('/');
          }
        }
      }
    }

    // 年份处理
    if (cLen >= 5) {
      int firstYearDigit = int.parse(cText[4]);
      if (firstYearDigit < 1 || firstYearDigit > 2) {
        // 年份第一位只能是1或2
        cText = cText.substring(0, 4);
        cLen = 4;
      } else {
        formatted.write(cText[4]);
        if (cLen >= 6) {
          int secondYearDigit = int.parse(cText[5]);
          if ((firstYearDigit == 1 && secondYearDigit != 9) || 
              (firstYearDigit == 2 && secondYearDigit != 0)) {
            // 年份前两位只能是19或20
            cText = cText.substring(0, 5);
            cLen = 5;
          } else {
            formatted.write(cText[5]);
            // 年份后两位无强制校验,直接拼接
            if (cLen >=7) formatted.write(cText[6]);
            if (cLen >=8) formatted.write(cText[7]);
          }
        }
      }
    }

    String result = formatted.toString();
    return TextEditingValue(
      text: result,
      selection: TextSelection.collapsed(offset: result.length),
    );
  }
}

使用方式

直接将该 formatter 配置给 TextField 即可:

TextField(
  inputFormatters: [_DateFormatter()],
  keyboardType: TextInputType.number,
  decoration: InputDecoration(hintText: 'MM/DD/YYYY'),
)

内容的提问来源于stack exchange,提问作者imgkl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 23:06:03