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.
here's the way I've used, it's working for negative number
First, create MinMaxFIlter.java class with the following code :
import android.text.InputFilter;
import android.text.Spanned;
import android.util.Log;
/**
* Created by 21 on 4/5/2016.
*/
public class MinMaxFilter implements InputFilter {
private double mIntMin, mIntMax;
public MinMaxFilter(double minValue, double maxValue) {
this.mIntMin = minValue;
this.mIntMax = maxValue;
}
public MinMaxFilter(String minValue, String maxValue) {
this.mIntMin = Double.parseDouble(minValue);
this.mIntMax = Double.parseDouble(maxValue);
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
Boolean isNeg = false;
String provi = dest.toString() + source.toString();
if("-".equals(provi.substring(0,1))){
if(provi.length()>1) {
provi = provi.substring(1, provi.length());
isNeg = true;
}
else{
if("".equals(source)){
return null;
}
return "-";
}
}
double input = Double.parseDouble(provi);
if(isNeg){input = input * (-1);}
if (isInRange(mIntMin, mIntMax, input)) {
return null;
}
} catch (Exception nfe) {}
return "";
}
private boolean isInRange(double a, double b, double c) {
if((c>=a && c<=b)){
return true;
}
else{
return false;
}
}
}
Then, create and set your filter to your edittext like this :
EditText edittext = new EditText(context);
editext.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
eInt.setFilters(new InputFilter[]{new MinMaxFilter(min, max)});
There is a small error in Pratik's code. For instance, if a value is 10 and you add a 1 at the beginning to make 110, the filter function would treat the new value as 101.
See below for a fix to this:
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
// Removes string that is to be replaced from destination
// and adds the new string in.
String newVal = dest.subSequence(0, dstart)
// Note that below "toString()" is the only required:
+ source.subSequence(start, end).toString()
+ dest.subSequence(dend, dest.length());
int input = Integer.parseInt(newVal);
if (isInRange(min, max, input))
return null;
} catch (NumberFormatException nfe) { }
return "";
}
I know there are a million answers to this already, with one accepted. However, there are numerous bugs in the accepted answer and most of the rest simply fix one (or maybe two) of them, without expanding to all possible use cases.
So I basically compiled most of the bug fixes suggested in support answers as well as adding a method to allow continuous input of numbers outside of the range in the direction of 0 (if the range doesn't start at 0), at least until it's certain that it can no longer be in the range. Because to be clear, this is the only time that really causes trouble with many of the other solutions.
Here's the fix:
public class InputFilterIntRange implements InputFilter, View.OnFocusChangeListener {
private final int min, max;
public InputFilterIntRange(int min, int max) {
if (min > max) {
// Input sanitation for the filter itself
int mid = max;
max = min;
min = mid;
}
this.min = min;
this.max = max;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
// Determine the final string that will result from the attempted input
String destString = dest.toString();
String inputString = destString.substring(0, dstart) + source.toString() + destString.substring(dstart);
// Don't prevent - sign from being entered first if min is negative
if (inputString.equalsIgnoreCase("-") && min < 0) return null;
try {
int input = Integer.parseInt(inputString);
if (mightBeInRange(input))
return null;
} catch (NumberFormatException nfe) {}
return "";
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
// Since we can't actively filter all values
// (ex: range 25 -> 350, input "15" - could be working on typing "150"),
// lock values to range after text loses focus
if (!hasFocus) {
if (v instanceof EditText) sanitizeValues((EditText) v);
}
}
private boolean mightBeInRange(int value) {
// Quick "fail"
if (value >= 0 && value > max) return false;
if (value >= 0 && value >= min) return true;
if (value < 0 && value < min) return false;
if (value < 0 && value <= max) return true;
boolean negativeInput = value < 0;
// If min and max have the same number of digits, we can actively filter
if (numberOfDigits(min) == numberOfDigits(max)) {
if (!negativeInput) {
if (numberOfDigits(value) >= numberOfDigits(min) && value < min) return false;
} else {
if (numberOfDigits(value) >= numberOfDigits(max) && value > max) return false;
}
}
return true;
}
private int numberOfDigits(int n) {
return String.valueOf(n).replace("-", "").length();
}
private void sanitizeValues(EditText valueText) {
try {
int value = Integer.parseInt(valueText.getText().toString());
// If value is outside the range, bring it up/down to the endpoint
if (value < min) {
value = min;
valueText.setText(String.valueOf(value));
} else if (value > max) {
value = max;
valueText.setText(String.valueOf(value));
}
} catch (NumberFormatException nfe) {
valueText.setText("");
}
}
}
Note that some input cases are impossible to handle "actively" (i.e., as the user is inputting it), so we must ignore them and handle them after the user is done editing the text.
Here's how you might use it:
EditText myEditText = findViewById(R.id.my_edit_text);
InputFilterIntRange rangeFilter = new InputFilterIntRange(25, 350);
myEditText.setFilters(new InputFilter[]{rangeFilter});
// Following line is only necessary if your range is like [25, 350] or [-350, -25].
// If your range has 0 as an endpoint or allows some negative AND positive numbers,
// all cases will be handled pre-emptively.
myEditText.setOnFocusChangeListener(rangeFilter);
Now, when the user tries to type in a number closer to 0 than the range allows, one of two things will happen:
If min
and max
have the same number of digits, they won't be allowed to input it at all once they get to the final digit.
If a number outside of the range is left in the field when the text loses focus, it will automatically be adjusted to the closest boundary.
And of course, the user will never be allowed to input a value farther from 0 than the range allows, nor is it possible for a number like that to "accidentally" be in the text field for this reason.
Known Issue(s?)
EditText
loses focus when the user is done with it. The other option is sanitizing when the user hits the "done"/return key, but in many or even most cases, this causes a loss of focus anyways.
However, closing the soft keyboard will not automatically un-focus the element. I'm sure 99.99% of Android developers wish it would (and that focus handling on EditText
elements was less of a quagmire in general), but as of yet there is no built-in functionality for it. The easiest method that I've found to get around this, if you need to, is to extend EditText
something like this:
public class EditTextCloseEvent extends AppCompatEditText {
public EditTextCloseEvent(Context context) {
super(context);
}
public EditTextCloseEvent(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EditTextCloseEvent(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
for (InputFilter filter : this.getFilters()) {
if (filter instanceof InputFilterIntRange)
((InputFilterIntRange) filter).onFocusChange(this, false);
}
}
return super.dispatchKeyEvent(event);
}
}
This will "trick" the filter into sanitizing the input even though the view hasn't actually lost focus. If the view happens to later lose focus on its own, the input sanitation will trigger again, but nothing will change since it was already fixed.
Closing
Whew. That was a lot. What originally seemed like it would be a pretty trivially easy problem ended up uncovering many little ugly pieces of vanilla Android (at least in Java). And once again, you only need to add the listener and extend EditText
if your range does not include 0 in some way. (And realistically, if your range doesn't include 0 but starts at 1 or -1, you also won't run into problems.)
As a last note, this only works for ints. There is certainly a way to implement it to work with decimals (double
, float
), but since neither I nor the original asker have a need for that, I don't particularly want to get all that deep into it. It would be very easy to simply use the post-completion filtering along with the following lines:
// Quick "fail"
if (value >= 0 && value > max) return false;
if (value >= 0 && value >= min) return true;
if (value < 0 && value < min) return false;
if (value < 0 && value <= max) return true;
You would only have to change from int
to float
(or double
), allow insertion of a single .
(or ,
, depending upon country?), and parse as one of the decimal types instead of an int
.
That handles most of the work anyways, so it would work very similarly.
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
String prefix = dest.toString().substring(0, dstart);
String insert = source.toString();
String suffix = dest.toString().substring(dend);
String input_string = prefix + insert + suffix;
int input = Integer.parseInt(input_string);
if (isInRange(min, max, input) || input_string.length() < String.valueOf(min).length())
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;
}
@Patrik’s code has a nice idea but with a lot of bugs. @Zac and @Anthony B ( negative numbers solutions) have solve some of them, but @Zac’s code still have 3 mayor bugs:
1. If user deletes all entries in the EditText, it’s impossible to type any number again.. Of course this can be controlled using a EditText changed listener on each field, but it will erase out the beauty of using a common InputFilter class for each EditText in your app.
2. Has @Guernee4 says, if for example min = 3, it’s impossible to type any number starting with 1.
3. If for example min = 0, you can type has many zeros you wish, that it’s not elegant the result. Or also, if no matter what is the min value, user can place the cursor in the left size of the first number, an place a bunch of leading zeros to the left, also not elegant.
I came up whit these little changes of @Zac’s code to solve this 3 bugs. Regarding bug # 3, I still haven't been able to completely remove all leading zeros at the left; It always can be one, but a 00, 01, 0100 etc in that case, is more elegant an valid that an 000000, 001, 000100, etc.etc. Etc.
Here is the code:
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
// Using @Zac's initial solution
String lastVal = dest.toString().substring(0, dstart) + dest.toString().substring(dend);
String newVal = lastVal.substring(0, dstart) + source.toString() + lastVal.substring(dstart);
int input = Integer.parseInt(newVal);
// To avoid deleting all numbers and avoid @Guerneen4's case
if (input < min && lastVal.equals("")) return String.valueOf(min);
// Normal min, max check
if (isInRange(min, max, input)) {
// To avoid more than two leading zeros to the left
String lastDest = dest.toString();
String checkStr = lastDest.replaceFirst("^0+(?!$)", "");
if (checkStr.length() < lastDest.length()) return "";
return null;
}
} catch (NumberFormatException ignored) {}
return "";
}
Have a nice day!
this is my code max=100, min=0
xml
<TextView
android:id="@+id/txt_Mass_smallWork"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000"
android:textSize="20sp"
android:textStyle="bold" />
java
EditText ed = findViewById(R.id.txt_Mass_smallWork);
ed.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {`
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if(!charSequence.equals("")) {
int massValue = Integer.parseInt(charSequence.toString());
if (massValue > 10) {
ed.setFilters(new InputFilter[]{new InputFilter.LengthFilter(2)});
} else {
ed.setFilters(new InputFilter[]{new InputFilter.LengthFilter(3)});
}
}
}
@Override
public void afterTextChanged(Editable editable) {
}
});