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);
        }

    }

}

以上就是一個包含了攔截器並與控件關聯的實現,固然你也能夠不用內部類來完成,我只是簡單的描述一下。代理

到這裏鍵盤事件的攔截問題告一小段落,有什麼其餘的想法能夠留言討論。

相關文章
相關標籤/搜索