赞
踩
刚接触Android开发的时候,对touch、key事件的处理总是一知半解,一会是Activity里的方法,一会是各种View
中的,自己始终不清楚到底哪个在先哪个在后,总之对整个处理流程没能很好的把握。每次写这部分代码的时候都有些心虚,
因为我不是很清楚什么时候、以什么样的顺序被调用,大都是打下log看看,没问题就算ok了。但随着时间流逝,这种感觉一直
折磨着我。期间也在网上搜索了相关资料,但总感觉不是那么令人满意。自打开始研究Android源码起,这部分内容的分析早就
被列在我的TODO list上了。因为弄懂这部分处理逻辑对明明白白地写android程序实在是太重要了,所以今天我就带领大家看看
这部分的处理逻辑。touch事件的处理我将放在另一篇博客中介绍(相比KeyEvent,大体都一样,只是稍微复杂些)。
为了突出本文的重点,我们直接从事件被派发到View层次结构的根节点DecorView开始分析,这里我们先来看看DecorView#
dispatchKeyEvent方法,代码如下:
01.
@Override
02.
public
boolean
dispatchKeyEvent(KeyEvent event) {
03.
final
int
keyCode = event.getKeyCode();
04.
final
int
action = event.getAction();
05.
final
boolean
isDown = action == KeyEvent.ACTION_DOWN;
06.
07.
/// 1. 第一次down事件的时候,处理panel的快捷键
08.
if
(isDown && (event.getRepeatCount() ==
0
)) {
09.
// First handle chording of panel key: if a panel key is held
10.
// but not released, try to execute a shortcut in it.
11.
if
((mPanelChordingKey >
0
) && (mPanelChordingKey != keyCode)) {
12.
boolean
handled = dispatchKeyShortcutEvent(event);
13.
if
(handled) {
14.
return
true
;
15.
}
16.
}
17.
18.
// If a panel is open, perform a shortcut on it without the
19.
// chorded panel key
20.
if
((mPreparedPanel !=
null
) && mPreparedPanel.isOpen) {
21.
if
(performPanelShortcut(mPreparedPanel, keyCode, event,
0
)) {
22.
return
true
;
23.
}
24.
}
25.
}
26.
27.
/// 2. 这里是我们本文的重点,当window没destroy且其Callback非空的话,交给其Callback处理
28.
if
(!isDestroyed()) {
// Activity、Dialog都是Callback接口的实现
29.
final
Callback cb = getCallback();
// mFeatureId < 0 表示是application的DecorView,比如Activity、Dialog
30.
final
boolean
handled = cb !=
null
&& mFeatureId <
0
? cb.dispatchKeyEvent(event)
// 派发给callback的方法
31.
:
super
.dispatchKeyEvent(event);
// 否则直接派发到ViewGroup#dispatchKeyEvent(View层次结构)
32.
if
(handled) {
33.
return
true
;
// 如果被上面的步骤处理了则直接返回true,不再往下传递
34.
}
35.
}
36.
37.
/// 3. 这是key事件的最后一步,如果到这一步还没处理掉,则派发到PhoneWindow对应的onKeyDown, onKeyUp方法
38.
return
isDown ? PhoneWindow.
this
.onKeyDown(mFeatureId, event.getKeyCode(), event)
39.
: PhoneWindow.
this
.onKeyUp(mFeatureId, event.getKeyCode(), event);
40.
}
接下来我们按照这个派发顺序依次来看看相关方法的实现,这里先看看Activity(Callback)的dispatchKeyEvent实现:
01.
/**
02.
* Called to process key events. You can override this to intercept all
03.
* key events before they are dispatched to the window. Be sure to call
04.
* this implementation for key events that should be handled normally.
05.
*
06.
* @param event The key event.
07.
*
08.
* @return boolean Return true if this event was consumed.
09.
*/
10.
@Override
11.
public
boolean
dispatchKeyEvent(KeyEvent event) {
12.
/// 2.1. 回调接口,实际开发中用处不大,你感兴趣可以参看其方法doc
13.
onUserInteraction();
14.
Window win = getWindow();
15.
/// 2.2. 从这里事件的处理交给了与之相关的window对象,实质是派发到了view层次结构
16.
if
(win.superDispatchKeyEvent(event)) {
17.
return
true
;
18.
}
19.
View decor = mDecor;
20.
if
(decor ==
null
) decor = win.getDecorView();
21.
/// 2.3. 到这里如果view层次结构没处理则交给KeyEvent本身的dispatch方法,Activity的各种回调方法会被触发
22.
return
event.dispatch(
this
, decor !=
null
23.
? decor.getKeyDispatcherState() :
null
,
this
);
24.
}
紧接着我们看看,Window#superDispatchKeyEvent方法,相关代码如下:
01.
<!-- Window.java -->
02.
/**
03.
* Used by custom windows, such as Dialog, to pass the key press event
04.
* further down the view hierarchy. Application developers should
05.
* not need to implement or call this.
06.
*
07.
*/
08.
public
abstract
boolean
superDispatchKeyEvent(KeyEvent event);
09.
10.
<!-- PhoneWindow.java -->
11.
12.
13.
@Override
14.
public
boolean
superDispatchKeyEvent(KeyEvent event) {
15.
return
mDecor.superDispatchKeyEvent(event);
16.
}
17.
18.
<!-- DecorView.superDispatchKeyEvent -->
19.
20.
public
boolean
superDispatchKeyEvent(KeyEvent event) {
21.
/// 2.2.1. 进入view层次结构了,即调用ViewGroup的对应实现了。。。
22.
if
(
super
.dispatchKeyEvent(event)) {
23.
return
true
;
// 如果被view层次结构处理了则直接返回true。
24.
}
25.
26.
// Not handled by the view hierarchy, does the action bar want it
27.
// to cancel out of something special?
28.
/// 2.2.2. ActionBar对BACK key的特殊处理
29.
if
(event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
30.
final
int
action = event.getAction();
31.
// Back cancels action modes first.
32.
if
(mActionMode !=
null
) {
33.
if
(action == KeyEvent.ACTION_UP) {
34.
mActionMode.finish();
35.
}
36.
return
true
;
37.
}
38.
39.
// Next collapse any expanded action views.
40.
if
(mActionBar !=
null
&& mActionBar.hasExpandedActionView()) {
41.
if
(action == KeyEvent.ACTION_UP) {
42.
mActionBar.collapseActionView();
43.
}
44.
return
true
;
45.
}
46.
}
47.
/// 2.2.3. 最后返回false表示没处理掉,会接着2.3.步骤处理
48.
return
false
;
49.
}
然后我们接着看看2.2.1.包括的小步骤,即ViewGroup#dispatchKeyEvent的实现,代码如下:
01.
@Override
02.
public
boolean
dispatchKeyEvent(KeyEvent event) {
03.
/// 2.2.1.1. keyevent一致性检测用的,可忽略。。。
04.
if
(mInputEventConsistencyVerifier !=
null
) {
05.
mInputEventConsistencyVerifier.onKeyEvent(event,
1
);
06.
}
07.
08.
if
((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
09.
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
10.
/// 2.2.1.2. 如果此ViewGroup是focused或者具体的大小被设置了,则交给他处理,即调用View的实现
11.
if
(
super
.dispatchKeyEvent(event)) {
12.
return
true
;
13.
}
14.
}
else
if
(mFocused !=
null
&& (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
15.
== PFLAG_HAS_BOUNDS) {
16.
/// 2.2.1.3. 否则,如果此ViewGroup中有focused的child,且child有具体的大小,则交给mFocused处理
17.
if
(mFocused.dispatchKeyEvent(event)) {
// 注意这里可能是个递归调用
18.
return
true
;
// 我们可以看到并不是每个child都能响应key事件,前提必须是focused child才有机会响应
19.
}
20.
}
21.
22.
if
(mInputEventConsistencyVerifier !=
null
) {
23.
mInputEventConsistencyVerifier.onUnhandledEvent(event,
1
);
24.
}
25.
/// 2.2.1.4. 最后都没被处理返回false,2.2.2.步骤会接着执行。。。
26.
return
false
;
27.
}
这里我们可以看出对KeyEvent来说在View层次结构中,如果ViewGroup条件满足则会优先处理事件而不是先派发给其孩子view,
这一点和touch事件有所不同。这里我们看看View的dispatchKeyEvent实现:
01.
/**
02.
* Dispatch a key event to the next view on the focus path. This path runs
03.
* from the top of the view tree down to the currently focused view. If this
04.
* view has focus, it will dispatch to itself. Otherwise it will dispatch
05.
* the next node down the focus path. This method also fires any key
06.
* listeners.
07.
*
08.
* @param event The key event to be dispatched.
09.
* @return True if the event was handled, false otherwise.
10.
*/
11.
public
boolean
dispatchKeyEvent(KeyEvent event) {
12.
if
(mInputEventConsistencyVerifier !=
null
) {
13.
mInputEventConsistencyVerifier.onKeyEvent(event,
0
);
14.
}
15.
16.
// Give any attached key listener a first crack at the event.
17.
//noinspection SimplifiableIfStatement
18.
ListenerInfo li = mListenerInfo;
19.
/// 2.2.1.2(3).1. 调用onKeyListener,如果它非空且view是ENABLED状态,监听器优先触发
20.
if
(li !=
null
&& li.mOnKeyListener !=
null
&& (mViewFlags & ENABLED_MASK) == ENABLED
21.
&& li.mOnKeyListener.onKey(
this
, event.getKeyCode(), event)) {
22.
return
true
;
23.
}
24.
25.
/// 2.2.1.2(3).2. 调用KeyEvent.dispatch方法,并将view对象本身作为参数传递进去,view的各种callback方法在这里被触发
26.
if
(event.dispatch(
this
, mAttachInfo !=
null
27.
? mAttachInfo.mKeyDispatchState :
null
,
this
)) {
28.
return
true
;
29.
}
30.
31.
if
(mInputEventConsistencyVerifier !=
null
) {
32.
mInputEventConsistencyVerifier.onUnhandledEvent(event,
0
);
33.
}
34.
/// 2.2.1.2(3).3. 还没处理掉返回false,接着2.2.1.4.执行
35.
return
false
;
36.
}
不管是这里的2.2.1.2(3).2.步骤还是前面Activity里的2.3.步骤,都调到了KeyEvent.dispatch方法,不过在看其代码之前我们
先来看看这里用到的mAttachInfo.mKeyDispatchState对象是咋来的,代码如下:
001.
// 这句代码位于View.AttachInfo类里
002.
final
KeyEvent.DispatcherState mKeyDispatchState
003.
=
new
KeyEvent.DispatcherState();
004.
005.
/**
006.
* Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}
007.
* for this view's window. Returns null if the view is not currently attached
008.
* to the window. Normally you will not need to use this directly, but
009.
* just use the standard high-level event callbacks like
010.
* {@link #onKeyDown(int, KeyEvent)}.
011.
*/
012.
public
KeyEvent.DispatcherState getKeyDispatcherState() {
013.
return
mAttachInfo !=
null
? mAttachInfo.mKeyDispatchState :
null
;
014.
}
015.
016.
// KeyEvent.DispatcherState类
017.
/**
018.
* Use with {@link KeyEvent#dispatch(Callback, DispatcherState, Object)}
019.
* for more advanced key dispatching, such as long presses.
020.
*/
021.
public
static
class
DispatcherState {
022.
int
mDownKeyCode;
023.
Object mDownTarget;
024.
SparseIntArray mActiveLongPresses =
new
SparseIntArray();
025.
026.
/**
027.
* Reset back to initial state.
028.
*/
029.
public
void
reset() {
// 清空内部状态
030.
if
(DEBUG) Log.v(TAG,
"Reset: "
+
this
);
031.
mDownKeyCode =
0
;
032.
mDownTarget =
null
;
033.
mActiveLongPresses.clear();
034.
}
035.
036.
/**
037.
* Stop any tracking associated with this target.
038.
*/
039.
public
void
reset(Object target) {
// 清空target对应的内部状态
040.
if
(mDownTarget == target) {
// 只有相同时才清空,否则啥也不做
041.
if
(DEBUG) Log.v(TAG,
"Reset in "
+ target +
": "
+
this
);
042.
mDownKeyCode =
0
;
043.
mDownTarget =
null
;
044.
}
045.
}
046.
047.
/**
048.
* Start tracking the key code associated with the given event. This
049.
* can only be called on a key down. It will allow you to see any
050.
* long press associated with the key, and will result in
051.
* {@link KeyEvent#isTracking} return true on the long press and up
052.
* events.
053.
*
054.
* <p>This is only needed if you are directly dispatching events, rather
055.
* than handling them in {@link Callback#onKeyDown}.
056.
*/
057.
public
void
startTracking(KeyEvent event, Object target) {
058.
if
(event.getAction() != ACTION_DOWN) {
// 状态检测
059.
throw
new
IllegalArgumentException(
060.
"Can only start tracking on a down event"
);
061.
}
062.
if
(DEBUG) Log.v(TAG,
"Start trackingt in "
+ target +
": "
+
this
);
063.
mDownKeyCode = event.getKeyCode();
// 赋值,表示正在track某个keycode
064.
mDownTarget = target;
065.
}
066.
067.
/**
068.
* Return true if the key event is for a key code that is currently
069.
* being tracked by the dispatcher.
070.
*/
071.
public
boolean
isTracking(KeyEvent event) {
072.
return
mDownKeyCode == event.getKeyCode();
073.
}
074.
075.
/**
076.
* Keep track of the given event's key code as having performed an
077.
* action with a long press, so no action should occur on the up.
078.
* <p>This is only needed if you are directly dispatching events, rather
079.
* than handling them in {@link Callback#onKeyLongPress}.
080.
*/
081.
public
void
performedLongPress(KeyEvent event) {
// 用来记录发生了生理长按事件
082.
mActiveLongPresses.put(event.getKeyCode(),
1
);
083.
}
084.
085.
/**
086.
* Handle key up event to stop tracking. This resets the dispatcher state,
087.
* and updates the key event state based on it.
088.
* <p>This is only needed if you are directly dispatching events, rather
089.
* than handling them in {@link Callback#onKeyUp}.
090.
*/
091.
public
void
handleUpEvent(KeyEvent event) {
092.
final
int
keyCode = event.getKeyCode();
093.
if
(DEBUG) Log.v(TAG,
"Handle key up "
+ event +
": "
+
this
);
094.
int
index = mActiveLongPresses.indexOfKey(keyCode);
095.
if
(index >=
0
) {
// 如果发生过生理长按则设置event.mFlags为CACELED,这样在接下来的receiver.onKeyUp中有些处理就不会发生了
096.
if
(DEBUG) Log.v(TAG,
" Index: "
+ index);
// 因为事件被标记为CANCELED了
097.
event.mFlags |= FLAG_CANCELED | FLAG_CANCELED_LONG_PRESS;
098.
mActiveLongPresses.removeAt(index);
099.
}
100.
if
(mDownKeyCode == keyCode) {
101.
if
(DEBUG) Log.v(TAG,
" Tracking!"
);
102.
event.mFlags |= FLAG_TRACKING;
// 设置event正确的mFlags,接下来的receiver.onKeyUp可能会检测此状态
103.
mDownKeyCode =
0
;
// reset,表示此keycode的tracking到此结束了
104.
mDownTarget =
null
;
105.
}
106.
}
107.
}
大概了解了KeyEvent.DispatcherState类,我们就可以来看看KeyEvent.dispatch方法了,代码如下:
01.
/**
02.
* Deliver this key event to a {@link Callback} interface. If this is
03.
* an ACTION_MULTIPLE event and it is not handled, then an attempt will
04.
* be made to deliver a single normal event.
05.
*
06.
* @param receiver The Callback that will be given the event.
07.
* @param state State information retained across events.
08.
* @param target The target of the dispatch, for use in tracking.
09.
*
10.
* @return The return value from the Callback method that was called.
11.
*/
12.
public
final
boolean
dispatch(Callback receiver, DispatcherState state,
13.
Object target) {
14.
switch
(mAction) {
15.
case
ACTION_DOWN: {
// DOWN事件
16.
mFlags &= ~FLAG_START_TRACKING;
//先清掉START_TRACKING标记
17.
if
(DEBUG) Log.v(TAG,
"Key down to "
+ target +
" in "
+ state
18.
+
": "
+
this
);
19.
boolean
res = receiver.onKeyDown(mKeyCode,
this
);
// 回调Callback接口的onKeyDown方法,View和Activity都是此接口的实现者
20.
if
(state !=
null
) {
// 一般都成立
21.
if
(res && mRepeatCount ==
0
&& (mFlags&FLAG_START_TRACKING) !=
0
) {
22.
if
(DEBUG) Log.v(TAG,
" Start tracking!"
);
// receiver.onKeyDown返回true了且不是repeated
23.
state.startTracking(
this
, target);
// 并且也没有开始tracking,则开始tracking当前的KeyEvent和target
24.
}
else
if
(isLongPress() && state.isTracking(
this
)) {
// 处理生理长按
25.
try
{
// 检测到生理长按则调用receiver.onKeyLongPress方法
26.
if
(receiver.onKeyLongPress(mKeyCode,
this
)) {
27.
if
(DEBUG) Log.v(TAG,
" Clear from long press!"
);
28.
state.performedLongPress(
this
);
// 记录此event已经有生理long press发生了。。。
29.
res =
true
;
// 设置为处理了
30.
}
31.
}
catch
(AbstractMethodError e) {
32.
}
33.
}
34.
}
35.
return
res;
// 返回down事件处理的结果
36.
}
37.
case
ACTION_UP:
// UP事件
38.
if
(DEBUG) Log.v(TAG,
"Key up to "
+ target +
" in "
+ state
39.
+
": "
+
this
);
40.
if
(state !=
null
) {
41.
state.handleUpEvent(
this
);
// reset state的内部状态,也改变了KeyEvent的某些状态
42.
}
43.
return
receiver.onKeyUp(mKeyCode,
this
);
// 最后调用receiver.onKeyUp方法
44.
case
ACTION_MULTIPLE:
// 这里可以忽略掉
45.
final
int
count = mRepeatCount;
46.
final
int
code = mKeyCode;
47.
if
(receiver.onKeyMultiple(code, count,
this
)) {
48.
return
true
;
49.
}
50.
if
(code != KeyEvent.KEYCODE_UNKNOWN) {
51.
mAction = ACTION_DOWN;
52.
mRepeatCount =
0
;
53.
boolean
handled = receiver.onKeyDown(code,
this
);
54.
if
(handled) {
55.
mAction = ACTION_UP;
56.
receiver.onKeyUp(code,
this
);
57.
}
58.
mAction = ACTION_MULTIPLE;
59.
mRepeatCount = count;
60.
return
handled;
61.
}
62.
return
false
;
63.
}
64.
return
false
;
65.
}
看完了KeyEvent的具体实现,我们接着看看receiver(Callback接口)的onKeyDown、onKeyUp实现,先来看View相关的,代码如下:
001.
/**
002.
* Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
003.
* KeyEvent.Callback.onKeyDown()}: perform press of the view
004.
* when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
005.
* is released, if the view is enabled and clickable.
006.
*
007.
* <p>Key presses in software keyboards will generally NOT trigger this listener,
008.
* although some may elect to do so in some situations. Do not rely on this to
009.
* catch software key presses.
010.
*
011.
* @param keyCode A key code that represents the button pressed, from
012.
* {@link android.view.KeyEvent}.
013.
* @param event The KeyEvent object that defines the button action.
014.
*/
015.
public
boolean
onKeyDown(
int
keyCode, KeyEvent event) {
016.
boolean
result =
false
;
017.
018.
if
(KeyEvent.isConfirmKey(keyCode)) {
// 只处理KEYCODE_DPAD_CENTER、KEYCODE_ENTER这2个按键
019.
if
((mViewFlags & ENABLED_MASK) == DISABLED) {
020.
return
true
;
// 针对disabled View直接返回true表示处理过了
021.
}
022.
// Long clickable items don't necessarily have to be clickable
023.
if
(((mViewFlags & CLICKABLE) == CLICKABLE ||
024.
(mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
025.
(event.getRepeatCount() ==
0
)) {
// clickable或者long_clickable且是第一次down事件
026.
setPressed(
true
);
// 标记pressed,你可能设置了View不同的background,这时候就会有所体现(比如高亮效果)
027.
checkForLongClick(
0
);
// 启动View的long click检测
028.
return
true
;
// 到达这一步就表示KeyEvent被处理掉了
029.
}
030.
}
031.
return
result;
032.
}
033.
034.
/**
035.
* Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)
036.
* KeyEvent.Callback.onKeyUp()}: perform clicking of the view
037.
* when {@link KeyEvent#KEYCODE_DPAD_CENTER} or
038.
* {@link KeyEvent#KEYCODE_ENTER} is released.
039.
* <p>Key presses in software keyboards will generally NOT trigger this listener,
040.
* although some may elect to do so in some situations. Do not rely on this to
041.
* catch software key presses.
042.
*
043.
* @param keyCode A key code that represents the button pressed, from
044.
* {@link android.view.KeyEvent}.
045.
* @param event The KeyEvent object that defines the button action.
046.
*/
047.
public
boolean
onKeyUp(
int
keyCode, KeyEvent event) {
048.
if
(KeyEvent.isConfirmKey(keyCode)) {
// 同onKeyDown,默认也只处理confirm key
049.
if
((mViewFlags & ENABLED_MASK) == DISABLED) {
050.
return
true
;
// 同样的逻辑,如果是DISABLED view,直接返回true表示处理过了
051.
}
052.
if
((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
053.
setPressed(
false
);
// 重置pressed状态
054.
055.
if
(!mHasPerformedLongPress) {
// 长按没发生的话,
056.
// This is a tap, so remove the longpress check
057.
removeLongPressCallback();
// 当up事件发生的时候,移除这些已经没用的callback
058.
return
performClick();
// 调用单击onClick监听器
059.
}
060.
}
061.
}
062.
return
false
;
// 其他所有的Key默认不处理
063.
}
064.
065.
/**
066.
* Sets the pressed state for this view.
067.
*
068.
* @see #isClickable()
069.
* @see #setClickable(boolean)
070.
*
071.
* @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
072.
* the View's internal state from a previously set "pressed" state.
073.
*/
074.
public
void
setPressed(
boolean
pressed) {
075.
final
boolean
needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
076.
077.
if
(pressed) {
078.
mPrivateFlags |= PFLAG_PRESSED;
079.
}
else
{
080.
mPrivateFlags &= ~PFLAG_PRESSED;
081.
}
082.
083.
if
(needsRefresh) {
084.
refreshDrawableState();
// 这行代码会刷新View的显示状态
085.
}
086.
dispatchSetPressed(pressed);
087.
}
088.
089.
private
void
checkForLongClick(
int
delayOffset) {
090.
if
((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
// 必须得是LONG_CLICKABLE的View
091.
mHasPerformedLongPress =
false
;
// 设置初始值
092.
093.
if
(mPendingCheckForLongPress ==
null
) {
// 只非空的时候才new一个
094.
mPendingCheckForLongPress =
new
CheckForLongPress();
095.
}
096.
mPendingCheckForLongPress.rememberWindowAttachCount();
097.
postDelayed(mPendingCheckForLongPress,
// post一个Runnable,注意延迟是个差值,而不是delayOffset
098.
ViewConfiguration.getLongPressTimeout() - delayOffset);
099.
}
100.
}
101.
102.
class
CheckForLongPress
implements
Runnable {
103.
104.
private
int
mOriginalWindowAttachCount;
105.
106.
public
void
run() {
107.
if
(isPressed() && (mParent !=
null
)
// 当时间到了,此Runnable没被移除掉的话,并且这些条件都满足的时候,
108.
&& mOriginalWindowAttachCount == mWindowAttachCount) {
109.
if
(performLongClick()) {
// 客户端定义的onLongClickListener监听器被触发
110.
mHasPerformedLongPress =
true
;
// 只有当被上面的方法处理掉了,才表示LongPress发生过了
111.
}
112.
}
113.
}
114.
115.
public
void
rememberWindowAttachCount() {
116.
mOriginalWindowAttachCount = mWindowAttachCount;
117.
}
118.
}
119.
120.
/**
121.
* Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
122.
* OnLongClickListener did not consume the event.
123.
*
124.
* @return True if one of the above receivers consumed the event, false otherwise.
125.
*/
126.
public
boolean
performLongClick() {
127.
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
128.
129.
boolean
handled =
false
;
130.
ListenerInfo li = mListenerInfo;
131.
if
(li !=
null
&& li.mOnLongClickListener !=
null
) {
// 优先触发监听器
132.
handled = li.mOnLongClickListener.onLongClick(View.
this
);
133.
}
134.
if
(!handled) {
// 如果还没处理,显示ContextMenu如果定义了的话
135.
handled = showContextMenu();
136.
}
137.
if
(handled) {
138.
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
139.
}
140.
return
handled;
// 返回处理结果
141.
}
接下来,看看Activity对应的onKeyDown,onKeyUp方法:
001.
/**
002.
* Called when a key was pressed down and not handled by any of the views
003.
* inside of the activity. So, for example, key presses while the cursor
004.
* is inside a TextView will not trigger the event (unless it is a navigation
005.
* to another object) because TextView handles its own key presses.
006.
*
007.
* <p>If the focused view didn't want this event, this method is called.
008.
*
009.
* <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK}
010.
* by calling {@link #onBackPressed()}, though the behavior varies based
011.
* on the application compatibility mode: for
012.
* {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications,
013.
* it will set up the dispatch to call {@link #onKeyUp} where the action
014.
* will be performed; for earlier applications, it will perform the
015.
* action immediately in on-down, as those versions of the platform
016.
* behaved.
017.
*
018.
* <p>Other additional default key handling may be performed
019.
* if configured with {@link #setDefaultKeyMode}.
020.
*
021.
* @return Return <code>true</code> to prevent this event from being propagated
022.
* further, or <code>false</code> to indicate that you have not handled
023.
* this event and it should continue to be propagated.
024.
* @see #onKeyUp
025.
* @see android.view.KeyEvent
026.
*/
027.
public
boolean
onKeyDown(
int
keyCode, KeyEvent event) {
028.
if
(keyCode == KeyEvent.KEYCODE_BACK) {
029.
if
(getApplicationInfo().targetSdkVersion
030.
>= Build.VERSION_CODES.ECLAIR) {
031.
event.startTracking();
032.
}
else
{
033.
onBackPressed();
034.
}
035.
return
true
;
036.
}
037.
038.
if
(mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
039.
return
false
;
040.
}
else
if
(mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
041.
if
(getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
042.
keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
043.
return
true
;
044.
}
045.
return
false
;
046.
}
else
{
047.
// Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
048.
boolean
clearSpannable =
false
;
049.
boolean
handled;
050.
if
((event.getRepeatCount() !=
0
) || event.isSystem()) {
051.
clearSpannable =
true
;
052.
handled =
false
;
053.
}
else
{
054.
handled = TextKeyListener.getInstance().onKeyDown(
055.
null
, mDefaultKeySsb, keyCode, event);
056.
if
(handled && mDefaultKeySsb.length() >
0
) {
057.
// something useable has been typed - dispatch it now.
058.
059.
final
String str = mDefaultKeySsb.toString();
060.
clearSpannable =
true
;
061.
062.
switch
(mDefaultKeyMode) {
063.
case
DEFAULT_KEYS_DIALER:
064.
Intent intent =
new
Intent(Intent.ACTION_DIAL, Uri.parse(
"tel:"
+ str));
065.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
066.
startActivity(intent);
067.
break
;
068.
case
DEFAULT_KEYS_SEARCH_LOCAL:
069.
startSearch(str,
false
,
null
,
false
);
070.
break
;
071.
case
DEFAULT_KEYS_SEARCH_GLOBAL:
072.
startSearch(str,
false
,
null
,
true
);
073.
break
;
074.
}
075.
}
076.
}
077.
if
(clearSpannable) {
078.
mDefaultKeySsb.clear();
079.
mDefaultKeySsb.clearSpans();
080.
Selection.setSelection(mDefaultKeySsb,
0
);
081.
}
082.
return
handled;
083.
}
084.
}
085.
086.
/**
087.
* Called when a key was released and not handled by any of the views
088.
* inside of the activity. So, for example, key presses while the cursor
089.
* is inside a TextView will not trigger the event (unless it is a navigation
090.
* to another object) because TextView handles its own key presses.
091.
*
092.
* <p>The default implementation handles KEYCODE_BACK to stop the activity
093.
* and go back.
094.
*
095.
* @return Return <code>true</code> to prevent this event from being propagated
096.
* further, or <code>false</code> to indicate that you have not handled
097.
* this event and it should continue to be propagated.
098.
* @see #onKeyDown
099.
* @see KeyEvent
100.
*/
101.
public
boolean
onKeyUp(
int
keyCode, KeyEvent event) {
102.
if
(getApplicationInfo().targetSdkVersion
103.
>= Build.VERSION_CODES.ECLAIR) {
104.
if
(keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
105.
&& !event.isCanceled()) {
106.
onBackPressed();
107.
return
true
;
108.
}
109.
}
110.
return
false
;
111.
}
最后是3.步骤,回到一开始DecorView.dispatchKeyEvent的最后几行代码,我们来看看PhoneWindow对应的onKeyDown,onKeyUp方法:
001.
/**
002.
* A key was pressed down and not handled by anything else in the window.
003.
*
004.
* @see #onKeyUp
005.
* @see android.view.KeyEvent
006.
*/
007.
protected
boolean
onKeyDown(
int
featureId,
int
keyCode, KeyEvent event) {
008.
/* ****************************************************************************
009.
* HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
010.
*
011.
* If your key handling must happen before the app gets a crack at the event,
012.
* it goes in PhoneWindowManager.
013.
*
014.
* If your key handling should happen in all windows, and does not depend on
015.
* the state of the current application, other than that the current
016.
* application can override the behavior by handling the event itself, it
017.
* should go in PhoneFallbackEventHandler.
018.
*
019.
* Only if your handling depends on the window, and the fact that it has
020.
* a DecorView, should it go here.
021.
* ****************************************************************************/
022.
023.
final
KeyEvent.DispatcherState dispatcher =
024.
mDecor !=
null
? mDecor.getKeyDispatcherState() :
null
;
025.
//Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
026.
// + " flags=0x" + Integer.toHexString(event.getFlags()));
027.
028.
switch
(keyCode) {
029.
case
KeyEvent.KEYCODE_VOLUME_UP:
// key event处理中的最后一步,
030.
case
KeyEvent.KEYCODE_VOLUME_DOWN:
031.
case
KeyEvent.KEYCODE_VOLUME_MUTE: {
// 处理音量调节键
032.
// Similar code is in PhoneFallbackEventHandler in case the window
033.
// doesn't have one of these. In this case, we execute it here and
034.
// eat the event instead, because we have mVolumeControlStreamType
035.
// and they don't.
036.
getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
037.
return
true
;
038.
}
039.
040.
case
KeyEvent.KEYCODE_MENU: {
041.
onKeyDownPanel((featureId <
0
) ? FEATURE_OPTIONS_PANEL : featureId, event);
042.
return
true
;
043.
}
044.
045.
case
KeyEvent.KEYCODE_BACK: {
046.
if
(event.getRepeatCount() >
0
)
break
;
047.
if
(featureId <
0
)
break
;
048.
// Currently don't do anything with long press.
049.
if
(dispatcher !=
null
) {
050.
dispatcher.startTracking(event,
this
);
051.
}
052.
return
true
;
053.
}
054.
055.
}
056.
057.
return
false
;
058.
}
059.
060.
/**
061.
* A key was released and not handled by anything else in the window.
062.
*
063.
* @see #onKeyDown
064.
* @see android.view.KeyEvent
065.
*/
066.
protected
boolean
onKeyUp(
int
featureId,
int
keyCode, KeyEvent event) {
067.
final
KeyEvent.DispatcherState dispatcher =
068.
mDecor !=
null
? mDecor.getKeyDispatcherState() :
null
;
069.
if
(dispatcher !=
null
) {
070.
dispatcher.handleUpEvent(event);
071.
}
072.
//Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
073.
// + " flags=0x" + Integer.toHexString(event.getFlags()));
074.
075.
switch
(keyCode) {
076.
case
KeyEvent.KEYCODE_VOLUME_UP:
077.
case
KeyEvent.KEYCODE_VOLUME_DOWN:
078.
case
KeyEvent.KEYCODE_VOLUME_MUTE: {
079.
// Similar code is in PhoneFallbackEventHandler in case the window
080.
// doesn't have one of these. In this case, we execute it here and
081.
// eat the event instead, because we have mVolumeControlStreamType
082.
// and they don't.
083.
getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
084.
return
true
;
085.
}
086.
087.
case
KeyEvent.KEYCODE_MENU: {
088.
onKeyUpPanel(featureId <
0
? FEATURE_OPTIONS_PANEL : featureId,
089.
event);
090.
return
true
;
091.
}
092.
093.
case
KeyEvent.KEYCODE_BACK: {
094.
if
(featureId <
0
)
break
;
095.
if
(event.isTracking() && !event.isCanceled()) {
096.
if
(featureId == FEATURE_OPTIONS_PANEL) {
097.
PanelFeatureState st = getPanelState(featureId,
false
);
098.
if
(st !=
null
&& st.isInExpandedMode) {
099.
// If the user is in an expanded menu and hits back, it
100.
// should go back to the icon menu
101.
reopenMenu(
true
);
102.
return
true
;
103.
}
104.
}
105.
closePanel(featureId);
106.
return
true
;
107.
}
108.
break
;
109.
}
110.
111.
case
KeyEvent.KEYCODE_SEARCH: {
112.
/*
113.
* Do this in onKeyUp since the Search key is also used for
114.
* chording quick launch shortcuts.
115.
*/
116.
if
(getKeyguardManager().inKeyguardRestrictedInputMode()) {
117.
break
;
118.
}
119.
if
(event.isTracking() && !event.isCanceled()) {
120.
launchDefaultSearch();
121.
}
122.
return
true
;
123.
}
124.
}
125.
126.
return
false
;
127.
}
至此所有按键事件的处理就分析完毕了,鉴于篇幅略长,我们最后稍微总结下。主要有这么几点:
1. View的各种KeyEvent.Callback接口早于Activity的对应接口被调用;
2. 整个处理环节中只要有一处表明处理掉了,则处理结束,不在往下传递;
3. 各种Callback接口的处理优先级低于监听器,也就是说各种onXXXListener的方法优先被调用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。