「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」
Android按键事件传递流程(4)
1.ViewGroup的dispatchKeyEvent
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
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) {
//递归传递到所有的布局和view中
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
// 这个判读主要是对按键事件进行一致性的检查,防止同样的错误会发生多次。
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
PFLAG_FOCUSED和PFLAG_HAS_BOUNDS分别确定了ViewGroup的焦点和大小边界。
mFocused表示ViewGroup中获得焦点的子View,如果该view或ViewGroup的焦点的大小边界已经确定,则调用该view或ViewGroup的dispatchKeyEvent。
假设Activity的布局如下图:
如图焦点在C上面,则dispatchKeyEvent的传递流程是:ViewGroup -> A -> B -> C。
2.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;
}
ListenerInfo类用于描述所有view相关监听器信息的类,例如OnFocusChangeListener,OnClickListener等。如果某个view设置了监听器则在对应的OnClickListener监听方法中会调用getListenerInfo创建ListenerInfo对象赋值给mListenerInfo;
//传递到KeyEvent
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
如果没有设置监听器或没有被监听器消耗掉,则该事件会继续想传递到KeyEvent的dispatch。
3.KeyEvent的dispatch
public final boolean dispatch(Callback receiver, DispatcherState state,Object target)
该方法中的参数Callback receiver既可能是Activity对象,有可能是View对象,因为是view的dispatchKeyEvent传过来的,所以目前分析的是View对象。
DispatcherState state View.java通过getKeyDispatcherState返回KeyEvent内部类DispatcherState对象,该对象用来进行高级别的按键事件处理,如长按事件等等;
public KeyEvent.DispatcherState getKeyDispatcherState() {
//mAttachInfo是AttachInfo对象,当view关联到窗口时的一系列信息,AttachInfo类用来描述、跟踪这些信息,一般情况下不为空
return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;
}
下面继续分析KeyEvent的dispatch:
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
//清掉FLAG_START_TRACKING标记
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
//用view的onKeyDown方法
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
//view的onKeyDown已经消耗掉,且是第一次按下,mFlags包含FLAG_START_TRACKING
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
//跟踪按键事件
state.startTracking(this, target);
//如果是长按事件,且正在跟踪当前按键onKeyLongPress处理,用户可根据需求去重写该方法
} 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);
}
//回调view的onKeyUp方法
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;
}
dispatch主要作用是回调view的onKeyDown、onKeyUp
3.1 view的onKeyDown
public boolean onKeyDown(int keyCode, KeyEvent event) {
//主要针对KEYCODE_DPAD_CENTER、KEYCODE_ENTER事件进行处理:
if (KeyEvent.isConfirmKey(keyCode)) {
//如果当前按键事件包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,并且view设置了DISABLED属性,直接返回true,说明该view已经被按下
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if (event.getRepeatCount() == 0) {
// Long clickable items don't necessarily have to be clickable.
final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
|| (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
// For the purposes of menu anchoring and drawable hotspots,
// key events are considered to be at the center of the view.
final float x = getWidth() / 2f;
final float y = getHeight() / 2f;
if (clickable) {
//如果view设置了单击CLICKABLE或长按状态LONG_CLICKABLE,调用setPressed把该view设置为PRESSED状态
setPressed(true, x, y);
}
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
// This is not a touch gesture -- do not classify it as one.
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION);
return true;
}
}
}
return false;
}
checkForLongClick方法的主要工作:
如果仅是长按状态,系统在500秒后执行下面几种情形:
a. 如果有长按监听器OnLongClickListener,就回调onLongClick,如果成功,系统会发出一个触觉反馈
b. 如果没有长按监听器,就显示一个菜单。
如果按键事件不包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,onKeyDown不作任何处理,直接return false
3.2 view的onKeyUp
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
//setPressed(false)去除view的pressed状态,同时更新其绘制状态(显示状态)
setPressed(false);
//在onKeyDown中没有处理长按事件false
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
//把长按事件对象从消息的队列中移除
removeLongPressCallback();
if (!event.isCanceled()) {
return performClickInternal();//当遥控器按键(KEYCODE_DPAD_CENTER、KEYCODE_ENTER)松开时,performClick()如果设置了OnClickListener监听器,会调用onClick方法
}
}
}
}
return false;
}
4. KeyEvent的dispatch——Activity
如果Activity里面的任何view或ViewGroup都没有处理按键,就会传递到Activity的onKeyDown,onKeyUp。
4.1 activity的onKeyDown
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
只有松开BACK按键时才会退出Activity,如果不松开不会退出Activity。随后根据按键模式mDefaultKeyMode决定做哪些事情......
4.2 activity的onKeyUp
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
//返回退出activity
onBackPressed();
return true;
}
}
return false;
}
如果onKeyDown,onKeyUp没有消耗掉按键事件,就逆向返回到KeyEvent的dispatch中处理,如果仍然没有被消耗,就返回到Activity —-> DecorView —-> PhoneWindow,进入到 PhoneWindow中处理,下面对其进行讲解。
5. PhoneWindow的onKeyDown、onKeyUp
上述方法处理后,如果仍然返回false,则表明没有被消耗掉,按键事件传递到了当前窗口的window处理DecorView中返回isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
onKeyDown、onKeyUp主要针对当前获取焦点的窗口的一些特殊按键的处理,例如音量键+/-。
6.总结
-
当按键事件传递到View树根DecorView后会分为两步:
- view树内
- 外部(获得焦点的窗口)如果没有被view树内消耗就会传递到view树外,传递给获得焦点的窗口的keydown和keyup事件。
-
view树内的传递一般先传递到当前的Activity
-
Activity对象内部分发给ViewGroup,viewGroup如果本身有焦点就传递给其父类view,如果没有就继续传递给获取焦点的子View
- 如果子view是LinearLayout等常见布局,就递归传递给获得焦点的view视图;
- 如果子view就是view视图,就传递给该视图;
-
view视图内部,如果设置了OnKeyListener监听器,就传递给OnKey;如果没有OnKeyListener监听器,就分发给KeyEvent的dispatch,dispatch主要回调view的onKeyDown/onKeyUp;
-
在view的onKeyDown/onKeyUp中,如果是KEYCODE_DPAD_CENTER,KEYCODE_ENTER,直接return true;详细看第二部分。