android 按键监听及键盘事件流(没法监听删除键)

android 按键监听及键盘事件流(没法监听删除键)

最近在作一个密码按键输入功能时须要对每次按键进行一些处理,因而使用了 OnKeyListener 接口监听,对于正常文本格式的输入按键事件都能监听到,可是一旦修改 EditText 的输入类型为 NumbberPassword(android:inputType="numberPassword") 则没法监听到键盘的删除按钮事件。java

因而查阅资料:android

通常使用 OnKeyListener 接口监听按键事件以下:

editText.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if(event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_DEL:
                        // 处理相关退格键行为
                        return true;
                    ...
                }
            }
            return false;
        }
    });

上面这个这个方案在大多数状况下都没有问题,可是当使用 android:inputType="numberPassword" 时事件并未获得响应。因而翻看了关于 OnKeyListener 的注释:

/**
     * Interface definition for a callback to be invoked when a hardware key event is
     * dispatched to this view. The callback will be invoked before the key event is
     * given to the view. This is only useful for hardware keyboards; a software input
     * method has no obligation to trigger this listener.
     */
    public interface OnKeyListener {
        /**
         * Called when a hardware key is dispatched to a view. This allows listeners to
         * get a chance to respond before the target view.
         * <p>Key presses in software keyboards will generally NOT trigger this method,
         * although some may elect to do so in some situations. Do not assume a
         * software input method has to be key-based; even if it is, it may use key presses
         * in a different way than you expect, so there is no way to reliably catch soft
         * input key presses.
         */
        boolean onKey(View v, int keyCode, KeyEvent event);
    }

类注释大概的意思是:硬件按键会必定会回调这个接口,这仅对硬件键盘有用。软件输入法没有义务触发此侦听器。
方法的注释大概意思是:硬件的key会派发到这里,但软件键盘中的按键一般不会触发此方法。尽管某些状况下可能会选择这样作,不要假设软件输入法必须基于密钥。即便是这样,它也可能以与您预期不一样的方式使用按键,所以没法可靠地捕获软输入按键。(意思就是这个监听对软键盘来讲并不可靠)。既然不可靠那么经过什么方式是谷歌推荐的呢,经过查阅资料得知。安全

InputConnection 接口

/**
 * The InputConnection interface is the communication channel from an
 * {@link InputMethod} back to the application that is receiving its
 * input. It is used to perform such things as reading text around the
 * cursor, committing text to the text box, and sending raw key events
 * to the application.
 ....
 */
public interface InputConnection {
    ...
}

从上面注释得知:InputConnection接口是从{@link InputMethod}返回到正在接收其输入的应用程序的通讯通道。它用于执行诸如读取光标周围的文本,将文本提交到文本框以及将原始键事件发送到应用程序之类的事情。
事实上谷歌的键盘输入流式这样完成的:
avatar
InputConnection有几个关键方法,经过重写这几个方法,咱们基本能够拦截软键盘的全部输入和点击事件:app

//当输入法输入了字符,包括表情,字母、文字、数字和符号等内容,会回调该方法
public boolean commitText(CharSequence text, int newCursorPosition) 

//当有按键输入时,该方法会被回调。好比点击退格键时,搜狗输入法应该就是经过调用该方法,
//发送keyEvent的,但谷歌输入法却不会调用该方法,而是调用下面的deleteSurroundingText()方法。  
public boolean sendKeyEvent(KeyEvent event);   

//当有文本删除操做时(剪切,点击退格键),会触发该方法 
public boolean deleteSurroundingText(int beforeLength, int afterLength) 

//结束组合文本输入的时候,回调该方法
public boolean finishComposingText();

那么 InputConnection 如何与 EditText 创建关联的呢?

实际上在EditText和输入法创建链接的时候,EditText的onCreateInputConnection()方法会被触发:ide

