Sé que ya hay un millón de respuestas a esto, con una aceptada. Sin embargo, hay numerosos errores en la respuesta aceptada y la mayoría del resto simplemente corrige uno (o tal vez dos) de ellos, sin expandirse a todos los casos de uso posibles.
Así que básicamente compilé la mayoría de las correcciones de errores sugeridas en las respuestas de soporte, así como agregué un método para permitir la entrada continua de números fuera del rango en la dirección de 0 (si el rango no comienza en 0), al menos hasta que sea seguro de que ya no puede estar en el rango. Para ser claros, este es el único momento que realmente causa problemas con muchas de las otras soluciones.
Aquí está la solución:
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("");
}
}
}
Tenga en cuenta que algunos casos de entrada son imposibles de manejar "activamente" (es decir, como el usuario lo está ingresando), por lo que debemos ignorarlos y manejarlos después de que el usuario haya terminado de editar el texto.
Así es como puede usarlo:
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);
Ahora, cuando el usuario intenta escribir un número más cercano a 0 de lo que permite el rango, sucederá una de dos cosas:
Si min
y max
tienen el mismo número de dígitos, no se les permitirá ingresarlo una vez que lleguen al último dígito.
Si se deja un número fuera del rango en el campo cuando el texto pierde el foco, se ajustará automáticamente al límite más cercano.
Y, por supuesto, al usuario nunca se le permitirá ingresar un valor más alejado de 0 que el rango permitido, ni es posible que un número como ese esté "accidentalmente" en el campo de texto por este motivo.
¿Problemas conocidos?)
- Esto solo funciona si
EditText
pierde el foco cuando el usuario termina con él.
La otra opción es desinfectar cuando el usuario presiona la tecla "listo" / regresar, pero en muchos o incluso en la mayoría de los casos, esto causa una pérdida de enfoque de todos modos.
Sin embargo, cerrar el teclado virtual no desenfocará automáticamente el elemento. Estoy seguro de que el 99.99% de los desarrolladores de Android desearían que lo hiciera (y que el manejo del enfoque en los EditText
elementos fue menos problemático en general), pero hasta el momento no hay una funcionalidad incorporada para ello. El método más fácil que he encontrado para solucionar esto, si es necesario, es extender EditText
algo como esto:
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);
}
}
Esto "engañará" al filtro para que desinfecte la entrada aunque la vista no haya perdido el foco. Si la vista luego pierde el foco por sí misma, el saneamiento de entrada se activará nuevamente, pero nada cambiará ya que ya se solucionó.
Clausura
Uf. Eso fue mucho. Lo que originalmente parecía ser un problema bastante trivialmente fácil, terminó descubriendo muchas pequeñas piezas feas de Android de vainilla (al menos en Java). Y una vez más, solo necesita agregar el oyente y extenderlo EditText
si su rango no incluye 0 de alguna manera. (Y de manera realista, si su rango no incluye 0 pero comienza en 1 o -1, tampoco tendrá problemas).
Como última nota, esto solo funciona para ints . Ciertamente, hay una manera de implementarlo para que funcione con decimales ( double
, float
), pero como ni yo ni el autor de la pregunta original lo necesitamos, no quiero profundizar en ello. Sería muy fácil usar simplemente el filtrado posterior a la finalización junto con las siguientes líneas:
// 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;
Solo tendría que cambiar de int
a float
(o double
), permitir la inserción de un solo .
(o ,
, ¿según el país?), Y analizar como uno de los tipos decimales en lugar de an int
.
De todos modos, maneja la mayor parte del trabajo, por lo que funcionaría de manera muy similar.