【问题标题】:Allow only enter 3 decimal number flutter只允许输入 3 位小数颤动
【发布时间】:2020-11-12 09:44:28
【问题描述】:

我想强制用户只输入一个点和三个小数点。

我在下面找到了代码:

class NumberRemoveExtraDotFormatter extends TextInputFormatter {
  NumberRemoveExtraDotFormatter({this.decimalRange = 3}) : assert(decimalRange == null || decimalRange > 0);

  final int decimalRange;

  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    var nValue = newValue.text;
    var nSelection = newValue.selection;

    Pattern p = RegExp(r'(\d+\.?)|(\.?\d+)|(\.?)');
    nValue = p.allMatches(nValue).map<String>((Match match) => match.group(0)).join();

    if (nValue.startsWith('.')) {
      nValue = '0.';
    } else if (nValue.contains('.')) {
      if (nValue.substring(nValue.indexOf('.') + 1).length > decimalRange) {
        nValue = oldValue.text;
      } else {
        if (nValue.split('.').length > 2) {
          var split = nValue.split('.');
          nValue = split[0] + '.' + split[1];
        }
      }
    }

    nSelection = newValue.selection.copyWith(
      baseOffset: math.min(nValue.length, nValue.length + 1),
      extentOffset: math.min(nValue.length, nValue.length + 1),
    );

    return TextEditingValue(text: Utils.addCommad(nValue), selection: nSelection, composing: TextRange.empty);
  }
}

但问题是当用户输入超过 3 个小数点然后想要删除时,它不会。因为数字保存在 textformfield 中,并且它们会被删除,直到它们到达小数点后 3 位,并且当从输入光标的中间输入时也会跳转到结尾。

如果用户输入超过 3 个小数点,我还想将数字从右侧移出。

我怎样才能做到这一点?