/**
     * Create a new InputConnection for an InputMethod to interact
     * with the view.  The default implementation returns null, since it doesn't
     * support input methods.  You can override this to implement such support.
     * This is only needed for views that take focus and text input.
     *
     * <p>When implementing this, you probably also want to implement
     * {@link #onCheckIsTextEditor()} to indicate you will return a
     * non-null InputConnection.</p>
     *
     * <p>Also, take good care to fill in the {@link android.view.inputmethod.EditorInfo}
     * object correctly and in its entirety, so that the connected IME can rely
     * on its values. For example, {@link android.view.inputmethod.EditorInfo#initialSelStart}
     * and  {@link android.view.inputmethod.EditorInfo#initialSelEnd} members
     * must be filled in with the correct cursor position for IMEs to work correctly
     * with your application.</p>
     */
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        return null;
    }

注释只贴了核心部分大概意思就是:为InputMethod建立一个新的InputConnection以便与视图进行交互。默认实现返回null,由于它不支持输入法。您能够覆盖它以实现这种支持。仅对于具备焦点和文本输入的视图才须要。
在实现此功能时,您可能还但愿实现onCheckIsTextEditor()来指示您将返回非null的InputConnection。
另外,请务必正确且完整地填写EditorInfo对象,以使链接的IME能够依赖其值。例如,必须使用正确的光标位置填充initialSelStart和initialSelEnd成员,IME才能正确地与您的应用程序一块儿使用。this

也就是说咱们只须要实现这个方法并给一个实现接口的返回咱们就能够接管键盘输入了。这个方法是 View 的方法,扩展下想象力,就是任何View均可以去响应按键的。那这里咱们就能够直接使用了么?并不能由于接口并无提供常规处理,若是彻底本身实现,咱们须要完成其余按键相关处理,工做量仍旧巨大。那么EditText具有这个功能那么应该也是有实现的吧,实时上是的。在 TextView 中就提供了 EditableInputConnection 类来处理输入,可是他是 hide 的没法被继承,可能出于安全角度考虑,因此就没有办法了么?其实google为咱们提供了一个类 InputConnectionWrapper 一个默认代理类,完成了大部分常规的操做,咱们能够继承这个类来针对本身想要的部分实现替换。google

/**
 * <p>Wrapper class for proxying calls to another InputConnection.  Subclass and have fun!
 */
public class InputConnectionWrapper implements InputConnection {
    ...
}

注释解释:包装器类,用于代理对另外一个InputConnection的调用。子类,玩得开心!(google工程师仍是很幽默的)
到这里咱们就能够经过实现这个类来完成键盘的拦截监听了。spa

/**
 * 始终从尾部输入的编辑文本控件
 */
public class TailInputEditText extends AppCompatEditText {

    public TailInputEditText(Context context) {
        this(context, null);
    }

    public TailInputEditText(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.editTextStyle);
    }

    public TailInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSelectionChanged(int selStart, int selEnd) {
        super.onSelectionChanged(selStart, selEnd);
        if (selStart == selEnd){//防止不能多选
            if(getText() == null){//判空,防止出现空指针
                setSelection(0);
            }else {
                setSelection(getText().length()); // 保证光标始终在最后面
//                setSelection(0, getText().length());
            }
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 其余按键事件响应
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        // 返回本身的实现
        return new BackspaceInputConnection(super.onCreateInputConnection(outAttrs), true);
    }

    private class BackspaceInputConnection extends InputConnectionWrapper {

        public BackspaceInputConnection(InputConnection target, boolean mutable) {
            super(target, mutable);
        }

        /**
         * 当软键盘删除文本以前,会调用这个方法通知输入框,咱们能够重写这个方法并判断是否要拦截这个删除事件。
         * 在谷歌输入法上,点击退格键的时候不会调用{@link #sendKeyEvent(KeyEvent event)},
         * 而是直接回调这个方法,因此也要在这个方法上作拦截;
         * */
        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
            // 作你想作的是拦截他
            return super.deleteSurroundingText(beforeLength, afterLength);
        }

    }

}

以上就是一个包含了拦截器并与控件关联的实现,固然你也能够不用内部类来完成,我只是简单的描述一下。代理

到这里键盘事件的拦截问题告一小段落,有什么其余的想法能够留言讨论。

相关文章
相关标签/搜索