So che ci sono già un milione di risposte a questo, con una accettata. Tuttavia, ci sono numerosi bug nella risposta accettata e la maggior parte degli altri risolve semplicemente uno (o forse due) di essi, senza espandersi a tutti i possibili casi d'uso.
Quindi ho sostanzialmente compilato la maggior parte delle correzioni di bug suggerite nelle risposte di supporto e ho aggiunto un metodo per consentire l'immissione continua di numeri al di fuori dell'intervallo nella direzione 0 (se l'intervallo non inizia da 0), almeno fino a quando non è certo che non può più essere nel range. Perché per essere chiari, questa è l'unica volta che causa davvero problemi con molte altre soluzioni.
Ecco la soluzione:
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("");
}
}
}
Si noti che alcuni casi di input sono impossibili da gestire "attivamente" (cioè, poiché l'utente lo sta inserendo), quindi dobbiamo ignorarli e gestirli dopo che l'utente ha terminato la modifica del testo.
Ecco come potresti 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);
Ora, quando l'utente tenta di digitare un numero più vicino a 0 di quanto consentito dalla gamma, accadrà una delle due cose:
Se min
e max
hanno lo stesso numero di cifre, non sarà possibile immetterle affatto una volta raggiunta la cifra finale.
Se un numero al di fuori dell'intervallo viene lasciato nel campo quando il testo perde lo stato attivo, verrà automaticamente regolato sul limite più vicino.
E, naturalmente, all'utente non sarà mai permesso inserire un valore più lontano da 0 di quanto consentito dalla gamma, né è possibile che un numero come quello sia "accidentalmente" nel campo di testo per questo motivo.
Problemi noti?)
- Questo funziona solo se
EditText
perde la concentrazione quando l'utente ha finito con esso.
L'altra opzione è la sanificazione quando l'utente preme il tasto "done" / return, ma in molti o anche nella maggior parte dei casi, ciò causa comunque una perdita di messa a fuoco.
Tuttavia, la chiusura della tastiera non disattiverà automaticamente l'elemento. Sono sicuro che il 99,99% degli sviluppatori Android lo vorrebbe (e che la gestione del focus sugli EditText
elementi era meno di un pantano in generale), ma al momento non esiste una funzionalità integrata per questo. Il metodo più semplice che ho trovato per aggirare questo, se necessario, è estendere EditText
qualcosa del genere:
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);
}
}
Ciò "indurrà" il filtro a disinfettare l'input anche se la vista non ha effettivamente perso la messa a fuoco. Se in seguito la vista dovesse perdere la concentrazione da sola, l'inizializzazione dell'input si innescherà di nuovo, ma nulla cambierà poiché è già stato risolto.
Chiusura
Accidenti. È stato molto. Ciò che originariamente sembrava essere un problema abbastanza banale finì per scoprire molti brutti pezzi di Android vaniglia (almeno in Java). E ancora una volta, devi solo aggiungere il listener ed estenderlo EditText
se il tuo intervallo non include 0 in qualche modo.(E realisticamente, se il tuo intervallo non include 0 ma inizia da 1 o -1, anche tu non incontrerai problemi.)
Come ultima nota, questo funziona solo per ints . C'è sicuramente un modo per implementarlo per lavorare con i decimali ( double
, float
), ma dal momento che né io né il richiedente originale ne abbiamo bisogno, non voglio approfondire particolarmente. Sarebbe molto semplice utilizzare semplicemente il filtro post-completamento insieme alle seguenti righe:
// 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;
Dovresti solo cambiare da int
a float
(o double
), consentire l'inserimento di un singolo .
(o ,
, a seconda del paese?) E analizzare come uno dei tipi decimali anziché uno int
.
Gestisce comunque la maggior parte del lavoro, quindi funzionerebbe in modo molto simile.