限制Android EditText中的小数位数。

152

我正在尝试编写一个帮助你管理财务的应用程序。我使用了一个EditText字段,用户可以在其中指定金额。

我将inputType设置为numberDecimal,这很好用,但是这允许人们输入像123.122这样不适合货币的数字。

有没有办法限制小数点后的数字位数为两位?


当编辑文本失去焦点时,您可以编写正则表达式并验证其内容。 - blindstuff
我找到了InputFilter接口,它似乎可以满足我的需求http://developer.android.com/reference/android/text/method/NumberKeyListener.html#filter%28java.lang.CharSequence,%20int,%20int,%20android.text.Spanned,%20int,%20int%29,但是我需要实现的`filter`方法对我来说相当困惑。是否已经有人编写了这样的过滤器并知道如何使用它? - Konstantin Weitz
任何建议的解决方案是否适用于RTL语言环境?据我所知,它们不会起作用... - Nick
40个回答

128

更优雅的方法是使用正则表达式(regex),如下所示:

public class DecimalDigitsInputFilter implements InputFilter {

Pattern mPattern;

public DecimalDigitsInputFilter(int digitsBeforeZero,int digitsAfterZero) {
    mPattern=Pattern.compile("[0-9]{0," + (digitsBeforeZero-1) + "}+((\\.[0-9]{0," + (digitsAfterZero-1) + "})?)||(\\.)?");
}

@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {

        Matcher matcher=mPattern.matcher(dest);       
        if(!matcher.matches())
            return "";
        return null;
    }

}

使用它的方法如下:

editText.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(5,2)});

39
你好,仍然有一些特殊情况没有被处理好。例如,当我输入2.45后,我往往会“将光标移动到文本的最前面”。我希望得出文本12.45,但它不允许这样做。 - Cheok Yan Cheng
4
用户输入小数点后2位数字后,不允许更改小数点前的数字。 - Gaurav Singla
11
很好的解决方案,但并不完全正确。匹配器不应该检查dest,而应该检查edittext中的值(dest.subSequence(0, dstart) + source.subSequence(start, end) + dest.subSequence(dend, dest.length()))。 - Mihaela Romanca
7
Mihaela是正确的,我们应该将匹配对象与尝试填充edittext的字符串进行匹配。我找到了如何连接另一个类似问题的字符序列,就像这样:CharSequence match = TextUtils.concat(dest.subSequence(0, dstart), source.subSequence(start, end), dest.subSequence(dend, dest.length())); 但之后正则表达式会出问题,所以我将其改为:“^\d{1," + digitsBeforeZero + "}(\.\d{0," + digitsAfterZero + "})?$”。但之后你还需要验证一下,因为使用这个正则表达式,“1.”是有效的,但我们需要它这样,以便可以键入句点。 - dt0
6
如何使逗号(,)也起作用?世界上一些地区使用逗号来表示小数(例如:123,45)。 - Andrew
显示剩余9条评论

70

不使用正则表达式的更简单解决方案:

import android.text.InputFilter;
import android.text.Spanned;

/**
 * Input filter that limits the number of decimal digits that are allowed to be
 * entered.
 */
public class DecimalDigitsInputFilter implements InputFilter {

  private final int decimalDigits;

  /**
   * Constructor.
   * 
   * @param decimalDigits maximum decimal digits
   */
  public DecimalDigitsInputFilter(int decimalDigits) {
    this.decimalDigits = decimalDigits;
  }

  @Override
  public CharSequence filter(CharSequence source,
      int start,
      int end,
      Spanned dest,
      int dstart,
      int dend) {


    int dotPos = -1;
    int len = dest.length();
    for (int i = 0; i < len; i++) {
      char c = dest.charAt(i);
      if (c == '.' || c == ',') {
        dotPos = i;
        break;
      }
    }
    if (dotPos >= 0) {

      // protects against many dots
      if (source.equals(".") || source.equals(","))
      {
          return "";
      }
      // if the text is entered before the dot
      if (dend <= dotPos) {
        return null;
      }
      if (len - dotPos > decimalDigits) {
        return "";
      }
    }

    return null;
  }

}

