📜 ⬆️ ⬇️

Android numeric entry field

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:

Additional requirements:

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 markup
digits - 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 methods
setFilters - 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 above
The 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:
  1. Before placing the buffer value in the input field, we will insert this value into some variable.
  2. If the resulting value meets our accuracy requirements, then we allow input by returning null .
  3. 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:
  1. The number of decimal separators should not exceed 1.
    123, 123.45 - right, 12.3.4 - wrong.
  2. 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; //    //    //      1 for (int i = 0; i < size; i++) { if (newText.charAt(i) == '.') { if (decInd < 0) { decInd = i; //    } else { //   1,   isValid = false; break; } } } 


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
 if (decInd < 0) { //     if (size > integerSize) { //     isValid = false; } } else if (decInd > digsBeforeDot) {//     isValid = false; } else if (size - decInd - 1 > digsAfterDot) { //     isValid = false; } 


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 @ github

Debugging


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.
Video


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("")) { //   return dest.subSequence(dstart, dend); //    } else { return ""; } 


Run the project and check.
Video


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 field
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 } } } 


Yes, this is a "hack", but this is the easiest and shortest way to hide the system keyboard for the input field.

Source: https://habr.com/ru/post/264493/


All Articles