今天測試提了一個bug顶掉,在小米手機上草娜,就是一個自定義的密碼框,輸錯密碼痒筒,想要刪除宰闰,按軟鍵盤的刪除鍵,卻發(fā)現(xiàn)沒有反應(yīng)簿透,但是在其他的手機上卻可以刪除移袍。這一聽就頭大,很明顯的系統(tǒng)不同造成的老充。但是bug提了葡盗,能怎么辦,使出獨門絕學(xué)百度大法啡浊。
一般正常的我們監(jiān)聽軟鍵盤的回調(diào)就好了觅够。如下:
editText.setSoftKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent keyEvent) {
if ((keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL ) && keyEvent.getAction() == KeyEvent.ACTION_DOWN && mCodes.size() > 0) {
mCodes.remove(mCodes.size() - 1);
showCode();
return true;
}
return false;
}
});
但是在小米手機上這種方式不起效。經(jīng)過百度查的巷嚣,有些文章也說在谷歌輸入法上也不其效果喘先。
/**
* Register a callback to be invoked when a hardware key is pressed in this view.
* Key presses in software input methods will generally not trigger the methods of
* this listener.
* @param l the key listener to attach to this view
*/
public void setOnKeyListener(OnKeyListener l) {
getListenerInfo().mOnKeyListener = l;
}
/**
* 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.
*
* @param v The view the key has been dispatched to.
* @param keyCode The code for the physical key that was pressed
* @param event The KeyEvent object containing full information about
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onKey(View v, int keyCode, KeyEvent event);
}
注釋的大概意思是這個監(jiān)聽器是用于監(jiān)聽實體鍵的key event的,雖然輸入法也可以發(fā)出key event廷粒,但是這種事是看緣分的窘拯。比如搜狗輸入法就是基于keyEvent和EditText交互的,但谷歌輸入法就不會發(fā)出keyEvent來告知EditText有輸入事件坝茎,所以用這個監(jiān)聽器來監(jiān)聽軟鍵盤的輸入和點擊事件是不靠譜的涤姊。
谷歌輸入法是通過InputConnection類,InputConnection 是輸入法和應(yīng)用內(nèi)View(通常是EditText)交互的通道景东,輸入法的文本輸入和刪改事件砂轻,包括key event事件都是通過InputConnection發(fā)送給EditText。
InputConnection有幾個關(guān)鍵方法斤吐,通過重寫這幾個方法,我們基本可以攔截軟鍵盤的所有輸入和點擊事件:
//當(dāng)輸入法輸入了字符厨喂,包括表情和措,字母、文字蜕煌、數(shù)字和符號等內(nèi)容派阱,會回調(diào)該方法
public boolean commitText(CharSequence text, int newCursorPosition)
//當(dāng)有按鍵輸入時,該方法會被回調(diào)斜纪。比如點擊退格鍵時贫母,搜狗輸入法應(yīng)該就是通過調(diào)用該方法文兑,
//發(fā)送keyEvent的,但谷歌輸入法卻不會調(diào)用該方法腺劣,而是調(diào)用下面的deleteSurroundingText()方法绿贞。
public boolean sendKeyEvent(KeyEvent event);
//當(dāng)有文本刪除操作時(剪切,點擊退格鍵)橘原,會觸發(fā)該方法
public boolean deleteSurroundingText(int beforeLength, int afterLength)
//結(jié)束組合文本輸入的時候籍铁,回調(diào)該方法
public boolean finishComposingText();
當(dāng)輸入法要和指定View建立連接的時候趾断,系統(tǒng)會通過該方法返回一個InputConnection 實例給輸入法拒名。所以我們要復(fù)寫EditText的這個方法,返回我們自己的InputConnection 芋酌。其原理圖如下:
所以需要自己重寫一個EditText.
public class WiseEditText extends AppCompatEditText {
private OnKeyListener keyListener;
public WiseEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public WiseEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WiseEditText(Context context) {
super(context);
}
@Nullable
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new MyInputConnection(super.onCreateInputConnection(outAttrs),true);
}
private class MyInputConnection extends InputConnectionWrapper {
public MyInputConnection(InputConnection target, boolean mutable) {
super(target, mutable);
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
if (keyListener != null){
keyListener.onKey(WiseEditText.this,event.getKeyCode(),event);
}
return super.sendKeyEvent(event);
}
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
if (beforeLength == 1 && afterLength == 0) {
// backspace
return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
/* && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL))*/;
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
}
//設(shè)置監(jiān)聽回調(diào)
public void setSoftKeyListener(OnKeyListener listener){
keyListener = listener;
}
}
然后在自定義的密碼框中去監(jiān)聽回調(diào):
// 監(jiān)聽驗證碼刪除按鍵
editText.setSoftKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent keyEvent) {
if ((keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL ) && keyEvent.getAction() == KeyEvent.ACTION_DOWN && mCodes.size() > 0) {
mCodes.remove(mCodes.size() - 1);
showCode();
return true;
}
return false;
}
});