使用方法:

editText.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(2)});

我如何防止将非数字字符(例如'a')插入字符串中? - Konstantin Weitz
4
这个: <EditText ... android:inputType="number" /> - peceps
1
应该是:editText.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(2)}); - frak
6
这并未处理这样一种情况:我先输入“999”,然后在第一个“9”后插入小数点。 - Jake Stoeffler
1
谢谢,这很有用。我们还可以使用长度过滤器来限制小数点前的数字,就像这样。Kotlin:edtAnyAmount.filters = arrayOf<InputFilter>(InputFilter.LengthFilter(7),DecimalDigitsInputFilter(2)) - Faldu Jaldeep
显示剩余6条评论

41
这个InputFilter的实现解决了这个问题。
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.DigitsKeyListener;

public class MoneyValueFilter extends DigitsKeyListener {
    public MoneyValueFilter() {
        super(false, true);
    }

    private int digits = 2;

    public void setDigits(int d) {
        digits = d;
    }

    @Override
    public CharSequence filter(CharSequence source, int start, int end,
            Spanned dest, int dstart, int dend) {
        CharSequence out = super.filter(source, start, end, dest, dstart, dend);

        // if changed, replace the source
        if (out != null) {
            source = out;
            start = 0;
            end = out.length();
        }

        int len = end - start;

        // if deleting, source is empty
        // and deleting can't break anything
        if (len == 0) {
            return source;
        }

        int dlen = dest.length();

        // Find the position of the decimal .
        for (int i = 0; i < dstart; i++) {
            if (dest.charAt(i) == '.') {
                // being here means, that a number has
                // been inserted after the dot
                // check if the amount of digits is right
                return (dlen-(i+1) + len > digits) ? 
                    "" :
                    new SpannableStringBuilder(source, start, end);
            }
        }

        for (int i = start; i < end; ++i) {
            if (source.charAt(i) == '.') {
                // being here means, dot has been inserted
                // check if the amount of digits is right
                if ((dlen-dend) + (end-(i + 1)) > digits)
                    return "";
                else
                    break;  // return new SpannableStringBuilder(source, start, end);
            }
        }

        // if the dot is after the inserted part,
        // nothing can break
        return new SpannableStringBuilder(source, start, end);
    }
}

请问我们需要返回SpannableStringBuilder而不是null的原因吗?我用null测试了一下,也能正常工作。另外,我们需要继承DigitsKeyListener吗?因为使用android:inputType="numberDecimal"将执行所有的“0123456789.”数字限制。 - Cheok Yan Cheng

36

这是一个示例InputFilter,它只允许小数点前最多4位数字,小数点后最多1位数字。

EditText允许的值:555.2555.2

EditText阻止的值:55555.2055.2555.42

        InputFilter filter = new InputFilter() {
        final int maxDigitsBeforeDecimalPoint=4;
        final int maxDigitsAfterDecimalPoint=1;

        @Override
        public CharSequence filter(CharSequence source, int start, int end,
                Spanned dest, int dstart, int dend) {
                StringBuilder builder = new StringBuilder(dest);
                builder.replace(dstart, dend, source
                        .subSequence(start, end).toString());
                if (!builder.toString().matches(
                        "(([1-9]{1})([0-9]{0,"+(maxDigitsBeforeDecimalPoint-1)+"})?)?(\\.[0-9]{0,"+maxDigitsAfterDecimalPoint+"})?"

                        )) {
                    if(source.length()==0)
                        return dest.subSequence(dstart, dend);
                    return "";
                }

            return null;

        }
    };

    mEdittext.setFilters(new InputFilter[] { filter });

不允许输入句点“.” - AmiNadimi

31

我不喜欢其他解决方案,所以我创建了自己的解决方案。 使用这个解决方案,在小数点前面不能输入超过MAX_BEFORE_POINT位数字,在小数点后面的小数位数也不能超过MAX_DECIMAL。

