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