In this article I would like to highlight the question of the user entering numbers with a given accuracy.
Let's see what is in the arsenal of Android and solve this problem.
Let's start with the requirements
Primary requirements:
- Implement an input field that allows you to enter only numbers with specified accuracy.
- Accuracy is given by the number of significant digits before and after the decimal point.
Additional requirements:
- Support cursor input.
- Standard editing options: copy, cut and paste.
Analysis
From the entire range of controls in
Android
we are interested in the
EditText
widget.
Referring to the documentation and see what the Android SDK offers us.
EditText
inherited from
TextView
, which, in turn, has properties:
XML markupdigits
- allows you to set a set of special characters that the field can accept and automatically turns on the number entry mode.
numeric
- sets the input handler numbers.
inputType
- using a set of constant values allows you to generate the required input handler.
Public class methodssetFilters
- allows you to specify a set of filters that will be applied when entering values in the field.
')
digits
,
numeric
and
inputType
allow you to limit the set of input characters, but do not affect the accuracy of the number, but
setFilters
is exactly what we need.
Implementation
Before creating your own filter, we will make it so that only numbers and the decimal separator can be entered in the field.
numberEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
Now we will set our own filter for the input field to accept only the values corresponding to the specified accuracy.
We will need to implement the
InputFilter
interface, and specifically override the
filter
method.
According to the documentation , this method is called when replacing the value in the input field (dest) in the range from dstart to dend with text from the buffer (source) in the range from start to end.
An example for understanding the aboveThe input field contains 123456 (dest), the cursor is at 1 [23] 456 (dstart = 1, dend = 3), the value 789 is inserted from the buffer (source = 789, start = 0, end 3).
The method returns an object of the
CharSequence
class, which will be set in the input field instead of the current value. If you do not need to replace the value of the input field, then the method should return
null
.
In the constructor of our filter, we will transmit the number of characters before and after the decimal separator.
Filter constructor public NumberInputFilter(int digsBeforeDot, int digsAfterDot) { this.digsBeforeDot = digsBeforeDot; this.digsAfterDot = digsAfterDot; }
Override the
filter
method. The algorithm for checking input values is as follows:
- Before placing the buffer value in the input field, we will insert this value into some variable.
- If the resulting value meets our accuracy requirements, then we allow input by returning
null
. - Otherwise, we return an empty string, thereby refusing the input value.
We form the future value after user input:
For convenience, manipulate the string, create an instance of the
StringBuilder
class based on the initial value of the input field and make the necessary replacements based on the entered value.
StringBuilder newText = new StringBuilder(dest).replace(dstart, dend, source.toString());
As a result, the
newText
variable will contain the future values of the text field.
Now we need to check the correctness of the new value.
It must satisfy the following conditions:
- The number of decimal separators should not exceed 1.
123, 123.45 - right, 12.3.4 - wrong. - The number of characters of the integer and fractional parts of a number must satisfy the specified accuracy.
Check the number of delimiters, for this we iterate over all the characters in the resulting string.
If there is a decimal separator, then remember its index. In the case of re-finding the separator, the input is considered incorrect and the cycle stops.
decimal delimiter search int size = newText.length(); int decInd = -1;
Check the correctness of the number itself. We already know the decimal separator index, or it is missing.
It remains only to compare the length of the whole number relative to the separator index.
Check the accuracy of the number At the end we return the result.
If the input is correct, then we return
null
, thereby taking the input values,
otherwise, return an empty string so as not to pass in the value input field.
if (isValid) { return null; } else { return ""; }
Full source code @ githubDebugging
We start the project and enter our test case, trying to go beyond its borders.
As a control example, we will use the number 123.45. You can not enter a number greater than 999.99 and less than 0.01.
We see that the application correctly processes the integer and fractional parts of the number, prohibits the introduction of several decimal separators.
However, there is an interesting situation, if we delete the decimal separator, then we go beyond the limits of accuracy.
This is mentioned in the documentation.
Be careful to replace text.
You need to be careful with zero-length replacements, as happens when deleting text.
What really happened?
Our algorithm worked correctly and in the condition of checking the length of the integer part of the number revealed that the permissible accuracy was violated and returned an empty string. But the act of deleting characters was applied to the original value of the input field.
We will solve this problem as follows - we will reveal the fact of deletion of symbols. Documentation suggests that in this case there is a replacement with zero length. Select the characters to be deleted from the initial value of the input field and return them as a result of our method, thereby discarding the deletion.
We will need to change the code block where the result is returned.
Character removal processing if (isValid) { return null; } else if (source.equals("")) {
Run the project and check.
Thus, the test case is executed, the specified accuracy is achieved.
The library is successfully used in production.
You can connect it using Gradle
repositories { maven { url "https://raw.githubusercontent.com/hyperax/Android-NumberEditText/master/maven-repo" } } compile 'ru.softbalance.widgets:NumberEditText:1.1.2'
Full source code for the @ github project .
Bonus
It is often necessary to prohibit the display of the system keyboard for the input field.
For example, if you use the keyboard from the application.
The class method EditText, which appeared in Android version 21 and higher, comes to the rescue:
setShowSoftInputOnFocus (boolean show)In fact, this method was in earlier versions, but was private.
add your method for the showSoftInputOnFocusCompat input fieldpublic 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) {
Yes, this is a "hack", but this is the easiest and shortest way to hide the system keyboard for the input field.