1,按键事件
手机上有各种各样的实体按键,比如音量上下键,开机键等等。
按键事件接口(KeyEvent):
ACTION_DOWN // 按键按下
ACTION_UP // 按键抬起
ACTION_MULTIPLE // 复合键
每一个按键唯一不同的是keycode,所有的键值都定义在
frameworks/base/core/java/android/view/KeyEvent.java 文件中.
2,按键分发处理
分发处理流程如下,
ViewRootImpl的内部类ViewPostImeInputStage的processKeyEvent方法会调用View的dispatchKeyEvent方法,
mView. dispatchKeyEvent (event);
mView变量是在setview方法中赋值的,对于应用窗口来说, mView变量指向PhoneWindow的内部类DecorView对象。
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
//按下时,处理panel的快捷键
if (isDown && (event.getRepeatCount() == 0)) {
// First handle chording of panel key: if a panel key is held
// but not released, try to execute a shortcut in it.
if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
boolean handled = dispatchKeyShortcutEvent(event);
if (handled) {
return true;
}
}
// If a panel is open, perform a shortcut on it without the
// chorded panel key
if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}
// activity和Dialog都是Callback接口的具体实现,也是本文的重点
if (!isDestroyed()) {
final Callback cb = getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
// 如果都未处理,最后PhoneWindow本身来处理
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
如果activity和view都未处理,则会调用PhoneWindow的onKeyDown和onKeyUp函数。主要处理一些和系统相关的按键,比如音量上下键,照相机按键
Activity的dispatchKeyEvent方法如下,
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// 1,处理菜单事件
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
// 2,回调给PhoneWindow处理
Window win = getWindow(); // 实质是PhoneWindow对象
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
// 3,最后按键事件本身来处理
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
由此可见,如果开发的activity中重写了dispatchKeyEvent方法,那么按键事件就不会分发到View树了。
PhoneWindow的superDispatchKeyEvent方法直接调用DecorView的superDispatchKeyEvent方法, superDispatchKeyEvent方法如下,
public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
// 处理返回键
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mPrimaryActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mPrimaryActionMode.finish();
}
return true;
}
}
// DecorView的父类是FrameLayout,最后调用ViewGroup中的dispatchKeyEvent方法,终于走到View树中了。
return super.dispatchKeyEvent(event);
}
ViewGroup的dispatchKeyEvent方法如下,
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
// 直接调用view的dispatchKeyEvent方法
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
// 调用获取焦点的子类进行处理,该子类可能还是一个ViewGroup,所以可能会有递归调用。
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
View的dispatchKeyEvent方法如下,
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
View中的处理主要分为2个部分,
1,如果view中OnKeyListener对象不为空,则调用其onKey方法,
mListenerInfo是View的静态内部类ListenerInfo对象, 其mOnKeyListener变量指向view内部接口OnKeyListener, OnKeyListener定义如下,
public interface OnKeyListener {
boolean onKey(View v, int keyCode, KeyEvent event);
}
开发时,设置view的按键事件监听方法如下,
startClientBtn.setOnKeyListener(new OnKeyListener(){
@Override
public boolean onKey(View arg0, int arg1, KeyEvent arg2) {
return false;
}
});
按照上面的逻辑,setOnKeyListener方法肯定会给ListenerInfo的mOnKeyListener变量赋值, setOnKeyListener方法如下,
public void setOnKeyListener(OnKeyListener l) {
getListenerInfo().mOnKeyListener = l; // 为mOnKeyListener对象赋值
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
2,如果view中没有监听按键事件,则调用KeyEvent的dispatch方法,并且传入了View对象,在activity中,传入的是activity对象。
下一篇文章分析KeyEvent如何处理按键事件。