“This is the 9th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”
Android Key Event Transfer Process (4)
1. The dispatchKeyEvent ViewGroup
@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) {
// Recursively to all layouts and views
if (mFocused.dispatchKeyEvent(event)) {
return true; }}// The main purpose of this interpretation is to check the consistency of the keystroke event to prevent the same error from happening more than once.
if(mInputEventConsistencyVerifier ! =null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
Copy the code
PFLAG_FOCUSED and PFLAG_HAS_BOUNDS determine the focus and size bounds of the ViewGroup, respectively.
MFocused is a subview of the ViewGroup that is focused. If the size of the focus of the View or ViewGroup is bounded, the dispatchKeyEvent of the View or ViewGroup is called.
Suppose the layout of your Activity looks like this:
ViewGroup -> A -> B -> C
2. View the 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;
}
Copy the code
The ListenerInfo class is used to describe all view-related listener information, such as OnFocusChangeListener, OnClickListener, etc. If a view has a listener set, the corresponding OnClickListener listener method will call getListenerInfo to create a ListenerInfo object and assign it to mListenerInfo;
// Pass to KeyEvent
if (event.dispatch(this, mAttachInfo ! =null
? mAttachInfo.mKeyDispatchState : null.this)) {
return true;
}
if(mInputEventConsistencyVerifier ! =null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
Copy the code
If no listener is set or consumed by a listener, the event continues to want to be passed to the dispatch of KeyEvent.
3. The KeyEvent dispatch
public final boolean dispatch(Callback receiver, DispatcherState state,Object target)
Copy the code
In this method, the parameter Callback receiver may be both an Activity object and a View object, because it is sent from the View’s dispatchKeyEvent, so the current analysis is the View object.
Java uses getKeyDispatcherState to return the KeyEvent internal DispatcherState object, which is used for high-level keystroke events, such as long press events, etc.
public KeyEvent.DispatcherState getKeyDispatcherState(a) {
//mAttachInfo is the AttachInfo object. The AttachInfo class is used to describe and track information when a view is associated with a window
returnmAttachInfo ! =null ? mAttachInfo.mKeyDispatchState : null;
}
Copy the code
KeyEvent dispatch
public final boolean dispatch(Callback receiver, DispatcherState state, Object target) {
switch (mAction) {
case ACTION_DOWN: {
// Clear FLAG_START_TRACKING
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ":" + this);
// Use the view onKeyDown method
boolean res = receiver.onKeyDown(mKeyCode, this);
if(state ! =null) {
// View's onKeyDown is exhausted and is pressed for the first time. MFlags includes FLAG_START_TRACKING
if (res && mRepeatCount == 0&& (mFlags&FLAG_START_TRACKING) ! =0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
// Trace keystroke events
state.startTracking(this, target);
// If it is a long-press event and the current key onKeyLongPress processing is being tracked, the user can override this method as required
} 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);
}
// Call the view's onKeyUp method
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;
}
Copy the code
Dispatch is used to call back onKeyDown and onKeyUp of a view
3.1 the view onKeyDown
public boolean onKeyDown(int keyCode, KeyEvent event) {
KEYCODE_DPAD_CENTER and KEYCODE_ENTER:
if (KeyEvent.isConfirmKey(keyCode)) {
// If the current key event contains KEYCODE_DPAD_CENTER and KEYCODE_ENTER and the view is DISABLED, return true to indicate that the view has been pressed
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) {
// If the view is set to CLICKABLE or LONG_CLICKABLE, call setPressed to set the view to 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;
}
Copy the code
The checkForLongClick method does the following:
If only the long-press state, the system executes the following situations after 500 seconds:
A. If there is an OnLongClickListener, call back onLongClick. If it succeeds, the system will send a haptic feedback
B. If there is no long-press listener, a menu is displayed.
If the key event does not contain KEYCODE_DPAD_CENTER or KEYCODE_ENTER, onKeyDown does nothing and returns false
3.2 the onKeyUp view
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) Remove the pressed state of the view and update its drawn state (display state)
setPressed(false);
// Long press event is not handled in onKeyDown false
if(! mHasPerformedLongPress) {// This is a tap, so remove the longpress check
// Remove the long press event object from the message queue
removeLongPressCallback();
if(! event.isCanceled()) {return performClickInternal();PerformClick () calls onClick if the OnClickListener listener is set when the remote keys (KEYCODE_DPAD_CENTER, KEYCODE_ENTER) are released}}}}return false;
}
Copy the code
4. KeyEvent dispatch — the Activity
If no view or ViewGroup in the Activity handles the key, the Activity’s onKeyDown, onKeyUp are passed.
4.1 the activity of onKeyDown
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
Copy the code
You will exit the Activity only if you release the BACK button, and will not exit the Activity if you do not release it. Then according to the key mode mDefaultKeyMode decide what to do……
4.2 the activity of onKeyUp
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if(keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && ! event.isCanceled()) {// Return to exit the activity
onBackPressed();
return true; }}return false;
}
Copy the code
If onKeyDown,onKeyUp does not consume the keystroke event, it is returned to the Dispatch of the KeyEvent. If it is still not consumed, it is returned to the Activity — > DecorView — > PhoneWindow, Enter PhoneWindow, which is explained below.
5. OnKeyDown and onKeyUp of PhoneWindow
If false is still returned, the keystroke event is not consumed, and passed to the window handling DecorView of the current window returns isDown? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
OnKeyDown and onKeyUp are mainly for the processing of some special keys of the current window to obtain the focus, such as the volume key +/-.
6. Summary
-
When the keystroke event is passed to the View root DecorView, there are two steps:
- The view within the tree
- Keydown and KeyUp events that are passed outside the view tree to the window that gets the focus if not consumed inside the view tree.
-
Deliveries within the View tree are generally passed to the current Activity first
-
Part of the Activity object is sent to the ViewGroup. If the ViewGroup itself has a focus, it is passed to its parent view; if it does not, it is passed on to the child view that gets the focus
- If the subview is a common LinearLayout, it is passed recursively to the view view that gets the focus.
- If the child view is the view view, it is passed to that view;
-
Inside the view, if the OnKeyListener is set, it is passed to OnKey; If there is no OnKeyListener, dispatch to the Dispatch of KeyEvent. The dispatch calls the view onKeyDown/onKeyUp.
-
In view onKeyDown/onKeyUp, if KEYCODE_DPAD_CENTER,KEYCODE_ENTER, return true; Look at part 2 in detail.