Я знаю, что на это уже есть миллион ответов, и один принят. Однако в принятом ответе есть множество ошибок, а большинство остальных просто исправляют одну (или, может быть, две) из них, не расширяя все возможные варианты использования.
Таким образом, я в основном скомпилировал большинство исправлений ошибок, предложенных в ответах службы поддержки, а также добавил метод, позволяющий непрерывно вводить числа за пределами диапазона в направлении 0 (если диапазон не начинается с 0), по крайней мере, до тех пор, пока он уверен, что его больше не может быть в диапазоне. Потому что, чтобы быть ясным, это единственный раз, когда действительно возникают проблемы со многими другими решениями.
Вот исправление:
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("");
}
}
}
Обратите внимание, что некоторые варианты ввода невозможно обработать «активно» (т. Е. Пока пользователь вводит их), поэтому мы должны игнорировать их и обрабатывать их после того, как пользователь закончит редактировать текст.
Вот как это можно использовать:
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);
Теперь, когда пользователь пытается ввести число ближе к 0, чем позволяет диапазон, произойдет одно из двух:
Если min
и max
имеют одинаковое количество цифр, им не будет разрешено вводить его вообще, как только они дойдут до последней цифры.
Если число, выходящее за пределы диапазона, остается в поле, когда текст теряет фокус, он будет автоматически настроен на ближайшую границу.
И, конечно же, пользователю никогда не будет разрешено вводить значение дальше 0, чем позволяет диапазон, и по этой причине такое число не может «случайно» оказаться в текстовом поле.
Известные проблемы?)
- Это работает только в том случае, если
EditText
объект теряет фокус, когда пользователь покончил с ним.
Другой вариант - очистка, когда пользователь нажимает клавишу «готово» / «возврат», но во многих или даже в большинстве случаев это все равно приводит к потере фокуса.
Однако закрытие программной клавиатуры не снимает фокус с элемента автоматически. Я уверен, что 99,99% разработчиков Android этого пожелают (и EditText
такая обработка фокуса на элементах в целом была не такой уж большой проблемой), но пока для этого нет встроенных функций. Самый простой способ, который я нашел, чтобы обойти это, если вам нужно, - это расширить EditText
что-то вроде этого:
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);
}
}
Это «обманом» заставит фильтр очистить ввод, даже если представление фактически не потеряло фокус. Если в дальнейшем произойдет потеря фокуса в представлении, очистка ввода сработает снова, но ничего не изменится, поскольку оно уже было исправлено.
закрытие
Уф. Это было много. То, что поначалу казалось тривиально простой задачей, в конечном итоге привело к обнаружению множества уродливых кусочков ванильного Android (по крайней мере, на Java). И еще раз, вам нужно только добавить слушателя и расширить, EditText
если ваш диапазон каким-то образом не включает 0. (И реально, если ваш диапазон не включает 0, но начинается с 1 или -1, вы также не столкнетесь с проблемами.)
И последнее замечание: это работает только для целых чисел . Конечно, есть способ реализовать его для работы с десятичными знаками ( double
, float
), но, поскольку ни мне, ни первоначальному пользователю, задающему вопрос, это не нужно, я не особо хочу вдаваться в подробности. Было бы очень просто использовать фильтрацию после завершения вместе со следующими строками:
// 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;
Вам нужно будет только изменить с int
на float
(или double
), разрешить вставку одного .
(или ,
, в зависимости от страны?) И проанализировать как один из десятичных типов вместо int
.
Это в любом случае выполняет большую часть работы, поэтому будет работать очень похоже.