Android - How to only allow a certain number of decimal places

10,503

Solution 1

I would go for a filter in the edit text itself with the power of regex. First the regex expression:

^\-?(\d{0,5}|\d{0,5}\.\d{0,3})$

Maybe there are multiple ways to improve this expression, but this does trick.

And now just set an input filter in the edittext, like this:

final String regex = "^\-?(\d{0,5}|\d{0,5}\.\d{0,3})$";
((EditText)rootView.findViewById(R.id.editText1)).setFilters(new InputFilter[] {
    new InputFilter() {
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned destination, int destinationStart, int destinationEnd) {
            if (end > start) {
                // adding: filter   
                // build the resulting text
                String destinationString = destination.toString();
                String resultingTxt = destinationString.substring(0, destinationStart) + source.subSequence(start, end) + destinationString.substring(destinationEnd);
                // return null to accept the input or empty to reject it
                return resultingTxt.matches(regex) ? null : "";
            }
            // removing: always accept
            return null;
        }
    }
});

Btw, I just tested this code and what it does is:

  1. The user can enter a maximum of 8 digits;
  2. As soon as the user enters a '.', the maximum decimal digits allowed are 8.

Did I correctly understand the problem you described?

-- EDIT

Ok, I was almost there. From what I understand, decimal(8,3) means at most 8 digits including digits to the left or right of the decimal point, ranging from -99999.999 to 99999.999. At least that's what I understand from this sentence Standard SQL requires that DECIMAL(5,2) be able to store any value with five digits and two decimals, so values that can be stored in the salary column range from -999.99 to 999.99. Even though it's from the MySQL documentation the MSSQL docs seem to do the same.

Solution 2

I have answser for you, me also suffered lot in this kind of situation.:D :P

I have implemented this for maximum of 4 digits to the left and 2 to the right of the decimal point ex: 4444.99

so small changes need to implement what i did: Need to do following changes

1) copy CustomTextWatcher.java to track input of editText.

import java.text.NumberFormat;

import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;

public class CustomTextWatcher implements TextWatcher {


    private NumberFormat nf = NumberFormat.getNumberInstance();
    private EditText et;
    private String tmp = "";
    private int moveCaretTo;
    private static final int INTEGER_CONSTRAINT = 4;
    private static final int FRACTION_CONSTRAINT = 2;
    private static final int MAX_LENGTH = INTEGER_CONSTRAINT
            + FRACTION_CONSTRAINT + 1;

    public CustomTextWatcher(EditText et) {
        this.et = et;
        nf.setMaximumIntegerDigits(INTEGER_CONSTRAINT);
        nf.setMaximumFractionDigits(FRACTION_CONSTRAINT);
        nf.setGroupingUsed(false);
    }

