privateintprocessKeyEvent(QueuedInputEvent q){ final KeyEvent event = (KeyEvent)q.mEvent;
if (mUnhandledKeyManager.preViewDispatch(event)) { return FINISH_HANDLED; }
// 1.分发按键,如果有消费返回true不继续往下执行 // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { return FINISH_HANDLED; }
if (shouldDropInputEvent(q)) { return FINISH_NOT_HANDLED; }
// This dispatch is for windows that don't have a Window.Callback. Otherwise, // the Window.Callback usually will have already called this (see // DecorView.superDispatchKeyEvent) leaving this call a no-op. if (mUnhandledKeyManager.dispatch(mView, event)) { return FINISH_HANDLED; }
// 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 { // 2.如果按下按键则执行焦点导航逻辑 if (performFocusNavigation(event)) { return FINISH_HANDLED; } } } return FORWARD; }
// 1.如果是第一次按下则处理panel的快捷键 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 ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) { boolean handled = dispatchKeyShortcutEvent(event); if (handled) { returntrue; } }
// If a panel is open, perform a shortcut on it without the // chorded panel key if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) { if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) { returntrue; } } }
// 2.当Window没destroy且其Callback非空的话,交给其Callback处理 if (!mWindow.isDestroyed()) { final Window.Callback cb = mWindow.getCallback(); finalboolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); if (handled) { returntrue; } }
/** * Called to process key events. You can override this to intercept all * key events before they are dispatched to the window. Be sure to call * this implementation for key events that should be handled normally. * * @param event The key event. * * @return boolean Return true if this event was consumed. */ publicbooleandispatchKeyEvent(KeyEvent event){ onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over // the window handling it finalint keyCode = event.getKeyCode(); if (keyCode == KeyEvent.KEYCODE_MENU && mActionBar != null && mActionBar.onMenuKeyEvent(event)) { returntrue; }
/** * Dispatch a key event to the next view on the focus path. This path runs * from the top of the view tree down to the currently focused view. If this * view has focus, it will dispatch to itself. Otherwise it will dispatch * the next node down the focus path. This method also fires any key * listeners. * * @param event The key event to be dispatched. * @return True if the event was handled, false otherwise. */ publicbooleandispatchKeyEvent(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; // 1.如果OnKeyListener非空且view是ENABLED状态,则监听器优先触发 if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { returntrue; }
/** * Deliver this key event to a {@link Callback} interface. If this is * an ACTION_MULTIPLE event and it is not handled, then an attempt will * be made to deliver a single normal event. * * @param receiver The Callback that will be given the event. * @param state State information retained across events. * @param target The target of the dispatch, for use in tracking. * * @return The return value from the Callback method that was called. */ publicfinalbooleandispatch(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); } elseif (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: finalint count = mRepeatCount; finalint code = mKeyCode; if (receiver.onKeyMultiple(code, count, this)) { returntrue; } 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; } returnfalse; } returnfalse; }
/** * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent) * KeyEvent.Callback.onKeyDown()}: perform press of the view * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER} * is released, if the view is enabled and clickable. * <p> * Key presses in software keyboards will generally NOT trigger this * listener, although some may elect to do so in some situations. Do not * rely on this to catch software key presses. * * @param keyCode a key code that represents the button pressed, from * {@link android.view.KeyEvent} * @param event the KeyEvent object that defines the button action */ publicbooleanonKeyDown(int keyCode, KeyEvent event){ if (KeyEvent.isConfirmKey(keyCode)) { // 1.如果View为不可用状态,则返回true if ((mViewFlags & ENABLED_MASK) == DISABLED) { returntrue; }
// 2.如果事件重复为0次并且View是可点击的或者可长按的,则设置按下View正中间坐标,检查长按 if (event.getRepeatCount() == 0) { // Long clickable items don't necessarily have to be clickable. finalboolean 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. finalfloat x = getWidth() / 2f; finalfloat y = getHeight() / 2f; if (clickable) { setPressed(true, x, y); } checkForLongClick(0, x, y); returntrue; } } }
/** * Called when a key was pressed down and not handled by any of the views * inside of the activity. So, for example, key presses while the cursor * is inside a TextView will not trigger the event (unless it is a navigation * to another object) because TextView handles its own key presses. * * <p>If the focused view didn't want this event, this method is called. * * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK} * by calling {@link #onBackPressed()}, though the behavior varies based * on the application compatibility mode: for * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications, * it will set up the dispatch to call {@link #onKeyUp} where the action * will be performed; for earlier applications, it will perform the * action immediately in on-down, as those versions of the platform * behaved. * * <p>Other additional default key handling may be performed * if configured with {@link #setDefaultKeyMode}. * * @return Return <code>true</code> to prevent this event from being propagated * further, or <code>false</code> to indicate that you have not handled * this event and it should continue to be propagated. * @see #onKeyUp * @see android.view.KeyEvent */ publicbooleanonKeyDown(int keyCode, KeyEvent event){ // 1.当按下返回键时调用onBackPressed,如果没走检查下是不是重写了onKeyDown方法retrun了true导致 if (keyCode == KeyEvent.KEYCODE_BACK) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) { event.startTracking(); } else { onBackPressed(); } returntrue; }
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) { returnfalse; } elseif (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) { Window w = getWindow(); if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) && w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) { returntrue; } returnfalse; } elseif (keyCode == KeyEvent.KEYCODE_TAB) { // Don't consume TAB here since it's used for navigation. Arrow keys // aren't considered "typing keys" so they already won't get consumed. returnfalse; } else { // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_* boolean clearSpannable = false; boolean handled; if ((event.getRepeatCount() != 0) || event.isSystem()) { clearSpannable = true; handled = false; } else { handled = TextKeyListener.getInstance().onKeyDown( null, mDefaultKeySsb, keyCode, event); if (handled && mDefaultKeySsb.length() > 0) { // something useable has been typed - dispatch it now.
final String str = mDefaultKeySsb.toString(); clearSpannable = true;
switch (mDefaultKeyMode) { case DEFAULT_KEYS_DIALER: Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); break; case DEFAULT_KEYS_SEARCH_LOCAL: startSearch(str, false, null, false); break; case DEFAULT_KEYS_SEARCH_GLOBAL: startSearch(str, false, null, true); break; } } } if (clearSpannable) { mDefaultKeySsb.clear(); mDefaultKeySsb.clearSpans(); Selection.setSelection(mDefaultKeySsb,0); } return handled; } }
/** * A key was pressed down and not handled by anything else in the window. * * @see #onKeyUp * @see android.view.KeyEvent */ protectedbooleanonKeyDown(int featureId, int keyCode, KeyEvent event){ /* **************************************************************************** * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES. * * If your key handling must happen before the app gets a crack at the event, * it goes in PhoneWindowManager. * * If your key handling should happen in all windows, and does not depend on * the state of the current application, other than that the current * application can override the behavior by handling the event itself, it * should go in PhoneFallbackEventHandler. * * Only if your handling depends on the window, and the fact that it has * a DecorView, should it go here. * ****************************************************************************/
switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { mMediaController.dispatchVolumeButtonEventAsSystemService(event); } else { getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(event, mVolumeControlStreamType); } returntrue; } // These are all the recognized media key codes in // KeyEvent.isMediaKey() case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MUTE: case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_STOP: case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: case KeyEvent.KEYCODE_MEDIA_REWIND: case KeyEvent.KEYCODE_MEDIA_RECORD: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { if (mMediaController != null) { if (mMediaController.dispatchMediaButtonEventAsSystemService(event)) { returntrue; } } returnfalse; }
case KeyEvent.KEYCODE_BACK: { if (event.getRepeatCount() > 0) break; if (featureId < 0) break; // Currently don't do anything with long press. if (dispatcher != null) { dispatcher.startTracking(event, this); } returntrue; }
privatebooleanperformFocusNavigation(KeyEvent event){ int direction = 0; // 1.判断方向键上下左右和Tab键 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; } elseif (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { direction = View.FOCUS_BACKWARD; } break; } if (direction != 0) { // 2.mView:即DecorView,DecorView是整个ViewTree的最顶层View,代表了整个应用的界面. View focused = mView.findFocus(); if (focused != null) { // 3.找到了当前获得焦点的focused,调用该焦点view的focusSearch方法 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); } // 4.找到的下一个可获取焦点的view不是当前已经获得焦点的view,则调用requestFocus方法 if (v.requestFocus(direction, mTempRect)) { playSoundEffect(SoundEffectConstants .getContantForFocusDirection(direction)); returntrue; } }
// 5.给当前获取焦点的focused view 最后一次处理事件的机会 // Give the focused view a last chance to handle the dpad key. if (mView.dispatchUnhandledMove(focused, direction)) { returntrue; } } else { // 6.递归调用,重置默认焦点(整个视图树上只能有唯一一个默认焦点view) if (mView.restoreDefaultFocus()) { returntrue; } } } returnfalse; }
/** * Find the nearest view in the specified direction that can take focus. * This does not actually give focus to that view. * * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT * * @return The nearest focusable in the specified direction, or null if none * can be found. */ public View focusSearch(@FocusRealDirection int direction){ if (mParent != null) { return mParent.focusSearch(this, direction); } else { returnnull; } }
/** * Find the nearest view in the specified direction that wants to take * focus. * * @param focused The view that currently has focus * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and * FOCUS_RIGHT, or 0 for not applicable. */ @Override public View focusSearch(View focused, int direction){ if (isRootNamespace()) { // root namespace means we should consider ourselves the top of the // tree for focus searching; otherwise we could be focus searching // into other tabs. see LocalActivityManager and TabHost for more info. return FocusFinder.getInstance().findNextFocus(this, focused, direction); } elseif (mParent != null) { return mParent.focusSearch(focused, direction); } returnnull; }
/** * Find the next view to take focus in root's descendants, starting from the view * that currently is focused. * @param root Contains focused. Cannot be null. * @param focused Has focus now. * @param direction Direction to look. * @return The next focusable view, or null if none exists. */ publicfinal View findNextFocus(ViewGroup root, View focused, int direction){ return findNextFocus(root, focused, null, direction); }
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction){ View next = null; ViewGroup effectiveRoot = getEffectiveRoot(root, focused); if (focused != null) { // 1.查找用户指定的获取下一个焦点的view next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction); } if (next != null) { // 2.如果找到用户指定的焦点,则直接返回该焦点 return next; } ArrayList<View> focusables = mTempList; try { focusables.clear(); // 3.添加effectiveRoot下的所有view到focusables集合中去,重写ViewGroup的该方法可以实现焦点记忆功能 effectiveRoot.addFocusables(focusables, direction); if (!focusables.isEmpty()) { // 4.根据系统默认的就近原则算法,查找下一个可获取焦点的最近的view next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables); } } finally { focusables.clear(); } return next; }
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction){ // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); View cycleCheck = userSetNextFocus; boolean cycleStep = true; // we want the first toggle to yield false while (userSetNextFocus != null) { if (userSetNextFocus.isFocusable() && userSetNextFocus.getVisibility() == View.VISIBLE && (!userSetNextFocus.isInTouchMode() || userSetNextFocus.isFocusableInTouchMode())) { return userSetNextFocus; } userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction); if (cycleStep = !cycleStep) { cycleCheck = cycleCheck.findUserSetNextFocus(root, direction); if (cycleCheck == userSetNextFocus) { // found a cycle, user-specified focus forms a loop and none of the views // are currently focusable. break; } } } returnnull; }
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction, ArrayList<View> focusables){ if (focused != null) { if (focusedRect == null) { focusedRect = mFocusedRect; } // 1.给focusedRect赋值为已获取焦点的view视图的可见绘图边界 // fill in interesting rect from focused focused.getFocusedRect(focusedRect); root.offsetDescendantRectToMyCoords(focused, focusedRect); } else { if (focusedRect == null) { focusedRect = mFocusedRect; // make up a rect at top left or bottom right of root switch (direction) { case View.FOCUS_RIGHT: case View.FOCUS_DOWN: // 2.修改focusedRect左上角边界为root的左上角边缘 setFocusTopLeft(root, focusedRect); break; case View.FOCUS_FORWARD: if (root.isLayoutRtl()) { setFocusBottomRight(root, focusedRect); } else { setFocusTopLeft(root, focusedRect); } break;
case View.FOCUS_LEFT: case View.FOCUS_UP: // 3.修改focusedRect右下角边界为root的右下角边缘 setFocusBottomRight(root, focusedRect); break; case View.FOCUS_BACKWARD: if (root.isLayoutRtl()) { setFocusTopLeft(root, focusedRect); } else { setFocusBottomRight(root, focusedRect); break; } } } }
switch (direction) { case View.FOCUS_FORWARD: case View.FOCUS_BACKWARD: // 4.在相对方向上找到下一个焦点 return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect, direction); case View.FOCUS_UP: case View.FOCUS_DOWN: case View.FOCUS_LEFT: case View.FOCUS_RIGHT: // 5.在绝对相对方向上找到下一个焦点 return findNextFocusInAbsoluteDirection(focusables, root, focused, focusedRect, direction); default: thrownew IllegalArgumentException("Unknown direction: " + direction); } }
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction){ // initialize the best candidate to something impossible // (so the first plausible view will become the best choice) mBestCandidateRect.set(focusedRect); switch(direction) { case View.FOCUS_LEFT: mBestCandidateRect.offset(focusedRect.width() + 1, 0); break; case View.FOCUS_RIGHT: mBestCandidateRect.offset(-(focusedRect.width() + 1), 0); break; case View.FOCUS_UP: mBestCandidateRect.offset(0, focusedRect.height() + 1); break; case View.FOCUS_DOWN: mBestCandidateRect.offset(0, -(focusedRect.height() + 1)); }
View closest = null;
int numFocusables = focusables.size(); for (int i = 0; i < numFocusables; i++) { View focusable = focusables.get(i);
// only interested in other non-root views if (focusable == focused || focusable == root) continue;
// get focus bounds of other view in same coordinate system focusable.getFocusedRect(mOtherRect); root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
/** * Is rect1 a better candidate than rect2 for a focus search in a particular * direction from a source rect? This is the core routine that determines * the order of focus searching. * @param direction the direction (up, down, left, right) * @param source The source we are searching from * @param rect1 The candidate rectangle * @param rect2 The current best candidate. * @return Whether the candidate is the new best. */ booleanisBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2){
// 1.source = focusedRect;rect1 = mOtherRect;rect2 = mBestCandidateRect // 以向左寻焦为例,判断rect1是否右边在source右边的左侧并且rect1的左边在source左边的左侧 // to be a better candidate, need to at least be a candidate in the first // place :) if (!isCandidate(source, rect1, direction)) { returnfalse; }
// 2.判断react2是否在source左边,如果不是则选择react1,如果是继续下面判断 // we know that rect1 is a candidate.. if rect2 is not a candidate, // rect1 is better if (!isCandidate(source, rect2, direction)) { returntrue; }
// 3.根据方向上是否重叠和距离判断谁更合适 // if rect1 is better by beam, it wins if (beamBeats(direction, source, rect1, rect2)) { returntrue; }
// 4.交换react1和react1继续比较 // if rect2 is better, then rect1 cant' be :) if (beamBeats(direction, source, rect2, rect1)) { returnfalse; }
// 5.否则,继续比较距离 // otherwise, do fudge-tastic comparison of the major and minor axis return (getWeightedDistanceFor( majorAxisDistance(direction, source, rect1), minorAxisDistance(direction, source, rect1)) < getWeightedDistanceFor( majorAxisDistance(direction, source, rect2), minorAxisDistance(direction, source, rect2))); }
/** * Is destRect a candidate for the next focus given the direction? This * checks whether the dest is at least partially to the direction of (e.g left of) * from source. * * Includes an edge case for an empty rect (which is used in some cases when * searching from a point on the screen). */ booleanisCandidate(Rect srcRect, Rect destRect, int direction){ switch (direction) { case View.FOCUS_LEFT: return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left; case View.FOCUS_RIGHT: return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right; case View.FOCUS_UP: return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top; case View.FOCUS_DOWN: return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom; } thrownew IllegalArgumentException("direction must be one of " + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); }
/** * One rectangle may be another candidate than another by virtue of being * exclusively in the beam of the source rect. * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's * beam */ booleanbeamBeats(int direction, Rect source, Rect rect1, Rect rect2){ finalboolean rect1InSrcBeam = beamsOverlap(direction, source, rect1); finalboolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
// 1.还是以左为例子,如果rect2在垂直方向重合或者rect1垂直方向不重合,则rect1不比rect2合适 // if rect1 isn't exclusively in the src beam, it doesn't win if (rect2InSrcBeam || !rect1InSrcBeam) { returnfalse; }
// we know rect1 is in the beam, and rect2 is not
// 2.source不在rect2的左边 // if rect1 is to the direction of, and rect2 is not, rect1 wins. // for example, for direction left, if rect1 is to the left of the source // and rect2 is below, then we always prefer the in beam rect1, since rect2 // could be reached by going down. if (!isToDirectionOf(direction, source, rect2)) { returntrue; }
// 3.如果是左右方向,则react1更合适 // for horizontal directions, being exclusively in beam always wins if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) { returntrue; }
// 4.以按下键为例,如果rect1顶部到source底部的距离小于rect2底部到source底部到距离,则rect1更合适 // for vertical directions, beams only beat up to a point: // now, as long as rect2 isn't completely closer, rect1 wins // e.g for direction down, completely closer means for rect2's top // edge to be closer to the source's top edge than rect1's bottom edge. return (majorAxisDistance(direction, source, rect1) < majorAxisDistanceToFarEdge(direction, source, rect2)); }
/** * Fudge-factor opportunity: how to calculate distance given major and minor * axis distances. Warning: this fudge factor is finely tuned, be sure to * run all focus tests if you dare tweak it. */ longgetWeightedDistanceFor(long majorAxisDistance, long minorAxisDistance){ return13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance; }
/** * Call this to try to give focus to a specific view or to one of its descendants * and give it hints about the direction and a specific rectangle that the focus * is coming from. The rectangle can help give larger views a finer grained hint * about where focus is coming from, and therefore, where to show selection, or * forward focus change internally. * * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns * false), or if it is focusable and it is not focusable in touch mode * ({@link #isFocusableInTouchMode}) while the device is in touch mode. * * A View will not take focus if it is not visible. * * A View will not take focus if one of its parents has * {@link android.view.ViewGroup#getDescendantFocusability()} equal to * {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}. * * See also {@link #focusSearch(int)}, which is what you call to say that you * have focus, and you want your parent to look for the next one. * * You may wish to override this method if your custom {@link View} has an internal * {@link View} that it wishes to forward the request to. * * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT * @param previouslyFocusedRect The rectangle (in this View's coordinate system) * to give a finer grained hint about where focus is coming from. May be null * if there is no hint. * @return Whether this view or one of its descendants actually took focus. */ publicbooleanrequestFocus(int direction, Rect previouslyFocusedRect){ return requestFocusNoSearch(direction, previouslyFocusedRect); }
privatebooleanrequestFocusNoSearch(int direction, Rect previouslyFocusedRect){ // need to be focusable if (!canTakeFocus()) { returnfalse; }
// need to be focusable in touch mode if in touch mode if (isInTouchMode() && (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) { returnfalse; }
// need to not have any parents blocking us if (hasAncestorThatBlocksDescendantFocus()) { returnfalse; }
/** * Give this view focus. This will cause * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called. * * Note: this does not check whether this {@link View} should get focus, it just * gives it focus no matter what. It should only be called internally by framework * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}. * * @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which * focus moved when requestFocus() is called. It may not always * apply, in which case use the default View.FOCUS_DOWN. * @param previouslyFocusedRect The rectangle of the view that had focus * prior in this View's coordinate system. */ voidhandleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect){ if (DBG) { System.out.println(this + " requestFocus()"); }
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) { mPrivateFlags |= PFLAG_FOCUSED;
// Unfocus us, if necessary super.unFocus(focused);
// 2.如果已经获取焦点的mFocused不是传进来的child,则清除掉mFocused的Focus状态,并且把child赋给mFocused // We had a previous notion of who had focus. Clear it. if (mFocused != child) { if (mFocused != null) { mFocused.unFocus(focused); }