Recently, there is a requirement to limit the number of characters that users can input, including 2 Chinese characters and 1 non-Chinese characters. For example, “one person” can be counted as 5 characters. When users input more than the word limit, they can intercept and remind users with toast.

First of all, we implement a method to detect Chinese characters. There are many methods on the Internet, mainly to detect the unicode value range of characters:

fun isChinese(c: Char): Boolean { return c.toInt() in 0x4E00.. 0x9FA5 }Copy the code

If it is Chinese, count 2, otherwise count 1, function implementation is as follows:

fun getCharTextCount(c: Char) = if (Utils.isChinese(c)) 2 else 1
Copy the code

Based on the above rules, the function that checks the number of words in a string is implemented as follows:

@jvmstatic fun calcTextLength(charSequence: charSequence?); : Int { if (charSequence.isNullOrEmpty()) { return 0; } var sum = 0 for (c in charSequence) { sum += Utils.getCharTextCount(c) } return sum }Copy the code

This code is in utils.java as the project’s functional utility class. The following lists the various implementations and compares them.

1. Use InputFilter to limit the word count

To implement InputFilter, you need to override a method called filter.

Public abstract CharSequence filter (CharSequence source, // input text int start, // input text start position int end, Spanned dest, int dstart, int dend, Spanned dest, int dend, int dend, Spanned dest, int dstart, int dendCopy the code

Filter (dest) {filter (dest) {filter (dest) {filter (dest) {filter (dest); Then implement this function:

class TextLengthFilter(private val maxLength: Int = Utils.MAX_LENGTH, val listener: TextLengthListener? = null) : InputFilter { override fun filter( source: CharSequence? , start: Int, end: Int, dest: Spanned? , dstart: Int, dend: Int ): CharSequence { if (source.isNullOrEmpty()) { return "" } // bug fixed. // val source: CharSequence = source.subSequence(start, end) var sum = Utils.calcTextLength(dest as CharSequence, dstart, dend) + Utils.calcTextLength(source) - maxLength if (sum > 0) { val delete = Utils.getDeleteIndex(source, 0, source.length, sum) listener? .onTextLengthoutofLimit () if (delete > 0) source.subsequence (0, delete) else ""} Return source}}Copy the code

We use utils.calcTextLength (source: CharSequence, dstart: Int, dend: Int) to count the number of characters in the string other than [dstart,dend], because characters in the range [dstart,dend] are replaced. Add InputFilter listener to the code to implement the function:

edit_inputfilter.filters = arrayOf(TextLengthFilter(listener = MainActivity@ this))
Copy the code

2. Use TextWatcher to limit the word count

Using TextWather to listen for character changes in EditText, we need to implement three abstract methods:

  • BeforeTextChanged (CharSequence S, int start, int count, int after) S: Changes the previous text. Start: the position in the string to be modified. Count: The length of the text in the string to be modified. If it is new, it is 0. After: Indicates the modified length of the modified text. 0 if it is deleted.
  • OnTextChanged (CharSequence s, int start, int before, int count) s: changed string start: changed string sequence number before: The value is 0 if the string is new. Count: The string length is added. The value is 0 if the string is deleted.
  • AfterTextChanged (Editable s) S: modified text

The comments above are clear. For example, in the beforeTextChanged callback, we know the position of the insert character start, the number of inserts after, and the number of replacements count, which are similar to the meanings of the parameters in the InputFilter. Implement these functions:

class TextLengthWatcher(private val maxLength: Int = Utils.MAX_LENGTH, val listener: TextLengthListener? = null) : TextWatcher { private var destCount: Int = 0 private var dStart: Int = 0 private var dEnd: Int = 0 override fun afterTextChanged(s: Editable) {// count is the entered length val count = utils.calctextLength (s) if (count > maxLength) {// If sum is exceeded, Var sum = count -maxlength val delete = utils.getDeleteIndex (s, dStart, dEnd, sum) listener? .onTextLengthoutofLimit () if (delete < dEnd) {// Input characters exceed the limit, Override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {destCount = utils. calcTextLength(s) // Obtain the start position of the input character dStart = start // Obtain the number of input characters dEnd = start + after} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { } }Copy the code

Note that modifying text in TextWatcher (Editable. Delete, EditText. SetText, etc.) should be careful not to fall into an endless loop. Text changes -> Watcher receives notification ->setText-> Text changes -> Watcher receives notification ->… . So we add an end condition count > maxLength before modifying the text. Add a TextWatcher listener to your code to do this:

edit_textwatcher.addTextChangedListener(TextLengthWatcher(listener = MainActivity@ this))
Copy the code

3. Use InputConnection to limit words

InputConnection is the channel through which an input method and an in-application View (usually EditText) interact. Text input and deletion events, including key events, of the input method are sent to the EditText through InputConnection. The schematic diagram is as follows:



InputConnection has several key methods, and by overriding these methods we can basically block all input and click events on the soft keyboard:

Public Boolean commitText(CharSequence text, int newCursorPosition) public Boolean commitText(CharSequence text, int newCursorPosition) The method is called back. For example, when you click the beat grid key, sogo input method should call this method, // send keyEvent, but Google input method does not call this method, but calls the following deleteSurroundingText() method. public boolean sendKeyEvent(KeyEvent event); Public Boolean deleteSurroundingText(int beforeLength, int afterLength) public Boolean deleteSurroundingText(int beforeLength, int afterLength) Public Boolean finishComposingText();Copy the code

As you can see, we can intercept user input with commitText. The method for setting InputConnection is in the EditText class, so we inherit the EditText to define a custom TextLengthEditText. It is expensive to rewrite InputConnection completely, so we can inherit InputConnectionWrapper:

inner class TextLengthInputConnecttion( val target: InputConnection, private val maxLength: Int = Utils.MAX_LENGTH, val listener: TextLengthListener? = null ) : InputConnectionWrapper(target, false) { override fun commitText(source: CharSequence, newCursorPosition: Int): Boolean { val count = Utils.calcTextLength(source) val destCount = Utils.calcTextLength(text as CharSequence, SelectionStart, selectionEnd) if (count + destCount > maxLength) Var sum = count + destCount -maxLength val delete = utils.getDeleteIndex (source, 0, source.length, sum) listener? .onTextLengthoutofLimit () // Input characters exceed the limit, Return super.mittext (if (delete > 0) source.subSequence(0, delete) else "", newCursorPosition) } return super.commitText(source, newCursorPosition) } }Copy the code

We define the TextLengthInputConnecttion into inner class, such ability can access external members of the class. Finally, you need to set the InputConnection by overriding the EditText onCreateInputConnection method:

override fun onCreateInputConnection(outAttrs: EditorInfo?) : InputConnection { return TextLengthInputConnecttion(super.onCreateInputConnection(outAttrs), listener = TextLengthEditText@ this) }Copy the code

Add the custom TextLengthEditText directly to the Layout XML file.

conclusion

This article introduces three methods to limit the number of characters. Each method has its own advantages and disadvantages. After all, we also need to consider future extensions. Finally, the summary and comparison of the methods are as follows:

\ advantages disadvantages
InputFilter Can detect text input, delete Unable to detect key input
TextWatcher Can detect text input, delete Key input cannot be detected, only after the input changes, resulting in callback methods that may be executed multiple times
InputConnection Can detect text input, delete, can block key input, before InputFilter, TextWatcher When implemented, you have to customize the EditText, which is cumbersome

Code has been uploaded to Github address, welcome star.

reference