    public int countOccurrences(String str, char c) {
        int count = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == c) {
                count++;
            }
        }
        return count;
    }

    @Override
    public void afterTextChanged(Editable s) {
        et.removeTextChangedListener(this); // remove to prevent stackoverflow
        String ss = s.toString();
        int len = ss.length();
        int dots = countOccurrences(ss, '.');
        boolean shouldParse = dots <= 1
                && (dots == 0 ? len != (INTEGER_CONSTRAINT + 1)
                        : len < (MAX_LENGTH + 1));
        if (shouldParse) {
            if (len > 1 && ss.lastIndexOf(".") != len - 1) {
                try {

                    if (ss.contains(".")) {

                        String[] integerFractionStrings = ss.split("\\.");

                        Log.v("Check SS ", ss);

                        Log.v("second string", "Found"
                                + integerFractionStrings.length);

                        if (integerFractionStrings.length > 1) {

                            Log.v("integerFractionStrings",
                                    integerFractionStrings[1]);

                            if (integerFractionStrings[1].length() == 1
                                    && integerFractionStrings[1].charAt(0) == '0') {

                                et.setText(ss);

                                Log.v("second string", "size 1");
                            } else {

                                Log.v("second string", "> 1");

                                Double d = Double.parseDouble(ss);
                                if (d != null) {
                                    et.setText(nf.format(d));
                                }

                            }
                        }
                    } else {

                        Log.v("First string", "No dot");

                        Double d = Double.parseDouble(ss);
                        if (d != null) {
                            et.setText(nf.format(d));
                        }
                    }

                } catch (NumberFormatException e) {
                }
            }
        } else {

            Log.v("second string", "size 1");
            et.setText(tmp);
        }
        et.addTextChangedListener(this); // reset listener

        // tried to fix caret positioning after key type:
        if (et.getText().toString().length() > 0) {
            if (dots == 0 && len >= INTEGER_CONSTRAINT
                    && moveCaretTo > INTEGER_CONSTRAINT) {
                moveCaretTo = INTEGER_CONSTRAINT;
            } else if (dots > 0 && len >= (MAX_LENGTH)
                    && moveCaretTo > (MAX_LENGTH)) {
                moveCaretTo = MAX_LENGTH;
            }
            try {
                et.setSelection(et.getText().toString().length());
                // et.setSelection(moveCaretTo); <- almost had it :))
            } catch (Exception e) {
            }
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
        moveCaretTo = et.getSelectionEnd();
        tmp = s.toString();
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        int length = et.getText().toString().length();
        if (length > 0) {
            moveCaretTo = start + count - before;
        }
    }
}

2) set this class to check your editText by following.

EditText review_food_Price;

review_food_Price = (EditText) findViewById(R.id.food_Price);

review_food_Price.setRawInputType(InputType.TYPE_CLASS_NUMBER
                | InputType.TYPE_NUMBER_FLAG_DECIMAL);

review_food_Price.addTextChangedListener(new CustomTextWatcher(
                review_food_Price));

Hope you can convert my code according to your need.

Share:
10,503
eddy
Author by

eddy

Updated on June 25, 2022

