В этой статье я бы хотел осветить вопрос ввода пользователем чисел с заданной точностью.
Давайте посмотрим что имеется в арсенале Android и решим эту задачу.
Основные требования:
Дополнительные требования:
Из всего спектра элементов управления в
Обратимся к документации и посмотрим, что нам предлагает Android SDK.
XML-разметка
Публичные методы класса
Перед созданием собственного фильтра, сделаем так, чтобы в поле можно было вводить только числа и десятичный разделитель.
Теперь зададим собственный фильтр для поля ввода, чтобы принимать только значения соответствующие заданной точности.
Нам потребуется реализовать интерфейс
Согласно документации, этот метод вызывается при замене значения в поле ввода (dest) в диапазоне от dstart до dend на текст из буфера (source) в диапазоне от start до end.
Метод возвращает объект класса
В конструктор нашего фильтра будем передавать количество символов до и после десятичного разделителя.
Переопределяем метод
Формируем будущее значение после ввода пользователя:
Для удобства манипуляция со строкой, создадим экземпляр класса
В результате в переменной
Теперь нам следует проверить корректность нового значения.
Оно должно удовлетворять следующим условиям:
Проверим количество разделителей, для этого мы переберем все символы результирующей строки.
Если найдется десятичный разделитель, то запомним его индекс. В случае повторного нахождения разделителя, ввод считается некорректным и цикл останавливается.
Проверим корректность самого числа. Нам уже известен индекс десятичного разделителя, либо он отсутствует.
Остается только сравнить длину всего числа относительно индекса разделителя.
В завершении возвращаем результат.
Если ввод корректный, то возвращаем
в противном случае возвращаем пустую строку, чтобы не передавать в поле ввода значения.
Полный исходный код@github
Запускаем проект и вводим наш контрольный пример, пытаясь выйти за его границы.
В качестве контрольного примера будем использовать число 123.45. Нельзя вводить число более 999.99 и менее 0.01.
Видим, что приложение корректно обрабатывает целую и дробную части числа, запрещает вводить несколько десятичных разделителей.
Однако наблюдается интересная ситуация, если удалить десятичный разделитель, то мы выходим за границы точности.
Об этом упоминается в документации
Нужно быть осторожным с заменами нулевой длины, как это происходит при удалении текста.
Что же произошло на самом деле?
Наш алгоритм отработал корректно и в условии проверки длины целой части числа выявил, что допустимая точность нарушена и вернул пустую строку. Но само действие удаления символов было применено к исходному значению поля ввода.
Решим эту проблему следующим образом — выявим факт удаления символов. Документация подсказывает, что в этом случае происходит замена с нулевой длиной. Выделим из исходного значения поля ввода удаляемые символы и вернем их в качестве результата нашего метода, тем самым откажемся от удаления.
Нам потребуется изменить блок кода, где возвращается результат
Запускаем проект и проверяем.
Таким образом контрольный пример выполнен, заданная точность достигнута.
Библиотека успешно используется в продакшене.
Подключить её можно с помощью Gradle
Полный исходный код проекта@github.
Часто бывает необходимым запретить отображение системной клавиатуры для поля ввода.
Например, если используется клавиатура из приложения.
На помощь приходит метод класса EditText, появившийся в Android версии 21 и выше:
setShowSoftInputOnFocus(boolean show)
Вообще-то, этот метод был и в ранних версиях, но был приватным.
Да это «хак», однако это наиболее простой и короткий способ скрыть системную клавиатуру для поля ввода.
Давайте посмотрим что имеется в арсенале Android и решим эту задачу.
Начнем с требований
Основные требования:
- Реализовать поле ввода, позволяющее вводить только числа с задаваемой точностью.
- Точность задается количеством значимых цифр перед и после десятичной запятой.
Дополнительные требования:
- Поддержка курсора ввода.
- Стандартные опции редактирования: копировать, вырезать и вставить.
Анализ
Из всего спектра элементов управления в
Android
нас интересует виджет EditText
.Обратимся к документации и посмотрим, что нам предлагает Android SDK.
EditText
наследуется от TextView
, который, в свою очередь, обладает свойствами:XML-разметка
digits
– позволяет установить набор специальных символов, которые может принимать поле и автоматически включает режим ввода чисел.numeric
– задает обработчик ввода чисел.inputType
– с помощью набора константных значений позволяет сгенерировать требуемый обработчик ввода.Публичные методы класса
setFilters
– позволяет задать набор фильтров, которые будут применяться при вводе значений в поле.digits
, numeric
и inputType
позволяют ограничить набор вводимых символов, но никак не влияют на точность числа, а вот setFilters
, как раз то, что нам нужно. Реализация
Перед созданием собственного фильтра, сделаем так, чтобы в поле можно было вводить только числа и десятичный разделитель.
numberEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
Теперь зададим собственный фильтр для поля ввода, чтобы принимать только значения соответствующие заданной точности.
Нам потребуется реализовать интерфейс
InputFilter
, а конкретно переопределить метод filter
.Согласно документации, этот метод вызывается при замене значения в поле ввода (dest) в диапазоне от dstart до dend на текст из буфера (source) в диапазоне от start до end.
Пример, для понимания написанного выше
Поле ввода содержит 123456 (dest), курсор находится в 1[23]456 (dstart = 1, dend = 3), из буфера вставляется значение 789 (source = 789, start = 0, end 3).
Метод возвращает объект класса
CharSequence
, который будет установлен в поле ввода взамен текущему значению. Если заменять значение поля ввода не требуется, то в методе следует вернуть null
.В конструктор нашего фильтра будем передавать количество символов до и после десятичного разделителя.
Конструктор фильтра
public NumberInputFilter(int digsBeforeDot, int digsAfterDot) {
this.digsBeforeDot = digsBeforeDot;
this.digsAfterDot = digsAfterDot;
}
Переопределяем метод
filter
. Алгоритм проверки вводимых значений следующий:- До помещения буферного значения в поле ввода мы будем вставлять это значение в некоторую переменную.
- Если полученное значение удовлетворяет нашим требованиям точности, то мы разрешаем ввод, вернув
null
. - В противном случае возвращаем пустую строку, тем самым отказываясь от вводимого значения.
Формируем будущее значение после ввода пользователя:
Для удобства манипуляция со строкой, создадим экземпляр класса
StringBuilder
на основе исходного значения поля ввода и произведем необходимые замены с учетом вводимого значения.StringBuilder newText = new StringBuilder(dest).replace(dstart, dend, source.toString());
В результате в переменной
newText
будет содержаться будущее значения текстового поля.Теперь нам следует проверить корректность нового значения.
Оно должно удовлетворять следующим условиям:
- Количество десятичных разделителей не должно превышать 1.
123, 123.45 — верно, 12.3.4 — неверно. - Количество символов целой и дробной частей числа должно удовлетворять заданной точности.
Проверим количество разделителей, для этого мы переберем все символы результирующей строки.
Если найдется десятичный разделитель, то запомним его индекс. В случае повторного нахождения разделителя, ввод считается некорректным и цикл останавливается.
поиск десятичного разделителя
int size = newText.length();
int decInd = -1; // индекс десятичного разделителя
// проверяем десятичный разделитель
// количество разделителей не должно превышать 1
for (int i = 0; i < size; i++) {
if (newText.charAt(i) == '.') {
if (decInd < 0) {
decInd = i; // запоминаем индекс разделителя
} else { // разделителей более 1, некорректный ввод
isValid = false;
break;
}
}
}
Проверим корректность самого числа. Нам уже известен индекс десятичного разделителя, либо он отсутствует.
Остается только сравнить длину всего числа относительно индекса разделителя.
Проверка точности числа
if (decInd < 0) { // случай когда разделителя нет
if (size > integerSize) { // проверяем длину всего числа
isValid = false;
}
} else if (decInd > digsBeforeDot) {// проверяем длину целой части
isValid = false;
} else if (size - decInd - 1 > digsAfterDot) { // проверяем длину дробной части
isValid = false;
}
В завершении возвращаем результат.
Если ввод корректный, то возвращаем
null
, тем самым принимая вводимые значения, в противном случае возвращаем пустую строку, чтобы не передавать в поле ввода значения.
if (isValid) {
return null;
} else {
return "";
}
Полный исходный код@github
Отладка
Запускаем проект и вводим наш контрольный пример, пытаясь выйти за его границы.
В качестве контрольного примера будем использовать число 123.45. Нельзя вводить число более 999.99 и менее 0.01.
Видео

