学习参考:Android焦点事件分发与传递机制
Android 焦点分发的主要方法以及拦截方法的讲解:
首先:在RootViewImpl中的函数通道是各种策略(InputStage)的组合,各个策略的任务不同,如:SyntheticInputStage、ViewPostImeInputStage、NativePostImeInputStage等等,这些策略以链表结构构造起来,当一个策略者没有消费事件时,就传递给下一个策略这。其中,触摸和按键事件由ViewPostImeInputState处理
ViewRootImpl.java 中ViewPostImeInputStage 及onProcess()的代码如下
/**
* Delivers post-ime input events to the view hierarchy.
*/
final class ViewPostImeInputStage extends InputStage {
public ViewPostImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q); //如果是按键事件走此处,处理按键和焦点问题了
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
其中,processKeyEvent(QueuedInputEvent q)的代码如下
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
int groupNavigationDirection = 0;
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
groupNavigationDirection = View.FOCUS_FORWARD;
} else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
// If a modifier is held, try to interpret the key as a shortcut.
if (event.getAction() == KeyEvent.ACTION_DOWN
&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())
&& groupNavigationDirection == 0) {
if (mView.dispatchKeyShortcutEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
}
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
/**
* Creates dpad events from unhandled trackball movements.
*/
final class SyntheticTrackballHandler {
private final TrackballAxis mX = new TrackballAxis();
private final TrackballAxis mY = new TrackballAxis();
private long mLastTime;
public void process(MotionEvent event) {
// Translate the trackball event into DPAD keys and try to deliver those.
long curTime = SystemClock.uptimeMillis();
if ((mLastTime + MAX_TRACKBALL_DELAY) < curTime) {
// It has been too long since the last movement,
// so restart at the beginning.
mX.reset(0);
mY.reset(0);
mLastTime = curTime;
}
final int action = event.getAction();
final int metaState = event.getMetaState();
switch (action) {
case MotionEvent.ACTION_DOWN:
mX.reset(2);
mY.reset(2);
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
break;
case MotionEvent.ACTION_UP:
mX.reset(2);
mY.reset(2);
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
break;
}
if (DEBUG_TRACKBALL) Log.v(mTag, "TB X=" + mX.position + " step="
+ mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
+ " move=" + event.getX()
+ " / Y=" + mY.position + " step="
+ mY.step + " dir=" + mY.dir + " acc=" + mY.acceleration
+ " move=" + event.getY());
final float xOff = mX.collect(event.getX(), event.getEventTime(), "X");
final float yOff = mY.collect(event.getY(), event.getEventTime(), "Y");
// Generate DPAD events based on the trackball movement.
// We pick the axis that has moved the most as the direction of
// the DPAD. When we generate DPAD events for one axis, then the
// other axis is reset -- we don't want to perform DPAD jumps due
// to slight movements in the trackball when making major movements
// along the other axis.
int keycode = 0;
int movement = 0;
float accel = 1;
if (xOff > yOff) {
movement = mX.generate();
if (movement != 0) {
keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
: KeyEvent.KEYCODE_DPAD_LEFT;
accel = mX.acceleration;
mY.reset(2);
}
} else if (yOff > 0) {
movement = mY.generate();
if (movement != 0) {
keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
: KeyEvent.KEYCODE_DPAD_UP;
accel = mY.acceleration;
mX.reset(2);
}
}
if (keycode != 0) {
if (movement < 0) movement = -movement;
int accelMovement = (int)(movement * accel);
if (DEBUG_TRACKBALL) Log.v(mTag, "Move: movement=" + movement
+ " accelMovement=" + accelMovement
+ " accel=" + accel);
if (accelMovement > movement) {
if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+ keycode);
movement--;
int repeatCount = accelMovement - movement;
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
}
while (movement > 0) {
if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+ keycode);
movement--;
curTime = SystemClock.uptimeMillis();
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_DOWN, keycode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_UP, keycode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
}
mLastTime = curTime;
}
}
public void cancel(MotionEvent event) {
mLastTime = Integer.MIN_VALUE;
// If we reach this, we consumed a trackball event.
// Because we will not translate the trackball event into a key event,
// touch mode will not exit, so we exit touch mode here.
if (mView != null && mAdded) {
ensureTouchMode(false);
}
}
}