你只需要输入正确数量的数字,没有其他影响! 另外,如果你输入 "." ,它会自动变成"0."。

  1. Set the EditText in the layout to:

    android:inputType="numberDecimal"

  2. Add the Listener in your onCreate. If you want modify the number of digits before and after the point edit the call to PerfectDecimal(str, NUMBER_BEFORE_POINT, NUMBER_DECIMALS), here is set to 3 and 2

    EditText targetEditText = (EditText)findViewById(R.id.targetEditTextLayoutId);
    
    targetEditText.addTextChangedListener(new TextWatcher() {
      public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}
    
      public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}
    
      public void afterTextChanged(Editable arg0) {
        String str = targetEditText.getText().toString();
        if (str.isEmpty()) return;
        String str2 = PerfectDecimal(str, 3, 2);
    
        if (!str2.equals(str)) {
            targetEditText.setText(str2);
            targetEditText.setSelection(str2.length());
        }
      }
    });
    
  3. Include this Funcion:

    public String PerfectDecimal(String str, int MAX_BEFORE_POINT, int MAX_DECIMAL){
      if(str.charAt(0) == '.') str = "0"+str;
      int max = str.length();
    
      String rFinal = "";
      boolean after = false;
      int i = 0, up = 0, decimal = 0; char t;
      while(i < max){
        t = str.charAt(i);
        if(t != '.' && after == false){
            up++;
            if(up > MAX_BEFORE_POINT) return rFinal;
        }else if(t == '.'){
            after = true;
        }else{
            decimal++;
            if(decimal > MAX_DECIMAL)
                return rFinal;
        }
        rFinal = rFinal + t;
        i++;
      }return rFinal;
    }
    

完成了!


1
干得好。当其他方法对我无效时,这个方法非常有效。 - Bear
1
这应该是被接受的答案。它非常完美。所有条件都得到满足。 - Nayan
1
在所有得到高票的答案中,这个答案实际上对我有用。 - Rohit Mandiwal
干得好!我尝试了所有的组合,看起来它运行得非常好。谢谢你。 - akelec
你应该移除 if(up > MAX_BEFORE_POINT) return rFinal; 这段代码。有一种情况是,如果你回到 EditText 的开头并开始输入,它将在 MAX_BEFORE_POINT 个字符后发生。这会删除之前存在的小数位。 - Tsuharesu
显示剩余4条评论

30

我对@Pinhassi的解决方案进行了修正。它处理了以下情况:

1.您可以将光标移动到任何位置

2.负号处理

3.digitsbefore = 2且digitsafter = 4,如果您输入12.4545。然后,如果您想删除“。”,它将不允许。

public class DecimalDigitsInputFilter implements InputFilter {
    private int mDigitsBeforeZero;
    private int mDigitsAfterZero;
    private Pattern mPattern;

    private static final int DIGITS_BEFORE_ZERO_DEFAULT = 100;
    private static final int DIGITS_AFTER_ZERO_DEFAULT = 100;

    public DecimalDigitsInputFilter(Integer digitsBeforeZero, Integer digitsAfterZero) {
    this.mDigitsBeforeZero = (digitsBeforeZero != null ? digitsBeforeZero : DIGITS_BEFORE_ZERO_DEFAULT);
    this.mDigitsAfterZero = (digitsAfterZero != null ? digitsAfterZero : DIGITS_AFTER_ZERO_DEFAULT);
    mPattern = Pattern.compile("-?[0-9]{0," + (mDigitsBeforeZero) + "}+((\\.[0-9]{0," + (mDigitsAfterZero)
        + "})?)||(\\.)?");
    }

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
    String replacement = source.subSequence(start, end).toString();
    String newVal = dest.subSequence(0, dstart).toString() + replacement
        + dest.subSequence(dend, dest.length()).toString();
    Matcher matcher = mPattern.matcher(newVal);
    if (matcher.matches())
        return null;