Видим, что приложение корректно обрабатывает целую и дробную части числа, запрещает вводить несколько десятичных разделителей.
Однако наблюдается интересная ситуация, если удалить десятичный разделитель, то мы выходим за границы точности.
Об этом упоминается в документации
Be careful to not to reject 0-length replacements, as this is what happens when you delete text.
Нужно быть осторожным с заменами нулевой длины, как это происходит при удалении текста.
Что же произошло на самом деле?
Наш алгоритм отработал корректно и в условии проверки длины целой части числа выявил, что допустимая точность нарушена и вернул пустую строку. Но само действие удаления символов было применено к исходному значению поля ввода.
Решим эту проблему следующим образом — выявим факт удаления символов. Документация подсказывает, что в этом случае происходит замена с нулевой длиной. Выделим из исходного значения поля ввода удаляемые символы и вернем их в качестве результата нашего метода, тем самым откажемся от удаления.
Нам потребуется изменить блок кода, где возвращается результат
Обработка удаления символов
if (isValid) {
return null;
} else if (source.equals("")) { // обрабатываем удаление
return dest.subSequence(dstart, dend); // возвращаем удаленные символы
} else {
return "";
}
Запускаем проект и проверяем.
Видео

Таким образом контрольный пример выполнен, заданная точность достигнута.
Библиотека успешно используется в продакшене.
Подключить её можно с помощью Gradle
repositories {
maven { url "https://raw.githubusercontent.com/hyperax/Android-NumberEditText/master/maven-repo" }
}
compile 'ru.softbalance.widgets:NumberEditText:1.1.2'
Полный исходный код проекта@github.
Бонус
Часто бывает необходимым запретить отображение системной клавиатуры для поля ввода.
Например, если используется клавиатура из приложения.
На помощь приходит метод класса EditText, появившийся в Android версии 21 и выше:
setShowSoftInputOnFocus(boolean show)
Вообще-то, этот метод был и в ранних версиях, но был приватным.
добавим свой метод для поля ввода showSoftInputOnFocusCompat
public void showSoftInputOnFocusCompat(boolean isShow) {
showSoftInputOnFocus = isShow;
if (Build.VERSION.SDK_INT >= 21) {
setShowSoftInputOnFocus(showSoftInputOnFocus);
} else {
try {
final Method method = EditText.class.getMethod("setShowSoftInputOnFocus", boolean.class);
method.setAccessible(true);
method.invoke(this, showSoftInputOnFocus);
} catch (Exception e) {
// ignore
}
}
}
Да это «хак», однако это наиболее простой и короткий способ скрыть системную клавиатуру для поля ввода.
Комментарии (8)
lavelas
11.08.2015 19:31+1Проверка точности числа выглядит ужасно. if if elseif elseif…
Хотя бы в один блок записали
if ((decInd < 0 && size > integerSize) || (decInd > digsBeforeDot) || (size - decInd - 1 > digsAfterDot)) { isValid = false; }
Andruhon
12.08.2015 00:08Хорошо и будет ещё лучше, если вы потратите еще немного времени и дополните README на github. Как использовать, куда записывать указанную конфигурацию, можно ли использовать этот элемент в XML, какая minSdk?
kemsky
12.08.2015 01:42Было думал написать инпут фильтр для ввода количества денег(евро с центами), но бажные примеры смутили, отложил на потом.
withkittens
Не вмешивайтесь в процесс ввода информации. Если у вас есть ограничения на вводимые данные, помейчайте поле ошибочным, но не меняйте UX.
mairos
это верно лишь в общем случае, но, к примеру, попробуйте в любом калькуляторе ввести второй разделитель. Да и у Google в гайдах есть примеры ввода чего угодно, только не дробных чисел :-)
withkittens
Эти калькуляторы имитируют своих железных собратьев, когда ввод осуществляется преимущественно с помощью кнопок.
А тут, у автора, полноценный текстбокс с курсором и клавиатурой, по крайней мере, поле пытается казаться таковым.
Потом представьте такой несколько надуманный, но возможный кейс: я ввёл с бумажки 123,45. Присмотрелся: ой, на самом деле это 12,345. Я вижу обычное с виду текстовое поле, в котором я бы попытался исправить ошибку следующим образом: удалить точку, сместиться влево, вписать точку. Я не уверен, что решение автора мне это позволит: вдруг там вбита точность 4 цифры перед точкой, т.е. оно не позволит мне удалить точку. Придётся выполнять множество лишних действий.
mairos
Может не позволить, Вы правы. Есть ещё пара жёстких вариантов — можно стирать первый разделитель при постановке второго (я обычно при виде таких фокусов
вспоминаю родственников этих проектировщиков UXвежливо ругаюсь :-D ) или сделать визуальную маску, чтобы знаки до разделителя и после вводились в разные поля.На самом деле всё от модели использования зависит, конечно. У автора, насколько мне известно, это практически всегда короткие числа с одним-двумя знаками после запятой, а ввод с самодельной числовой клавиатуры — не надо ждать, пока ОС свою откроет/закроет.
Идеалогически мне Ваш вариант больше всего нравится, но в системах ввода «а-ля калькулятор» он пока не прижился.