Android 中keyEvent的消息处理(转) -- view部分
1. ViewRootImpl.deliverKeyEvent(QueuedInputEvent q)
1. 如果mView为空或者mAdded为false,就直接调用finishInputEvent。
2. mView.dispatchKeyEventPreIme(event), 在传递给IME之前做一些预处理。因为对于View来说,如果有输入窗口存在的话,会先将按键消息派发到输入窗口,只有当输入窗口没有处理这个事件,才会派发到真正的视图。因此如果想要在输入法截取事件前处理该消息,则可以重载这个方法去处理一些特定的按键消息。
3. 如果有IME窗口存在,就把这个传递给IME进行处理。imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
4. deliverKeyEventPostIme(q); 将消息派发给真正的视图。
- private void deliverKeyEvent(QueuedInputEvent q) {
- final KeyEvent event = (KeyEvent)q.mEvent;
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onKeyEvent(event, 0);
- }
- if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
- // If there is no view, then the event will not be handled.
- if (mView == null || !mAdded) {
- finishInputEvent(q, false);
- return;
- }
- if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
- // Perform predispatching before the IME.
- if (mView.dispatchKeyEventPreIme(event)) {
- finishInputEvent(q, true);
- return;
- }
- // Dispatch to the IME before propagating down the view hierarchy.
- // The IME will eventually call back into handleImeFinishedEvent.
- if (mLastWasImTarget) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- final int seq = event.getSequenceNumber();
- if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
- + seq + " event=" + event);
- imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
- return;
- }
- }
- }
- // Not dispatching to IME, continue with post IME actions.
- deliverKeyEventPostIme(q);
- }
private void deliverKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 0); } if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) { // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { finishInputEvent(q, false); return; } if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView); // Perform predispatching before the IME. if (mView.dispatchKeyEventPreIme(event)) { finishInputEvent(q, true); return; } // Dispatch to the IME before propagating down the view hierarchy. // The IME will eventually call back into handleImeFinishedEvent. if (mLastWasImTarget) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { final int seq = event.getSequenceNumber(); if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" + seq + " event=" + event); imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); return; } } } // Not dispatching to IME, continue with post IME actions. deliverKeyEventPostIme(q); }
1.2 ViewRootImpl.deliverKeyEventPostIme(q);
1. mView.dispatchKeyEvent(event); 将事件派发到View视图。因为mView就是一个DecorView,所以就是调用DecorView的dispatchKeyEvent的方法。
- private void deliverKeyEventPostIme(QueuedInputEvent q) {
- final KeyEvent event = (KeyEvent)q.mEvent;
- ... ...
- // If the key's purpose is to exit touch mode then we consume it and consider it handled.
- if (checkForLeavingTouchModeAndConsume(event)) {
- finishInputEvent(q, true);
- return;
- }
- // Make sure the fallback event policy sees all keys that will be delivered to the
- // view hierarchy.
- mFallbackEventHandler.preDispatchKeyEvent(event);
- // Deliver the key to the view hierarchy.
- if (mView.dispatchKeyEvent(event)) {
- finishInputEvent(q, true);
- return;
- }
- // If the Control modifier is held, try to interpret the key as a shortcut.
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.isCtrlPressed()
- && event.getRepeatCount() == 0
- && !KeyEvent.isModifierKey(event.getKeyCode())) {
- if (mView.dispatchKeyShortcutEvent(event)) {
- finishInputEvent(q, true);
- return;
- }
- }
- // Apply the fallback event policy.
- if (mFallbackEventHandler.dispatchKeyEvent(event)) {
- finishInputEvent(q, true);
- return;
- }
- // Handle automatic focus changes.
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- int direction = 0;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_LEFT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_RIGHT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_UP;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_DOWN;
- }
- break;
- case KeyEvent.KEYCODE_TAB:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_FORWARD;
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- direction = View.FOCUS_BACKWARD;
- }
- break;
- }
- if (direction != 0) {
- View focused = mView.findFocus();
- if (focused != null) {
- View v = focused.focusSearch(direction);
- if (v != null && v != focused) {
- // do the math the get the interesting rect
- // of previous focused into the coord system of
- // newly focused view
- focused.getFocusedRect(mTempRect);
- if (mView instanceof ViewGroup) {
- ((ViewGroup) mView).offsetDescendantRectToMyCoords(
- focused, mTempRect);
- ((ViewGroup) mView).offsetRectIntoDescendantCoords(
- v, mTempRect);
- }
- if (v.requestFocus(direction, mTempRect)) {
- playSoundEffect(SoundEffectConstants
- .getContantForFocusDirection(direction));
- finishInputEvent(q, true);
- return;
- }
- }
- // Give the focused view a last chance to handle the dpad key.
- if (mView.dispatchUnhandledMove(focused, direction)) {
- finishInputEvent(q, true);
- return;
- }
- }
- }
- }
- // Key was unhandled.
- finishInputEvent(q, false);
- }
private void deliverKeyEventPostIme(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; ... ... // If the key's purpose is to exit touch mode then we consume it and consider it handled. if (checkForLeavingTouchModeAndConsume(event)) { finishInputEvent(q, true); return; } // Make sure the fallback event policy sees all keys that will be delivered to the // view hierarchy. mFallbackEventHandler.preDispatchKeyEvent(event); // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { finishInputEvent(q, true); return; } // If the Control modifier is held, try to interpret the key as a shortcut. if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed() && event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(event.getKeyCode())) { if (mView.dispatchKeyShortcutEvent(event)) { finishInputEvent(q, true); return; } } // Apply the fallback event policy. if (mFallbackEventHandler.dispatchKeyEvent(event)) { finishInputEvent(q, true); return; } // Handle automatic focus changes. if (event.getAction() == KeyEvent.ACTION_DOWN) { int direction = 0; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_LEFT: if (event.hasNoModifiers()) { direction = View.FOCUS_LEFT; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: if (event.hasNoModifiers()) { direction = View.FOCUS_RIGHT; } break; case KeyEvent.KEYCODE_DPAD_UP: if (event.hasNoModifiers()) { direction = View.FOCUS_UP; } break; case KeyEvent.KEYCODE_DPAD_DOWN: if (event.hasNoModifiers()) { direction = View.FOCUS_DOWN; } break; case KeyEvent.KEYCODE_TAB: if (event.hasNoModifiers()) { direction = View.FOCUS_FORWARD; } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { direction = View.FOCUS_BACKWARD; } break; } if (direction != 0) { View focused = mView.findFocus(); if (focused != null) { View v = focused.focusSearch(direction); if (v != null && v != focused) { // do the math the get the interesting rect // of previous focused into the coord system of // newly focused view focused.getFocusedRect(mTempRect); if (mView instanceof ViewGroup) { ((ViewGroup) mView).offsetDescendantRectToMyCoords( focused, mTempRect); ((ViewGroup) mView).offsetRectIntoDescendantCoords( v, mTempRect); } if (v.requestFocus(direction, mTempRect)) { playSoundEffect(SoundEffectConstants .getContantForFocusDirection(direction)); finishInputEvent(q, true); return; } } // Give the focused view a last chance to handle the dpad key. if (mView.dispatchUnhandledMove(focused, direction)) { finishInputEvent(q, true); return; } } } } // Key was unhandled. finishInputEvent(q, false); }
1.2.1 DecorView.dispatchKeyEvent(KeyEvent event)
1. 如果是Down事件, 首先会判断是不是Shortcut事件也就是快捷键按钮或者特殊事件,如果是就会调用dispatchKeyShortcutEvent和performPanelShortcut进行一些处理然后返回。
2. 如果不是快捷键按钮,就调用Callback Activity的cb.dispatchKeyEvent(event)去处理。Activity的dispatchKeyEvent中其实也还是去调用PhoneWindow中的superDispatchKeyEvent, PhoneWindow最终还是调用mDecor.superDispatchKeyEvent(event);
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- final int keyCode = event.getKeyCode();
- final int action = event.getAction();
- final boolean isDown = action == KeyEvent.ACTION_DOWN;
- 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;
- }
- }
- }
- if (!isDestroyed()) {
- final Callback cb = getCallback();
- final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
- : super.dispatchKeyEvent(event);
- if (handled) {
- return true;
- }
- }
- return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
- : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
- }
@Override public boolean dispatchKeyEvent(KeyEvent event) { final int keyCode = event.getKeyCode(); final int action = event.getAction(); final boolean isDown = action == KeyEvent.ACTION_DOWN; 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; } } } if (!isDestroyed()) { final Callback cb = getCallback(); final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); if (handled) { return true; } } return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); }
1.2.1.2 DecorView.mDecor.superDispatchKeyEvent(event);
1. 调用父类的dispatchKeyEvent。
- public boolean superDispatchKeyEvent(KeyEvent event) {
- if (super.dispatchKeyEvent(event)) {
- return true;
- }
- // Not handled by the view hierarchy, does the action bar want it
- // to cancel out of something special?
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- final int action = event.getAction();
- // Back cancels action modes first.
- if (mActionMode != null) {
- if (action == KeyEvent.ACTION_UP) {
- mActionMode.finish();
- }
- return true;
- }
- // Next collapse any expanded action views.
- if (mActionBar != null && mActionBar.hasExpandedActionView()) {
- if (action == KeyEvent.ACTION_UP) {
- mActionBar.collapseActionView();
- }
- return true;
- }
- }
- return false;
- }
public boolean superDispatchKeyEvent(KeyEvent event) { if (super.dispatchKeyEvent(event)) { return true; } // Not handled by the view hierarchy, does the action bar want it // to cancel out of something special? if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { final int action = event.getAction(); // Back cancels action modes first. if (mActionMode != null) { if (action == KeyEvent.ACTION_UP) { mActionMode.finish(); } return true; } // Next collapse any expanded action views. if (mActionBar != null && mActionBar.hasExpandedActionView()) { if (action == KeyEvent.ACTION_UP) { mActionBar.collapseActionView(); } return true; } } return false; }
1.2.1.2.1 ViewGroup.dispatchKeyEvent(KeyEvent event)
1. mPrivateFlags 是在View中定义的,标志当前View的属性。 这里吧的FOCUSED 和HAS_BOUNDS表示当前的View是否是focused和有bounds的。一般的View和ViewGroup都会有HAS_BOUNDS, 而mFocused记录这当前ViewGroup中处于focus的View。
例如:如果A包含B,B又包含C,如果C是一个View,C的mPrivateFlags就是 FOCUSED | HAS_BOUNDS那么B的mPrivateFlags就是FOCUSED,mFocused就是C,同理A的mPrivateFlags也是FOCUSED,mFocused就是B。
2. 所以一般情况下,ViewGroup通过mFocused.dispatchKeyEvent()递归把Event传到View。
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onKeyEvent(event, 1);
- }
- if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- if (super.dispatchKeyEvent(event)) {
- return true;
- }
- } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- if (mFocused.dispatchKeyEvent(event)) {
- return true;
- }
- }
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
- }
- return false;
- }
public boolean dispatchKeyEvent(KeyEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 1); } if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { if (super.dispatchKeyEvent(event)) { return true; } } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { if (mFocused.dispatchKeyEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 1); } return false; }
1.2.1.2.1.2 View.dispatchKeyEvent()
如果mFocused是ViewGroup那么还是调用1.2.1.2.1;如果是View,那么就调用View的dispatchKeyEvent。
1. 如果有注册Listener,就直接调用Listener的onKey去相应,然后返回。
2. 如果没有注册Listener,就调用KeyEvent.dispatch去做最后的处理
- 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;
- }
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; }
1.2.1.2.1.2.2 KeyEvent.dispatch
1. KeyEvent.dispatch会根据Action的类型调用receiver的对应的onXXX函数去处理。(receiver通常都是View)
2. 如果所有的View都没有处理这个KeyEvent,那么会最终通过KeyEvent调用Activity的onXXX函数。
- public final boolean dispatch(Callback receiver, DispatcherState state,
- Object target) {
- switch (mAction) {
- case ACTION_DOWN: {
- mFlags &= ~FLAG_START_TRACKING;
- if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
- + ": " + this);
- boolean res = receiver.onKeyDown(mKeyCode, this);
- if (state != null) {
- if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
- if (DEBUG) Log.v(TAG, " Start tracking!");
- state.startTracking(this, target);
- } else if (isLongPress() && state.isTracking(this)) {
- try {
- if (receiver.onKeyLongPress(mKeyCode, this)) {
- if (DEBUG) Log.v(TAG, " Clear from long press!");
- state.performedLongPress(this);
- res = true;
- }
- } catch (AbstractMethodError e) {
- }
- }
- }
- return res;
- }
- case ACTION_UP:
- if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
- + ": " + this);
- if (state != null) {
- state.handleUpEvent(this);
- }
- return receiver.onKeyUp(mKeyCode, this);
- case ACTION_MULTIPLE:
- final int count = mRepeatCount;
- final int code = mKeyCode;
- if (receiver.onKeyMultiple(code, count, this)) {
- return true;
- }
- if (code != KeyEvent.KEYCODE_UNKNOWN) {
- mAction = ACTION_DOWN;
- mRepeatCount = 0;
- boolean handled = receiver.onKeyDown(code, this);
- if (handled) {
- mAction = ACTION_UP;
- receiver.onKeyUp(code, this);
- }
- mAction = ACTION_MULTIPLE;
- mRepeatCount = count;
- return handled;
- }
- return false;
- }
- return false;
- }
public final boolean dispatch(Callback receiver, DispatcherState state, Object target) { switch (mAction) { case ACTION_DOWN: { mFlags &= ~FLAG_START_TRACKING; if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state + ": " + this); boolean res = receiver.onKeyDown(mKeyCode, this); if (state != null) { if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) { if (DEBUG) Log.v(TAG, " Start tracking!"); state.startTracking(this, target); } else if (isLongPress() && state.isTracking(this)) { try { if (receiver.onKeyLongPress(mKeyCode, this)) { if (DEBUG) Log.v(TAG, " Clear from long press!"); state.performedLongPress(this); res = true; } } catch (AbstractMethodError e) { } } } return res; } case ACTION_UP: if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state + ": " + this); if (state != null) { state.handleUpEvent(this); } return receiver.onKeyUp(mKeyCode, this); case ACTION_MULTIPLE: final int count = mRepeatCount; final int code = mKeyCode; if (receiver.onKeyMultiple(code, count, this)) { return true; } if (code != KeyEvent.KEYCODE_UNKNOWN) { mAction = ACTION_DOWN; mRepeatCount = 0; boolean handled = receiver.onKeyDown(code, this); if (handled) { mAction = ACTION_UP; receiver.onKeyUp(code, this); } mAction = ACTION_MULTIPLE; mRepeatCount = count; return handled; } return false; } return false; }