因为业务需要,有时候我们好监听软键盘向下的动作,当我们按下向下的按钮时,可以进行监听,从而执行相应的动作。
于是我们写下下面的代码
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK){
Log.i(TAG, "onKeyDown");
}
return super.onKeyDown(keyCode, event);
}
我们按下向下的按钮,滴,没反应。再按一次,滴,握草,直接退出了,这个时候才有 log 打印出来。这个 log 是退出当前 Activity, 而不是软键盘向下的。
如果没有想到好的解决方案,就问 google , 于是我们得到了解决方法,就是对 Edittext.onKeyPreIme() 方法重写
/**
* 拦截键盘向下的 EditTextView
*/
public class TextEditTextView extends EditText {
public TextEditTextView(Context context) {
super(context);
}
public TextEditTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TextEditTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == 1) {
Log.i("main_activity", "键盘向下 ");
super.onKeyPreIme(keyCode, event);
return false;
}
return super.onKeyPreIme(keyCode, event);
}
}
在布局中
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/activity_main"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<com.yxhuang.myapplication.TextEditTextView
android:id="@+id/edt_text"
android:layout_width="match_parent"
android:hint="测试"
android:layout_marginTop="30dp"
android:textColorHint="@android:color/black"
android:layout_height="wrap_content"/>
</RelativeLayout>
点击向下后,可以看到 log
这是键盘向下的监听,我们得到了,然后可以执行相应的动作。
作为一个程序员,我们不仅要知道怎样做,还应该知道为什么会这样。这个问题就涉及到 Android 输入系统,详情见上一篇博文 Android 输入系统
为什么 重写 Activity.onKeyDown() 方法为什么没有用?
根据 Android 输入系统 一文,我们知道事件的流水线处理顺序
Ime 是键盘输入法, ViewPreImeStage 是在输入法之前,ImeStage 是输入法处理,ViewPostImeStage 才是最终传递到 Activity.onKeyDown() 的。
ViewPreImeStage 中可以将键盘向下的事件传递到 View, 而到了 ViewPostImeStage 则没有,那应该是有东西消耗了这个事件。当我们按下向下的按钮,键盘会收起来,显然是键盘消耗了这个事件。
我们看看 ImeStage 的源码
/**
* Delivers input events to the ime.
* Does not support pointer events.
*/
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {
public ImeInputStage(InputStage next, String traceCounter) {
super(next, traceCounter);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (mLastWasImTarget) {
// 获取 InputMethodManager 实例
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
final InputEvent event = q.mEvent;
// dispatchInputEvent 方法处理了事件
int result = imm.dispatchInputEvent(event, q, this, mHandler);
if (result == InputMethodManager.DISPATCH_HANDLED) {
return FINISH_HANDLED;
} else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
return FINISH_NOT_HANDLED;
} else {
return DEFER; // callback will be invoked later
}
}
}
return FORWARD;
}
@Override
public void onFinishedInputEvent(Object token, boolean handled) {
QueuedInputEvent q = (QueuedInputEvent)token;
if (handled) {
finish(q, true);
return;
}
forward(q);
}
}
我们看到如果 InputMethodeManager.dispatchInputEvent(…) 方法返回的结果是
InputMethodManager.DISPATCH_HANDLED 则结束事件的传递。事件就传递不到 ViewPostImeStage, 从而也就传递不到 Activity.onKeyDown() 中了。
到这来我们终于知道了为什么重写 Activity.onKeyDown() 方法没用了,想要监听键盘向下的事件,需要重写 Edittext.onKeyPreime() 方法,在这个方法里进行监听了。