Je sais qu'il y a déjà un million de réponses à cela, dont une acceptée. Cependant, il existe de nombreux bogues dans la réponse acceptée et la plupart des autres en corrigent simplement un (ou peut-être deux), sans s'étendre à tous les cas d'utilisation possibles.
J'ai donc compilé la plupart des corrections de bogues suggérées dans les réponses de support ainsi que l'ajout d'une méthode pour permettre l'entrée continue de nombres en dehors de la plage dans la direction de 0 (si la plage ne commence pas à 0), du moins jusqu'à ce qu'elle soit certain qu'il ne peut plus être dans la plage. Parce que pour être clair, c'est le seul moment qui cause vraiment des problèmes avec de nombreuses autres solutions.
Voici la solution:
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("");
}
}
}
Notez que certains cas de saisie sont impossibles à gérer "activement" (c'est-à-dire lorsque l'utilisateur le saisit), nous devons donc les ignorer et les gérer une fois que l'utilisateur a fini de modifier le texte.
Voici comment vous pourriez l'utiliser:
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);
Maintenant, lorsque l'utilisateur essaie de taper un nombre plus proche de 0 que la plage le permet, l'une des deux choses suivantes se produit:
Si min
et max
ont le même nombre de chiffres, ils ne seront pas autorisés à le saisir du tout une fois qu'ils auront atteint le dernier chiffre.
Si un nombre en dehors de la plage est laissé dans le champ lorsque le texte perd le focus, il sera automatiquement ajusté à la limite la plus proche.
Et bien sûr, l'utilisateur ne sera jamais autorisé à entrer une valeur plus éloignée de 0 que la plage le permet, et il n'est pas non plus possible pour un nombre comme celui-là d'être "accidentellement" dans le champ de texte pour cette raison.
Problèmes connus?)
- Cela ne fonctionne que si le
EditText
perd le focus lorsque l'utilisateur en a terminé avec lui.
L'autre option consiste à nettoyer lorsque l'utilisateur appuie sur la touche «terminé» / retour, mais dans de nombreux cas, voire dans la plupart des cas, cela provoque de toute façon une perte de concentration.
Cependant, la fermeture du clavier virtuel ne désactive pas automatiquement la mise au point de l'élément. Je suis sûr que 99,99% des développeurs Android le souhaiteraient (et cette concentration sur la gestion des EditText
éléments était moins un bourbier en général), mais pour l'instant, il n'y a pas de fonctionnalité intégrée pour cela. La méthode la plus simple que j'ai trouvée pour contourner ce problème, si vous en avez besoin, est d'étendre EditText
quelque chose comme ceci:
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);
}
}
Cela "trompera" le filtre en nettoyant l'entrée même si la vue n'a pas réellement perdu le focus. Si la vue perd par la suite le focus d'elle-même, l'assainissement d'entrée se déclenchera à nouveau, mais rien ne changera car il a déjà été corrigé.
Fermeture
Ouf. C'était beaucoup. Ce qui semblait à l'origine être un problème assez trivial a fini par découvrir de nombreux petits morceaux laids d'Android vanille (du moins en Java). Et encore une fois, il vous suffit d'ajouter l'écouteur et de l'étendre EditText
si votre plage n'inclut pas 0 d'une manière ou d'une autre. (Et de manière réaliste, si votre plage n'inclut pas 0 mais commence à 1 ou -1, vous ne rencontrerez pas non plus de problèmes.)
En dernier lieu, cela ne fonctionne que pour les ints . Il existe certainement un moyen de l'implémenter pour travailler avec des décimales ( double
, float
), mais comme ni moi ni le demandeur d'origine n'en avons besoin, je ne veux pas particulièrement approfondir tout cela. Il serait très facile d'utiliser simplement le filtrage post-achèvement avec les lignes suivantes:
// 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;
Il vous suffirait de changer de int
à float
(ou double
), d'autoriser l'insertion d'un seul .
(ou ,
, selon le pays?), Et d'analyser comme l'un des types décimaux au lieu d'un int
.
Cela gère la plupart du travail de toute façon, donc cela fonctionnerait de manière très similaire.