【问题讨论】:

    标签: flutter


    【解决方案1】:

    如果你只是想强制用户只输入一个点和 3 个小数点,这可以工作。

    FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,3}'))
    

    根据您的评论:

    1. 如何添加千位分隔符?
    2. 移出号码不起作用。如果用户在小数点达到最大值时开始输入小数部分,我想将数字移出。例如当前值为 0.333,用户将光标设置在第 3 秒 (0.3|33) 并键入 2。那么值必须为 0.323。

    我们可以使用intl NumberFormat来格式化数字。

    这是我的代码,我没有进行彻底详细的测试。如果您发现任何错误,请指出。

    更新

    当输入最大分数为 0 的长数字时,将添加错误的数字。 => 这不取决于 maximumFractionDigits。它总是在发生。

    我认为 NumberFormat 有一些意外行为,我将其更改为自定义方法,现在它支持负数。

    class NumberInputFormatter extends TextInputFormatter {
      final int maximumFractionDigits;
    
      NumberInputFormatter({
        this.maximumFractionDigits = 3,
      }) : assert(maximumFractionDigits != null && maximumFractionDigits >= 0);
    
      @override
      TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
        var newText = newValue.text;
        var selectionOffset = newValue.selection.extent.offset;
        bool isNegative = false;
        if (newText.startsWith('-')) {
          newText = newText.substring(1);
          isNegative = true;
        }
        if (newText.isEmpty) {
          return newValue;
        }
        if (newText.indexOf('.') != newText.lastIndexOf('.')) {
          // inputted more than one dot.
          return oldValue;
        }
        if (newText.startsWith('.') && maximumFractionDigits > 0) {
          newText = '0$newText';
          selectionOffset += 1;
        }
        while (newText.length > 1 && !newText.startsWith('0.') && newText.startsWith('0')) {
          newText = newText.substring(1);
          selectionOffset -= 1;
        }
        if (_decimalDigitsOf(newText) > maximumFractionDigits) {
          // delete the extra digits.
          newText = newText.substring(0, newText.indexOf('.') + 1 + maximumFractionDigits);
        }
        if (newValue.text.length == oldValue.text.length - 1 &&
            oldValue.text.substring(newValue.selection.extentOffset, newValue.selection.extentOffset + 1) == ',') {
          // in this case, user deleted the thousands separator, we should delete the digit number before the cursor.
          newText = newText.replaceRange(newValue.selection.extentOffset - 1, newValue.selection.extentOffset, '');
          selectionOffset -= 1;
        }
        if (newText.endsWith('.')) {
          // in order to calculate the selection offset correctly, we delete the last decimal point first.
          newText = newText.replaceRange(newText.length - 1, newText.length, '');
        }
        int lengthBeforeFormat = newText.length;
        newText = _removeComma(newText);
        if (double.tryParse(newText) == null) {
          // invalid decimal number
          return oldValue;
        }
        newText = _addComma(newText);
        selectionOffset += newText.length - lengthBeforeFormat; // thousands separator newly added
        if (maximumFractionDigits > 0 && newValue.text.endsWith('.')) {
          // decimal point is at the last digit, we need to append it back.
          newText = '$newText.';
        }
        if (isNegative) {
          newText = '-$newText';
        }
        return TextEditingValue(
          text: newText,
          selection: TextSelection.collapsed(offset: min(selectionOffset, newText.length)),
        );
      }
    
      static int _decimalDigitsOf(String text) {
        var index = text?.indexOf('.') ?? -1;
        return index == -1 ? 0 : text.length - index - 1;
      }
    
      static String _addComma(String text) {
        StringBuffer sb = StringBuffer();
        var pointIndex = text.indexOf('.');
        String integerPart;
        String decimalPart;
        if (pointIndex >= 0) {
          integerPart = text.substring(0, pointIndex);
          decimalPart = text.substring(pointIndex);
        } else {
          integerPart = text;
          decimalPart = '';
        }
        List<String> parts = [];
        while (integerPart.length > 3) {
          parts.add(integerPart.substring(integerPart.length - 3));
          integerPart = integerPart.substring(0, integerPart.length - 3);
        }
        parts.add(integerPart);
        sb.writeAll(parts.reversed, ',');
        sb.write(decimalPart);
        return sb.toString();
      }
    
      static String _removeComma(String text) {
        return text.replaceAll(',', '');
      }
    }
    

    【讨论】:

    • 虽然这段代码可以解决问题,including an explanation 解决问题的方式和原因将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提出问题的人。请edit您的答案添加解释并说明适用的限制和假设。
    • 有两个问题: 1) 如何添加千位分隔符? 2) 移出号码不起作用。如果用户在小数点达到最大值时开始输入小数部分,我想将数字移出。例如当前值为 0.333,用户将光标设置在秒 3 (0.3|33) 并键入 2。那么值必须为 0.323。反正 tnx 回答:)
    • 这很好。但是一个错误是当输入长数字时,maximumFractionDigits 为 0,会添加错误的数字。当您在 iOS 中输入超过 maximumFractionDigits 时,退格也不起作用。
    • 并且maximumFractionDigits为0的用户仍然可以输入点。
    • 当输入最大分数为0的长数字时,将添加错误的数字。 => 这不取决于 maximumFractionDigits。它总是在发生。
    【解决方案2】:

    试试这个:

    FilteringTextInputFormatter(RegExp(r'(^[0-9]*(?:\.[0-9]{0,3})?$)'), allow: true),

    基本上,正则表达式会尝试匹配 0 次或多次出现的数字,然后是可选的小数,然后是小数点后最多 3 位数字。您也可以修改它以使用负值^(?:\-)?[0-9]*(?:\.[0-9]{0,3})?$

    【讨论】:

    • 我试试这个。但根本不工作。当我输入小数部分的第四个数字时。删除整个值
    • @BeHappy 是的。我在 dartpad.dev 上试过,也发生在我身上。一旦我有空闲时间,我可以修改答案。
    • 请查看我的评论以获取其他答案。
    【解决方案3】:

    完整代码在这里, (更新也可以通过光标更改数据)

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'dart:math' as math;
    
    class DecimalChecker extends TextInputFormatter {
      DecimalChecker({this.decimalRange = 3})
          : assert(decimalRange == null || decimalRange > 0);
    
      final int decimalRange;
    
      @override
      TextEditingValue formatEditUpdate(
          TextEditingValue oldValue, TextEditingValue newValue) {
        String valueTxt = newValue.text;
        TextSelection valueSet = newValue.selection;
        var newlength = newValue.text.length;
        var oldlength = oldValue.text.length;
        if (oldlength < newlength) {
          Pattern p = RegExp(r'(\d+\.?)|(\.?\d+)|(\.?)');
          valueTxt = p
              .allMatches(valueTxt)
              .map<String>((Match match) => match.group(0))
              .join();
          print("------>");
          if (valueTxt.startsWith('.')) {
            valueTxt = '0.';
          } else if (valueTxt.contains('.')) {
            if (valueTxt.substring(valueTxt.indexOf('.') + 1).length >
                decimalRange) {
              valueTxt = oldValue.text;
            } else {
              if (valueTxt.split('.').length > 2) {
                List<String> split = valueTxt.split('.');
                valueTxt = split[0] + '.' + split[1];
              }
            }
          }
    
          valueSet = newValue.selection.copyWith(
            baseOffset: math.min(valueTxt.length, valueTxt.length + 1),
            extentOffset: math.min(valueTxt.length, valueTxt.length + 1),
          );
    
          return TextEditingValue(
              text: valueTxt, selection: valueSet, composing: TextRange.empty);
        } else {
          return TextEditingValue(
              text: valueTxt, selection: valueSet, composing: TextRange.empty);
        }
      }
    }
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'My Decimal Check App'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      TextEditingController numberController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0),
                  child: TextField(
                    controller: numberController,
                    keyboardType: TextInputType.numberWithOptions(decimal: true),
                    inputFormatters: [DecimalChecker()],
                    decoration: InputDecoration(
                      hintText: "Please enter Number",
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    【讨论】:

    • 当用户在十进制部分输入更多数字时,退格不起作用。
    • 有2个问题:1)如何添加Utils.addComma(千位分隔符)? 2) 移出号码不起作用。如果用户在小数点达到最大值时开始输入小数部分,我想将数字移出。例如当前值为 0.333,用户将光标设置在秒 3 (0.3|33) 并键入 2。那么值必须为 0.323。反正 tnx 回答:)
    • 再次检查,我也更新了我的代码,现在你通过光标改变值..
    猜你喜欢
    • 2020-01-09
    • 2017-08-30
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多