Comments

  • eddy
    eddy almost 2 years

    Do you know of any method to make sure users can only enter figures with a maximum number of decimals. I'm not sure how to address this problem. In the MS SQL database I'm going to send data from my app I've got columns with this type decimal(8,3) Now considering the data type of the column that's finally going to store the value I want to validate in Android, I've considered these two cases:

    1. If the user enters a number with no decimals, the maximum number of digits must be 8
    2. If the user enters a number with decimals, the maximum number of digits must be 8 (including the digits to the right of the decimal point)

    Now I'm sure about the first case, but not so much about the second. Is it right to keep the number of maximum digits fixed(for example, always 8)? Or should I consider allowing a maximum of 8 digits to the left and 3 to the right of the decimal point?

    Either way this is what I've been trying in Android:

    mQuantityEditText.addTextChangedListener(new TextWatcher() {
                @Override
                public void afterTextChanged(Editable s) {
                    String str = mQuantityEditText.getText().toString();
                    DecimalFormat format = (DecimalFormat) DecimalFormat
                            .getInstance();
                    DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
                    char sep = symbols.getDecimalSeparator();
    
                    int indexOFdec = str.indexOf(sep);
    
                    if (indexOFdec >= 0) {
                        if (str.substring(indexOFdec, str.length() - 1).length() > 3) {                     
                            s.replace(0, s.length(),
                                    str.substring(0, str.length() - 1));                        
                        }
                    }
                }
    
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count,
                        int after) {
    
                }
    
                @Override
                public void onTextChanged(CharSequence s, int start, int before,
                        int count) {
    
                }
            });
    

    Even though, the above code handles the maximum number of decimal places. It does not limit the total number of digits allowed in the EditText.

    Do you think you could help me improve my code so that it handles both the maximum number of decimal places and the total number of digits allowed in a EditText (considering both numbers to the left and to the right of the decimal point)

    EDIT

    Well, now I'm trying what João Sousa suggested and here's what I've tried:

    1) I defined a class that implements InputFilter

    public class NumberInputFilter implements InputFilter {
        private Pattern mPattern;   
    
        public NumberInputFilter(int precision, int scale) {        
            String pattern="^\\-?(\\d{0," + (precision-scale) + "}|\\d{0," + (precision-scale) + "}\\.\\d{0," + scale + "})$";
            this.mPattern=Pattern.compile(pattern);
    
        }
    
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned destination, int destinationStart, int destinationEnd) {
             if (end > start) {
                 // adding: filter   
                 // build the resulting text
                 String destinationString = destination.toString();
                 String resultingTxt = destinationString.substring(0, destinationStart) + source.subSequence(start, end) + destinationString.substring(destinationEnd);
                 // return null to accept the input or empty to reject it
                 return resultingTxt.matches(this.mPattern.toString()) ? null : "";
             }
             // removing: always accept
             return null;
        }
    
    }
    

    2) Tried to use the class like this :

    mQuantityEditText.setFilters(new InputFilter[] { new NumberInputFilter(8,3)} );
    
    • Jibran Khan
      Jibran Khan over 9 years
      possible duplicate of Android - Round to 2 decimal places
    • eddy
      eddy over 9 years
      @JibranKhan Sorry but I don't intend to round anything here, just limit the number of decimals a user can enter.
  • eddy
    eddy over 9 years
    I tried to use it, but for some reason the <br.com.sapereaude.maskedEditText.MaskedEditText> is not recognized not even when I change it to the name of my package. <com.ss.quicorder.maskedEditText.MaskedEditText>
  • eddy
    eddy over 9 years
    Thanks @joão I'll give it a try. About point 2, I think I have to options: 1) a total of 8 digits (including numbers to the left and to the right of the decimal point) or a total of 11 digits(a max 8 digits to the left and a max of 3 to the right of the decimal point) What would you choose, knowing that the database where you finally going to store the value is of this type : decimal(8,3)
  • eddy
    eddy over 9 years
    Thank you @nil, I'll give it a try
  • Joao Sousa
    Joao Sousa over 9 years
    I edited the regex, it now accepts values from -99999.999 to 99999.999. I still think this solution is the best as now it's just a matter of tweaking the regular expression if necessary.
  • Nil
    Nil over 9 years
    May be it will help you. Don't forget to upvote and accept, if it work for you. :)
  • eddy
    eddy over 9 years
    Yes, @joão you're right decimal(8,3) would only allow a max of 8 digits not matter whether the number has decimal places or not. Having said that, I guess the regex should be something like this: ^\-?(\d{0,8}|\d{0,5}\.\d{0,3})$
  • Joao Sousa
    Joao Sousa over 9 years
    Are you 100% sure that MySQL would accept 99999999 (8 digits)? From what I understood it would only accept a number to a maximum of 99999.999. Therefore, try it and if it does accept, then you're right and please let me know as well :)
  • Joao Sousa
    Joao Sousa over 9 years
    I just tried it myself and if you try to add to the table an integer number with 6 digits (no decimal cases) the query fails, therefore I would say my regex expression is correct.
  • eddy
    eddy over 9 years
    I kind of confused, I thought the the first number was precision, that is the maximum total number of digits that will be stored, both to the left and to the right of the decimal point. msdn.microsoft.com/library/ms187746.aspx
  • Joao Sousa
    Joao Sousa over 9 years
    Yeah, I tried using a mysql database as I don't have mssql. As I said, try it yourself and let me know.
  • eddy
    eddy over 9 years
    I edited my question with the changes I made to my code to use you regex. Please take a look at it
  • Joao Sousa
    Joao Sousa over 9 years
    You didn't add backslashes so that java escapes some special characters there. The correct pattern: String pattern="^\\-?(\\d{0," + (precision-scale) + "}|\\d{0," + (precision-scale) + "}\\.\\d{0," + scale + "})$";
  • eddy
    eddy over 9 years
    That was it. So far it's been working as expected. Thank you very much.
  • Nayan
    Nayan about 5 years
    The editText becomes vulnerable, the cursor changes it's position each time you write a number.
  • Nil
    Nil almost 5 years
    @Nayan, That's why I have added removeTextChangedListener.