I want to define a min and max value for an EditText
.
For example: if any person tries to enter a month value in it, the value must be between 1-12.
To define the minimum value of the EditText, I used this:
if (message.trim().length() >= 1 && message.trim().length() <= 12) {
// do stuf
} else {
// Too short or too long
}
There is something wrong in the accepted answer.
int input = Integer.parseInt(dest.toString() + source.toString());
If I move cursor into middle of text, then type something, then the above statement will produce wrong result. For example, type "12" first, then type "0" between 1 and 2, then the statement mentioned above will produce "120" instead of 102. I modified this statement to statements below:
String destString = dest.toString();
String inputString = destString.substring(0, dstart) + source.toString() + destString.substring(dstart);
int input = Integer.parseInt(inputString);
I stumbled upon this problem when I was making a pet project. I've read some of the answers here and I've probably adopted on or two of them in my code.
BAD NEWS: I managed to do this by using a very dirty way (you'll see why). There are still some bugs that I haven't bothered to address (I was writing this at like 2 am), like if the min
value is 10, you won't be able to input a number to begin with.
GOOD NEWS: I've managed to get rid of the leading zeros bug mentioned by @nnyerges using solely the InputFilter
down to only one 0, that is if the min
value is 0. However, the limit of my implementation of the InputFilter
comes when user deletes the first number(s) that's followed by zero(s), e.g. if at first user inputs 1000
but then deletes 1
, it will become 000
. That's ugly, and that's where my dirty ugly use of TextChangedListener
/ TextWatcher
comes in. (I know OP already said that he can do it using TextWatcher
, but whatever.)
Another limitation (or maybe MY limitation?) using the InputFilter
is when the inputType
is numberDecimal
, which means user can input a decimal separator. Example case: range is 0 - 100
, user inputs 99.99
, user then deletes the separator, we'd have 9999
. We don't want that, do we?
I've also made it to accommodate negative value.
Some features in my code, whether you like it or not, include trimming insignificant 0
s, e.g. if user deletes 1
from 10032
, as long as it's within the defined range, it will trim the leading 0
s, so the final result will be 32. Second, when user tries to delete the negative (-
) notation or the decimal separator (.
), it will check whether the resulting number after deletion is still in range. If not, then it will revert back to last value. In other words, user isn't allowed to do that kind of removal. But, if you prefer to set the new values to either min
or max
values when that happens, you can do it, too.
NOTE: I'm too lazy to even bother with localization, so people who use comma as decimal separator will have to manually change it themselves.
SECOND NOTE: The code is very messy and probably have some or a lot of redundant checks, so be aware. Also, if you have suggestion, feel free to comment, as I want to improve it, too. I may need to use it in the future. Who knows?
Anyway, here it goes.
import android.text.InputFilter;
import android.text.Spanned;
import android.util.Log;
public class InputFilterMinMax implements InputFilter {
private double min, max;
public InputFilterMinMax(double min, double max) {
this.min = min;
this.max = max;
}
public InputFilterMinMax(String min, String max) {
this.min = Double.parseDouble(min);
this.max = Double.parseDouble(max);
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
String lastVal = dest.toString();
String newVal = lastVal.substring(0, dstart) + source.toString() + lastVal.substring(dstart);
String strInput = source.toString();
double input;
if (strInput.equals("-") && (lastVal.length() == 0 || lastVal.equals("0"))) {
return null;
} else {
input = Double.parseDouble(newVal);
}
if (isInRange(min, max, input)) {
try {
if (lastVal.equals("0") && strInput.equals("0") && !strInput.equals(".")) {
Log.d("Checkpoint 1", "Can't put 0 again.");
return "";
} else if (strInput.equals("0")) {
if (dstart == 0) {
if (lastVal.substring(0, 1).equals("0")) {
Log.d("Checkpoint 2", "Can't put 0 again.");
return "";
} else if (!lastVal.substring(0, 1).equals(".")) {
Log.d("Checkpoint 3", "Can't put 0 in front of them.");
return "";
}
} else {
if (lastVal.substring(0, 1).equals("0") && dstart == 1) {
Log.d("Checkpoint 4", "Can't put 0 again.");
return "";
} else if (lastVal.substring(0, 1).equals("-")) {
if (Double.parseDouble(lastVal) == 0) {
if (!lastVal.contains(".")) {
Log.d("Checkpoint 5", "Can't put 0 here.");
return "";
} else {
if (dstart <= lastVal.indexOf(".")) {
Log.d("Checkpoint 6", "Can't put 0 here.");
return "";
}
}
} else {
if (lastVal.indexOf("0") == 1 && (dstart == 1 || dstart == 2)) {
Log.d("Checkpoint 7", "Can't put 0 here.");
return "";
} else if ((!lastVal.substring(1, 2).equals("0") && !lastVal.substring(1, 2).equals(".")) && dstart == 1) {
Log.d("Checkpoint 8", "Can't put 0 here.");
return "";
}
}
}
}
}
/**
* If last value is a negative that equals min value,
* and user tries to input a decimal separator at the
* very end, ignore it, because they won't be able to
* input anything except 0 after that anyway.
*/
if (strInput.equals(".") && lastVal.substring(0,1).equals("-")
&& Double.parseDouble(lastVal) == min && dstart == lastVal.length()) {
return "";
}
} catch (Exception e) {
}
return null;
}
} catch (Exception ignored) {
ignored.printStackTrace();
}
return "";
}
private boolean isInRange(double a, double b, double c) {
return b > a ? c >= a && c <= b : c >= b && c <= a;
}
}
Now, the really dirty part:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity implements TextWatcher {
private EditText editInput;
/**
* Var to store old value in case the new value is either
* out of range or invalid somehow. This was because I
* needed a double value for my app, which means I can
* enter a dot (.), and that could mean trouble if I decided
* to delete that dot, e.g. assume the range is 0 - 100.
* At first I enter 99.99, the InputFilter would allow that,
* but what if somewhere down the line I decided to delete
* the dot/decimal separator for "fun"?
* Wow, now I have 9999.
* Also, when I delete negative notation, it can produce
* the same problem.
*/
private String oldVal;
private int min, max;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editInput = findViewById(R.id.edt_input);
editInput.addTextChangedListener(this);
min = -1600;
max = 1500;
editInput.setFilters(new InputFilter[]{new InputFilterMinMax(min, max)});
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
oldVal = saveOldValue(s, start);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
validateChange(editInput, oldVal);
}
private String saveOldValue(CharSequence s, int start) {
String oldVal = s.toString();
if (oldVal.contains(".") && start == oldVal.indexOf(".") && start != oldVal.length() - 1) {
return oldVal;
} else if (oldVal.contains("-") && start == oldVal.indexOf("-") && start != oldVal.length() - 1) {
return oldVal;
}
return null;
}
private void validateChange(EditText editText, String oldVal) {
String strNewVal = editText.getText().toString().trim();
boolean isChanged = false;
if (strNewVal.indexOf("0") == 0 || (strNewVal.indexOf("-") == 0 && strNewVal.indexOf("0") == 1)) {
if (strNewVal.contains(".")) {
while ((strNewVal.indexOf("0") == 0 && strNewVal.indexOf(".") != 1 && strNewVal.length() > 2) ||
(strNewVal.indexOf("0") == 1 && strNewVal.indexOf(".") != 2 && strNewVal.length() > 3)) {
Log.d("Trimming 0", "");
strNewVal = strNewVal.replaceFirst("0", "");
isChanged = true;
}
} else if (!strNewVal.contains(".")) {
while (strNewVal.indexOf("0") == 0 && strNewVal.length() > 1) {
Log.d("Trimming 0", "");
strNewVal = strNewVal.replaceFirst("0", "");
isChanged = true;
}
if (Double.parseDouble(strNewVal) > max) {
editText.setText(oldVal); // Or, you can set it to max values here.
return;
}
}
}
if (strNewVal.indexOf(".") == 0) {
strNewVal = "0" + strNewVal;
isChanged = true;
}
try {
double newVal = Double.parseDouble(strNewVal);
Log.d("NewVal: ", String.valueOf(newVal));
if (newVal > max || newVal < min) {
Log.d("Over Limit", "Let's Reset");
editText.setText(oldVal); // Or, you can set it to min or max values here.
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
if (isChanged) {
editText.setText(strNewVal);
}
}
}
To add to Pratik's answer, here is a modified version where user can enter min 2 digits also , for example, 15 to 100:
import android.text.InputFilter;
import android.text.Spanned;
public class InputFilterMinMax implements InputFilter {
private int min, max;
public InputFilterMinMax(int min, int max) {
this.min = min;
this.max = max;
}
public InputFilterMinMax(String min, String max) {
this.min = Integer.parseInt(min);
this.max = Integer.parseInt(max);
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
if(end==1)
min=Integer.parseInt(source.toString());
int input = Integer.parseInt(dest.toString() + source.toString());
if (isInRange(min, max, input))
return null;
} catch (NumberFormatException nfe) {
}
return "";
}
private boolean isInRange(int a, int b, int c) {
return b > a ? c >= a && c <= b : c >= b && c <= a;
}}
added: if(end==1) min=Integer.parseInt(source.toString());
Hope this helps. kindly dont downvote without reasons.
First make this class :
package com.test;
import android.text.InputFilter;
import android.text.Spanned;
public class InputFilterMinMax implements InputFilter {
private int min, max;
public InputFilterMinMax(int min, int max) {
this.min = min;
this.max = max;
}
public InputFilterMinMax(String min, String max) {
this.min = Integer.parseInt(min);
this.max = Integer.parseInt(max);
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
int input = Integer.parseInt(dest.toString() + source.toString());
if (isInRange(min, max, input))
return null;
} catch (NumberFormatException nfe) { }
return "";
}
private boolean isInRange(int a, int b, int c) {
return b > a ? c >= a && c <= b : c >= b && c <= a;
}
}
Then use this from your Activity :
EditText et = (EditText) findViewById(R.id.myEditText);
et.setFilters(new InputFilter[]{ new InputFilterMinMax("1", "12")});
This will allow user to enter values from 1 to 12 only.
EDIT :
Set your edittext with android:inputType="number"
.
You can find more details at https://www.techcompose.com/how-to-set-minimum-and-maximum-value-in-edittext-in-android-app-development/.
Thanks.
I extended @Pratik Sharmas code to use BigDecimal objects instead of ints so that it can accept larger numbers, and account for any formatting in the EditText that isn't a number (like currency formatting i.e. spaces, commas and periods)
EDIT: note that this implementation has 2 as the minimum significant figures set on the BigDecimal (see the MIN_SIG_FIG constant) as I used it for currency, so there was always 2 leading numbers before the decimal point. Alter the MIN_SIG_FIG constant as necessary for your own implementation.
public class InputFilterMinMax implements InputFilter {
private static final int MIN_SIG_FIG = 2;
private BigDecimal min, max;
public InputFilterMinMax(BigDecimal min, BigDecimal max) {
this.min = min;
this.max = max;
}
public InputFilterMinMax(String min, String max) {
this.min = new BigDecimal(min);
this.max = new BigDecimal(max);
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart,
int dend) {
try {
BigDecimal input = formatStringToBigDecimal(dest.toString()
+ source.toString());
if (isInRange(min, max, input)) {
return null;
}
} catch (NumberFormatException nfe) {
}
return "";
}
private boolean isInRange(BigDecimal a, BigDecimal b, BigDecimal c) {
return b.compareTo(a) > 0 ? c.compareTo(a) >= 0 && c.compareTo(b) <= 0
: c.compareTo(b) >= 0 && c.compareTo(a) <= 0;
}
public static BigDecimal formatStringToBigDecimal(String n) {
Number number = null;
try {
number = getDefaultNumberFormat().parse(n.replaceAll("[^\\d]", ""));
BigDecimal parsed = new BigDecimal(number.doubleValue()).divide(new BigDecimal(100), 2,
BigDecimal.ROUND_UNNECESSARY);
return parsed;
} catch (ParseException e) {
return new BigDecimal(0);
}
}
private static NumberFormat getDefaultNumberFormat() {
NumberFormat nf = NumberFormat.getInstance(Locale.getDefault());
nf.setMinimumFractionDigits(MIN_SIG_FIG);
return nf;
}