    if (TextUtils.isEmpty(source))
        return dest.subSequence(dstart, dend);
    else
        return "";
    }
}

我认为这应该是完美的解决方案,让我的一天变得完美。谢谢。 - Pratik Butani
@android_dev 为什么我无法输入负值(减号)? - user924
如果您设置了 android:inputType="number"android:inputType="numberDecimal",它将不允许输入负号,而 android:digits="0123456789.-" 也无法解决此问题。 - user924
我尝试了 android:inputType ="numbersigned",现在我可以输入负号,但是我无法输入小数点 .android:digits="0123456789." 也没有帮助! - user924
终于我找到了一个解决方案 android:digits="0123456789.-"android:inputType="numberDecimal|numberSigned" - user924

20
我通过以下方式使用TextWatcher实现了这一点。
final EditText et = (EditText) findViewById(R.id.EditText1);
int count = -1;
et.addTextChangedListener(new TextWatcher() {
    public void onTextChanged(CharSequence arg0, int arg1, int arg2,int arg3) {             

    }
    public void beforeTextChanged(CharSequence arg0, int arg1,int arg2, int arg3) {             

    }

    public void afterTextChanged(Editable arg0) {
        if (arg0.length() > 0) {
            String str = et.getText().toString();
            et.setOnKeyListener(new OnKeyListener() {
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if (keyCode == KeyEvent.KEYCODE_DEL) {
                        count--;
                        InputFilter[] fArray = new InputFilter[1];
                        fArray[0] = new InputFilter.LengthFilter(100);
                        et.setFilters(fArray);
                        //change the edittext's maximum length to 100. 
                        //If we didn't change this the edittext's maximum length will
                        //be number of digits we previously entered.
                    }
                    return false;
                }
            });
            char t = str.charAt(arg0.length() - 1);
            if (t == '.') {
                count = 0;
            }
            if (count >= 0) {
                if (count == 2) {                        
                    InputFilter[] fArray = new InputFilter[1];
                    fArray[0] = new InputFilter.LengthFilter(arg0.length());
                    et.setFilters(fArray);
                    //prevent the edittext from accessing digits 
                    //by setting maximum length as total number of digits we typed till now.
                }
                count++;
            }
        }
    }
});

这个解决方案将不允许用户在小数点后输入超过两位数字。同时,您可以在小数点前输入任意数量的数字。希望这能有所帮助。谢谢。


抱歉晚了。不要忘记用“-1”初始化“count”。只有这样才能正常工作。int count = -1; - Gunaseelan
Gunaseelan - 我尝试了上面的代码,它运行良好。但是当我删除已输入的文本并重新开始输入时,它只能输入一个数字,有什么解决办法吗? - Siva K
@SivaK 没门,朋友。如果您删除然后重新输入,它将接受最少100个数字。我不知道您如何访问此“listener”。无论如何,请查看我的博客文章。您可能会得到一些想法。如果您无法理解,请告诉我。我将帮助您解决此问题。 - Gunaseelan
1
@Gunaseelan,感谢您的解决方案。但是它存在一些错误。例如,当我删除第二个小数点时,无法再次输入它(我必须删除所有小数点才能再次输入)。此外,在删除整个条目后,重新输入时会出现一些奇怪的限制。 - akelec
你检查过从缓冲区复制粘贴的解决方案了吗?我认为这样的逻辑很容易被破坏。 - user924
显示剩余2条评论

18

我设计的InputFilter允许您配置小数点前后的数字位数。此外,它禁止前导零。

public class DecimalDigitsInputFilter implements InputFilter
{
    Pattern pattern;

    public DecimalDigitsInputFilter(int digitsBeforeDecimal, int digitsAfterDecimal)
    {
        pattern = Pattern.compile("(([1-9]{1}[0-9]{0," + (digitsBeforeDecimal - 1) + "})?||[0]{1})((\\.[0-9]{0," + digitsAfterDecimal + "})?)||(\\.)?");
    }

