2

I have a class MyEditText that extends EditText. When receiving a specific keycode from an InputMethodService, say 35 which is equivilant of '#', I want to perform an action on my EditText instead of appending it to the text.

Solutions that did not work:

  1. Setting an onEditorActionListener does not help since onEditAction() is called only for a specific set of codes such as KeyEvent.KEYCODE_ENTER, and I need to detect the event of receiving a code out of that set.

  2. Setting an onKeyListener. This also did not work but I don't know the reason and I am not even sure this listener is meant for this purpose. This is the code I tried and it doesn't log anything when typing:

    edittext.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            Log.d("EDITOR_TAG", "onKey()");
            return false;
        }
    });
    

    Edit: after further testing the onKey() also gets called for KeyEvent.KEYCODE_ENTER but not other key codes. Just like onEditorAction().

  3. A workaround that might work but I prefer to avoid: implementing beforeTextChanged(), onTextChanged() and afterTextChanged() of the TextWatcher interface. The reason I want to avoid this is that it will interfere with a history mechanism implemented in those methods (for undo/redo). I want to consume the event of receiving a key code 35 before it even changes the text.

tba
  • 333
  • 3
  • 15
  • what do you mean by *receiving a key code 35 before it even changes the text*, How can you get the text before it changes ? Does it mean same as `beforeTextChanged()` of TextWatcher ? – Shree Krishna Jun 14 '16 at 11:21
  • These 3 methods of `TextWatcher` are should only be called if the text changes. If you catch the event, in `onEditorAction()` for example, and return true which means you're done handling the event, these methods will not be called. Unfortunately it works only for a specific set of characters as stated in the question. – tba Jun 14 '16 at 12:07

2 Answers2

0

Try This wrapper class, hope it will solve your problem :

public class CustomEditText extends EditText {

public CustomEditText(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public CustomEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomEditText(Context context) {
    super(context);
}


@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
    if (lengthBefore < lengthAfter) {
        char c = text.charAt(start + lengthAfter - 1);
        if (c == '#') {
            char[] newChars = new char[text.length() - 1];
            for (int i = 0, j = 0; i < text.length(); i++) {
                if (i != (start + lengthAfter - 1))
                    newChars[j++] = text.charAt(i);
            }
            setText(new String(newChars));
        }
    }
}

}

As per my rnd I didn't find any direct method of doing this without TextWatcher. However I found a workaround, It is working fine but need little modification as per your custom requirements.

Hope it will work for you :)

Neo
  • 3,546
  • 1
  • 24
  • 31
  • As I already stated this solution did not work for me. I added the actual code now, maybe you will find something wrong in it. – tba Jun 14 '16 at 11:59
  • I added the results of this attempt to the question. Did you test it yourself? – tba Jun 14 '16 at 12:54
  • @tba try the updated code, It should work perfect :) – Neo Jun 14 '16 at 13:00
  • Looks interesting. I didn't think of implementing an inner class. I'll try it and report the results soon. – tba Jun 14 '16 at 13:28
  • Sure, looking for positive answer :) – Neo Jun 14 '16 at 13:50
  • Weirdly, this also only only catches the event with `KeyEvent.KEYCODE_ENTER`. I took exactly your code and changed the `isEscapeKey()` to always return true and added logging in `sendKeyEvent()`. When I pressed enter on the keyboard it logged an event with keycode 66. Any other keyboard presses did not log anything. – tba Jun 14 '16 at 14:01
  • Okay, Let me check!! – Neo Jun 14 '16 at 14:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/114652/discussion-between-neo-and-tba). – Neo Jun 14 '16 at 15:17
0

I was facing the exact same challenge and came up with a solution. Just copy paste the following class to your project:

OnTextChangedListener.class

import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;

public class OnTextChangedListener implements TextWatcher {

    private static final String TAG = OnTextChangedListener.class.getSimpleName();

    private boolean isManualChange = false;
    private EditText mEditText;
    private char[] consumeChars;

    public OnTextChangedListener(char[] consumeChars, EditText editText){
        mEditText = editText;
        if(consumeChars == null){
            this.consumeChars = new char[0];
        } else{
            this.consumeChars = consumeChars;
        }
    }

    public OnTextChangedListener(){
        this.consumeChars = new char[0];
    }

    @Override
    public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
        String newString = charSequence.toString();
        if(containsConsumeChar(newString)){
            newString = removeConsumeChars(newString);
            int cursorPos = mEditText.getSelectionStart(); // or getSelectionEnd() doesn't matter
            isManualChange = true;
            mEditText.setText(newString);
            int maxPos = newString.length();
            mEditText.setSelection(cursorPos > maxPos ? maxPos : cursorPos);
        } else if(before != count && !isManualChange){
            Log.d(TAG, "Text (actually) changed!");
            String text = charSequence.toString();

            // calls your own implementation if you have one
            onTextActuallyChanged(text, start, before, count);
        } else{
            Log.d(TAG, "consumed manual change");
            isManualChange = false;
        }
    }

    @Override
    public void afterTextChanged(Editable editable) {

    }

    public void onTextActuallyChanged(String newText, int start, int lengthBefore, int lengthNow){

    }

    public void onCharacterConsumed(char consumed){

    }

    private boolean containsConsumeChar(String text){
        for(char c : consumeChars){
            if(text.contains(""+c)) return true;
        }
        return false;
    }

    private String removeConsumeChars(String text){
        for(char c : consumeChars){
            if(text.contains(""+c)){
                Log.d(TAG, "consumed all '" + c + "'s");
                text = text.replaceAll(""+c, "");

                // calls your own implementation if you have one
                onCharacterConsumed(c);
            }
        }
        return text;
    }
}

Usage:

OnTextChangedListener consumeThis = new OnTextChangedListener(new char[]{'#'}, myEditText);
myEditText.addTextChangedListener(consumeThis);

or

myEditText.addTextChangedListener(new OnTextChangedListener(new  char['#'], myEditText){
    @Override
    public void onCharacterConsumed(char consumed) {
        // TODO: implement your magic here
    }
});