Я знаю, що вже є мільйон відповідей на це, причому одна прийнята. Однак у прийнятій відповіді є чимало помилок, і більшість решти просто виправляють один (а може і два) з них, не розширюючись на всі можливі випадки використання.
Таким чином, я в основному склав більшість виправлень помилок, запропонованих у відповідях підтримки, а також додав метод, щоб дозволити безперервне введення чисел поза діапазону в напрямку 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, ви також не зіткнетеся з проблемами.)
Як остання примітка, це працює лише для ints . Звичайно, існує спосіб реалізувати його для роботи з десятковою комою ( 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
.
Це в будь-якому випадку справляється з більшою частиною роботи, так що це буде працювати дуже аналогічно.