    @Override public CharSequence filter(CharSequence source, int sourceStart, int sourceEnd, Spanned destination, int destinationStart, int destinationEnd)
    {
        // Remove the string out of destination that is to be replaced.
        String newString = destination.toString().substring(0, destinationStart) + destination.toString().substring(destinationEnd, destination.toString().length());

        // Add the new string in.
        newString = newString.substring(0, destinationStart) + source.toString() + newString.substring(destinationStart, newString.length());

        // Now check if the new string is valid.
        Matcher matcher = pattern.matcher(newString);

        if(matcher.matches())
        {
            // Returning null indicates that the input is valid.
            return null;
        }

        // Returning the empty string indicates the input is invalid.
        return "";
    }
}

// To use this InputFilter, attach it to your EditText like so:
final EditText editText = (EditText) findViewById(R.id.editText);

EditText.setFilters(new InputFilter[]{new DecimalDigitsInputFilter(4, 4)});

不错的解决方案!它对我有用,但我想禁止前导句点(点)。例如,“.123”是不允许的序列。如何实现这一点? - ibogolyubskiy
此解决方案允许在开头打点。 - user924
这是最好的解决方案!因为它限制了小数点后的位数。 - Himanshu Yadav Anshu

16

要求小数点后有2位数字,小数点前的数字没有限制。因此,解决方案应为:

public class DecimalDigitsInputFilter implements InputFilter {

    Pattern mPattern;

    public DecimalDigitsInputFilter() {
        mPattern = Pattern.compile("[0-9]*+((\\.[0-9]?)?)||(\\.)?");
    }

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        Matcher matcher = mPattern.matcher(dest);
        if (!matcher.matches())
            return "";
        return null;
    }
}

然后将其用作,

mEditText.setFilters(new InputFilter[]{new DecimalDigitsInputFilter()});

感谢@Pinhassi的启发。


2
好的...工作正常 - jojo
1
简单明了的答案,它按预期工作。 - Himanshi Thakur
但是它在输入一个小数后就将其视为输入。 - Himanshu Yadav Anshu

12

我的解决方案简单且完美地运作!

public class DecimalInputTextWatcher implements TextWatcher {

private String mPreviousValue;
private int mCursorPosition;
private boolean mRestoringPreviousValueFlag;
private int mDigitsAfterZero;
private EditText mEditText;

public DecimalInputTextWatcher(EditText editText, int digitsAfterZero) {
    mDigitsAfterZero = digitsAfterZero;
    mEditText = editText;
    mPreviousValue = "";
    mRestoringPreviousValueFlag = false;
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    if (!mRestoringPreviousValueFlag) {
        mPreviousValue = s.toString();
        mCursorPosition = mEditText.getSelectionStart();
    }
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}

@Override
public void afterTextChanged(Editable s) {
    if (!mRestoringPreviousValueFlag) {

        if (!isValid(s.toString())) {
            mRestoringPreviousValueFlag = true;
            restorePreviousValue();
        }

    } else {
        mRestoringPreviousValueFlag = false;
    }
}

private void restorePreviousValue() {
    mEditText.setText(mPreviousValue);
    mEditText.setSelection(mCursorPosition);
}

private boolean isValid(String s) {
    Pattern patternWithDot = Pattern.compile("[0-9]*((\\.[0-9]{0," + mDigitsAfterZero + "})?)||(\\.)?");
    Pattern patternWithComma = Pattern.compile("[0-9]*((,[0-9]{0," + mDigitsAfterZero + "})?)||(,)?");

    Matcher matcherDot = patternWithDot.matcher(s);
    Matcher matcherComa = patternWithComma.matcher(s);

    return matcherDot.matches() || matcherComa.matches();
}
}

用法:

myTextEdit.addTextChangedListener(new DecimalInputTextWatcher(myTextEdit, 2));

将模式从 isValid() 移动到构造函数中,以避免在每次 isValid() 调用时重新创建模式。 - Hemant Kaushik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接