androidandroid-edittextandroid-inputtypeandroid-input-filter

android - EditText length filter not working as it should


First I have to say I have read similar questions and answers here on SO and this question is basically a duplicate of this question and many others but the answers given to those questions doesn't work like the way i want it.

The problem:

Setting length filter on my EditText programmatically like this:

editText.setFilters(new InputFilter[]{new LengthFilter(10)} );

The only thing it does is hide the text that go over the limit in the EditText. It still shows the long (unlimited) text in suggestion box and i have to delete (backspace) for each letter that go over before being able to delete what is shown in the EditText.

Suggested Solutions:

  1. Setting InputType to textFilter.

    Programmatically I did this:

    editText.setInputType( InputType.TYPE_TEXT_VARIATION_FILTER );
    

    It hides suggestions but the unlimited text is still present and i still have to use backspace to delete letters that shouldn't be present.

  2. Setting InputType to textNoSuggestions|textVisiblePassword.

    Programmatically I did this (had to add TYPE_CLASS_TEXT too otherwise it wouldn't work):

    editText.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD );
    

    This one does work but the problem is it stops "gesture typing" and it changes the font to monospace.

Better Solutions?
As you can see these two methods don't actually work without additional problems. Is there any other way of doing this that I missed. Should I just use a TextWatcher if I want to keep gesture typing and suggestions?


Solution

  • I ended up using a TextWatcher instead. I'm not sure if it is the best way to do this but it does work with suggestions and it doesn't turn off gesture typing or change the font style. Here's how I did it (I'm quite new to android so if this needs improvement feel free to let me know).

    I added an example in the comments to clarify what is going on.

    Make these global variables:

    private boolean mWatcherIsBlocked = false;
    private String mBeforeChange;
    private String mFilteredString; 
    private int mCursorPosition = 0;
    

    Then create the TextWatcher and add it to your EditText

    final int maxLength = 10; // desired length limit
    
    /** 
     * lets say our EditText is showing "abcdefgh". We select "cdef" from it and 
     * paste a new text "ijklmnop" in the middle. What we should get according to
     * our maxLength is this: 
     * (1) "ab" (0th up to the letter from before_change_text we were selecting) + 
     * (2) "ijklmn" (part of the text we pasted minus the number of letters the whole 
     *      after_change_text goes over the 10 letter limit) + 
     * (3) "gh" (last part of before_change_text that wasn't selected)
     * 
     * so the new text has to be "abijkmngh"
     */
    
    TextWatcher textWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // get before_change_text if textWatcher isn't blocked
            if (!mWatcherIsBlocked) mBeforeChange = s.toString();
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (!mWatcherIsBlocked){
                // get after_change_text if textWatcher isn't blocked
                String after = s.toString();
                // if after_change_text's length is bigger than the limit 
                if (after.length() > maxLength) {
                    // see how much it goes over the limit
                    int over = after.length() - maxLength;
                    // add parts (1) and (2) like our example above
                    String st = mBeforeChange.substring(0, start) + // (1)
                                after.substring(start, start + count - over); // (2)
                    // get where the cursor position should be after pasting (
                    // = after the last letter we could paste = length of (1) + (2) )
                    mCursorPosition = st.length();
                    // now add part (3) of our text to the first two
                    st += mBeforeChange.substring(
                              mBeforeChange.length() - (maxLength - st.length()), 
                              mBeforeChange.length());
                    // now assign this new text to a global variable
                    mFilteredString = st;
                } else { 
                    // if after_change_text hasn't gone over the limit assign it 
                    // directly to our global variable
                    mFilteredString = s.toString();
                }
            }
        }
        @Override
        public void afterTextChanged(Editable s) {
            // if filtered text is not the same as unfiltered text 
            // or textWatcher is not blocked
            if (!mFilteredString.equals(s.toString()) && !mWatcherIsBlocked) {
                // block textWatcher to avoid infinite loops created by setText 
                // (this might not work as I well as I think!)
                mWatcherIsBlocked = true;
                // set new text to our EditText
                editText.setText(mFilteredString);
                // set its cursor position 
                editText.setSelection(mCursorPosition);
                // unblock the textWatcher
                mWatcherIsBlocked = false;
            }
        }
    };
    
    // add the TextWatcher to our EditText
    editText.addTextChangedListener(textWatcher);