
作者简介
罗铁锤,六年安卓踩坑经验,致力于底层平台、上层应用等多领域开发。文能静坐弹吉他,武能通宵写代码
这一节内容,我们来聊聊 dispatchKeyEvent。
1 按键机制
<ViewRootImpl.java>/*** Delivers post-ime input events to the view hierarchy.*/final class ViewPostImeInputStage extends InputStage {public ViewPostImeInputStage(InputStage next) {super(next);}@Overrideprotected 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);}}}@Overrideprotected void onDeliverToNext(QueuedInputEvent q) {...}private boolean performFocusNavigation(KeyEvent event) {...}private boolean performKeyboardGroupNavigation(int direction) {...}private int processKeyEvent(QueuedInputEvent q) {...}private int processPointerEvent(QueuedInputEvent q) {...}private void maybeUpdatePointerIcon(MotionEvent event) {...}private int processTrackballEvent(QueuedInputEvent q) {...}private int processGenericMotionEvent(QueuedInputEvent q) {...}}
2 QueuedInputEvent
<ViewRootImpl.java>private static final class QueuedInputEvent {public static final int FLAG_DELIVER_POST_IME = 1 << 0;public static final int FLAG_DEFERRED = 1 << 1;public static final int FLAG_FINISHED = 1 << 2;public static final int FLAG_FINISHED_HANDLED = 1 << 3;public static final int FLAG_RESYNTHESIZED = 1 << 4;public static final int FLAG_UNHANDLED = 1 << 5;public QueuedInputEvent mNext;public InputEvent mEvent;public InputEventReceiver mReceiver;public int mFlags;...}// InputEvent 的两个子类public class KeyEvent extends InputEvent implements Parcelable {}public final class MotionEvent extends InputEvent implements Parcelable {}
<ViewRootImpl.java>@Overrideprotected int onProcess(QueuedInputEvent q) {if (q.mEvent instanceof KeyEvent) {// 接收到的事件是按键事件return processKeyEvent(q);// 进入这个分支,按键事件处理} else {final int source = q.mEvent.getSource();// 手指触摸的 touch 事件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);}}}private int processKeyEvent(QueuedInputEvent q) {final KeyEvent event = (KeyEvent)q.mEvent;// 获取到该按键事件信息,我们常见的 KeyCode,Acton,RepeatCount 等信息都包含在里面// Deliver the key to the view hierarchy.if (mView.dispatchKeyEvent(event)) {// mView 实际上就是 DecorView,这里看到如果 dispatchKeyEvent 返回 true,会直接返回,这里的按键事件分发后面单独一篇讲解,对比 touch 事件分发要简单不少return FINISH_HANDLED;}if (shouldDropInputEvent(q)) {// 是否抛弃该事件,里面主要是判断 View 是否初始化或者还未 add 进来,window 失去焦点(window 失去焦点也就是说该 window 无法交互,所以接收事件也没用,直接返回)return FINISH_NOT_HANDLED;}int groupNavigationDirection = 0;// 根据 tab 和 shift 按键判断导航方向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;}
3 方向键导航处理
<ViewRootImpl.java>private boolean performFocusNavigation(KeyEvent event) {int direction = 0;switch (event.getKeyCode()) {// 将按键事件的键值转换为 View 的焦点导航方向值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;// 向后} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {direction = View.FOCUS_BACKWARD;// 向前}break;}if (direction != 0) {// 是上,下,左,右,前,后其中的一个View focused = mView.findFocus();// 从 decorview 中查找当前的焦点if (focused != null) {View v = focused.focusSearch(direction);// 根据方向查找下一个焦点,调用 parent 的 focusSearch 查找if (v != null && v != focused) {// 已经查找到下一个焦点// do the math the get the interesting rect// of previous focused into the coord system of// newly focused viewfocused.getFocusedRect(mTempRect);// 获取下一个焦点的视图区域if (mView instanceof ViewGroup) {// 平移视图让焦点区域在当前视图中完全可见((ViewGroup) mView).offsetDescendantRectToMyCoords(focused, mTempRect);((ViewGroup) mView).offsetRectIntoDescendantCoords(v, mTempRect);}if (v.requestFocus(direction, mTempRect)) {// 对查找到的焦点 view 调用 requestFocus,清除oldFocus 的焦点状态playSoundEffect(SoundEffectConstants// 播放焦点移动音效,处理结束.getContantForFocusDirection(direction));return true;}}// Give the focused view a last chance to handle the dpad key.if (mView.dispatchUnhandledMove(focused, direction)) {// 查找焦点失败,再提供一个机会去处理该次按键事件下 view 的移动return true;}} else {// 如果当前都没有焦点if (mView.restoreDefaultFocus()) {// 重新初始化默认焦点,处理完毕return true;}}}return false;}
<View.java>public View focusSearch(@FocusRealDirection int direction) {if (mParent != null) {return mParent.focusSearch(this, direction);// 通过 parent 父 View 去查找下一个焦点} else {return null;}}<ViewGroup.java>@Overridepublic View focusSearch(View focused, int direction) {if (isRootNamespace()) {// 当前 view == decorView,一般我们最终会走到这个分支// 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);} else if (mParent != null) {return mParent.focusSearch(focused, direction);}return null;}
<FocusFinder.java>private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {View next = null;// 下一个焦点ViewGroup effectiveRoot = getEffectiveRoot(root, focused);if (focused != null) {next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);// 当前焦点不为 null,首先判断用户对当前焦点的 view 在该方向上是否指定 id,也就是我们通常 xml 中写的 nextFocusLeft 这种}if (next != null) {return next;// 如果用户指定了下个焦点 id,直接返回该 id 对应的 view}ArrayList<View> focusables = mTempList;// 这个集合是用来装所有可获得焦点的 Viewtry {focusables.clear();effectiveRoot.addFocusables(focusables, direction);// 查找可获得焦点的 view,添加进集合if (!focusables.isEmpty()) {// 存在可获得焦点的 viewnext = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);// 继续在所有可获得焦点的 view 集合中查找下一个焦点}} finally {focusables.clear();// 查找完毕后清理数据,释放内存}return next;// 返回下一个焦点}
首先会去判断用户有没有手动在 xml 中指定该方向的下一个焦点 view 的 id,如果指定了直接返回该 view 作为下一个焦点,流程结束。对于 findNextUserSpecifiedFocus 方法逻辑还是比较好理解,在此不做展开分析。 接着会查找所有可获得焦点的view,将它们添加到 focusables 集合中,缩小焦点查找范围。这里有个关键方法:addFocusables ,这个方法在平时定制化开发中可以用于焦点记忆,例如 leanback 视图中每一行 recyclerView 中的焦点记忆。
<View.java>public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);}public void addFocusables(ArrayList<View> views, @FocusDirection int direction,@FocusableMode int focusableMode) {if (views == null) {return;}if (!isFocusable()) {// 不可聚焦,直接返回return;}if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE&& !isFocusableInTouchMode()) {// 触摸模式下,但是 focusInTouchMode 设置为 false,直接返回return;}views.add(this);// 将自己添加到集合中}<ViewGroup.java>@Overridepublic void addFocusables(ArrayList<View> views, int direction, int focusableMode) {final int focusableCount = views.size();final int descendantFocusability = getDescendantFocusability();final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);// 自己可以聚焦if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {// 如果设置了拦截焦点if (focusSelf) {super.addFocusables(views, direction, focusableMode);// 调用 View 的 addFocusables 将自己添加进集合}return;// 直接返回,不再添加自己 view 数结构下面的子 View}if (blockFocusForTouchscreen) {focusableMode |= FOCUSABLES_TOUCH_MODE;}if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {super.addFocusables(views, direction, focusableMode);// 调用 View 的 addFocusables 将自己添加进集合}int count = 0;final View[] children = new View[mChildrenCount];for (int i = 0; i < mChildrenCount; ++i) {// 遍历当前 viewGroup下的所有子 ViewView child = mChildren[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {// view 处于可见状态children[count++] = child;// 赋值给 children 数组}}FocusFinder.sort(children, 0, count, this, isLayoutRtl());// 根据方向排序for (int i = 0; i < count; ++i) {children[i].addFocusables(views, direction, focusableMode);// 如果 children[i] 这个子 view 是viewGroup的话,递归调用继续查找该 child viewGroup下的子 View,直到查找所有最下层的子 view,最终调用 View.addFocusables 判断是否可聚焦,可聚焦则添加进集合}// 走到这里,views 中已经保存了所有可聚焦的子 View// When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if// there aren't any focusable descendants. this is// to avoid the focus search finding layouts when a more precise search// among the focusable children would be more interesting.if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf&& focusableCount == views.size()) {// 如果是 FOCUS_AFTER_DESCENDANTS,除了子 view 判断外,最后将自己也添加进去super.addFocusables(views, direction, focusableMode);}}
<FocusFinder.java>private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {...if (!focusables.isEmpty()) {// 存在可获得焦点的 viewnext = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);// 继续在所有可获得焦点的 view 集合中查找下一个焦点}...return next;// 返回下一个焦点}private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,int direction, ArrayList<View> focusables) {if (focused != null) {// 当前焦点不为 nullif (focusedRect == null) {focusedRect = mFocusedRect;}// fill in interesting rect from focusedfocused.getFocusedRect(focusedRect);// 获取到当前焦点的 rect 区域root.offsetDescendantRectToMyCoords(focused, focusedRect);// 考虑 scroll 滑动状态,即把视框拉伸至滑动到屏幕外的视图也可见状态,统一坐标系便于下面焦点查找计算} else {if (focusedRect == null) {focusedRect = mFocusedRect;// make up a rect at top left or bottom right of rootswitch (direction) {case View.FOCUS_RIGHT:case View.FOCUS_DOWN:setFocusTopLeft(root, focusedRect);// 当前焦点为 null,将滑动后的左上角作为寻找起始点(scrollX,scrollY),走到这里的话这个 focusedRect 实际上是个点break;case View.FOCUS_FORWARD:if (root.isLayoutRtl()) {// 会根据 rtl 区分(某些国家语言是从右往左书写习惯)setFocusBottomRight(root, focusedRect);} else {setFocusTopLeft(root, focusedRect);}break;case View.FOCUS_LEFT:case View.FOCUS_UP:setFocusBottomRight(root, focusedRect);// 当前焦点为 null,将滑动后的右下角作为寻找起始点(scrollX,scrollY),走到这里的话这个 focusedRect 实际上是个点break;case View.FOCUS_BACKWARD:if (root.isLayoutRtl()) {// 会根据 rtl 区分是否将坐标反转setFocusTopLeft(root, focusedRect);} else {setFocusBottomRight(root, focusedRect);break;}}}}switch (direction) {case View.FOCUS_FORWARD:case View.FOCUS_BACKWARD:return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,direction);case View.FOCUS_UP:case View.FOCUS_DOWN:case View.FOCUS_LEFT:case View.FOCUS_RIGHT:// 我们重点只关注这方向键的焦点查找算法return findNextFocusInAbsoluteDirection(focusables, root, focused,focusedRect, direction);default:throw new IllegalArgumentException("Unknown direction: " + direction);}}
如果当前焦点不为 null,先获取当前焦点的 rect 视图区域,考虑到 scroll 状态,将当前焦点的 rect 坐标系进行转换。 如果当前焦点为 null,根据导航方向,设置一个左上角或者右下角的 rect 为默认的起始参考点,根据这个点再结合方向去计算下一个焦点。这里我们重点看下上下左右移动的这个方法 findNextFocusInAbsoluteDirection ,大致看下它内部查找焦点算法:
<FocusFinder.java>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);// 将当前焦点的 rect 赋值给 mBestCandidateRectswitch(direction) {// 在反方向上偏移一个 width 或者 height + 1 个像素点,虚构出来的下一个候补焦点(优先级应该是最低的,因为是反方向平移)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++) {// 开始遍历所有可聚焦的子 viewView focusable = focusables.get(i);// only interested in other non-root viewsif (focusable == focused || focusable == root) continue;// 如果集合中的 view 是当前的焦点或者 viewGroup,直接跳过继续查找下一个// get focus bounds of other view in same coordinate systemfocusable.getFocusedRect(mOtherRect);root.offsetDescendantRectToMyCoords(focusable, mOtherRect);// 将该 view 也进行坐标系转换,和当前焦点在同一个坐标系进行计算if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {mBestCandidateRect.set(mOtherRect);// 如果找到一个符合的,则将其的区域赋值给虚构的候补焦点,参照物变了之后,继续遍历看有没有更优的closest = focusable;// 这个 closest 会不断刷新,因为每次进入该分支,最新的 focusable 符合条件都会优于上一个候补焦点}}return closest;}
<FocusFinder.java>// 几个参数含义:direction 方向,source 当前焦点,rect1 当前对比的 view,rect2 虚构的候补焦点(如果有符合的,rect2 会刷新为当前符合条件的 view 区域,即如果成立,rect1 会赋值给下次该方法的 rect2)// 这几个参数命名比较容易弄混,尤其是下面调用算法的时候又改名了,要区分清楚boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {// to be a better candidate, need to at least be a candidate in the first// place :)if (!isCandidate(source, rect1, direction)) {// 先将当前遍历的 view 与当前焦点比较return false;}// 走到这里说明上面的 isCandidate 返回 true,也就是当前遍历的 rect1 符合条件。例如 direction 为左,说明 rect1 在当前焦点的左侧,符合条件,加入候选,进行下一步判断// we know that rect1 is a candidate.. if rect2 is not a candidate,// rect1 is betterif (!isCandidate(source, rect2, direction)) {// 第一次走到这的话这个 isCandidate 肯定返回 false,因为 rect2 第一次是我们之前虚构的候补焦点,是在导航的反方向,肯定为 false,直接返回 true。再后面的话,相当于上一个候补和当前焦点进行比较,肯定返回 true,继续下一步判断return true;}// if rect1 is better by beam, it winsif (beamBeats(direction, source, rect1, rect2)) {// 当前遍历的 view 也符合条件,将它和上一个候补进行比较return true;// 当前遍历的 view 优于上一个候补,将当前遍历的赋值给最新的 closest,也就是目前遍历过程中最优焦点}// if rect2 is better, then rect1 cant' be :)if (beamBeats(direction, source, rect2, rect1)) {// 上一个候补优于当前遍历的return false;}// otherwise, do fudge-tastic comparison of the major and minor axisreturn (getWeightedDistanceFor(// 计算 rect1 和 rect2 相对于当前焦点的距离majorAxisDistance(direction, source, rect1),minorAxisDistance(direction, source, rect1))< getWeightedDistanceFor(majorAxisDistance(direction, source, rect2),minorAxisDistance(direction, source, rect2)));}
4 重点算法 1 isCandidate
<FocusFinder.java>// srcRect 当前焦点,destRect 比较的 view,direction 方向boolean isCandidate(Rect srcRect, Rect destRect, int direction) {switch (direction) {case View.FOCUS_LEFT:// 向左:只比较 left 和 right,就是 dest 是否整体在 src 的左侧,这里说的是整体,dest 可以与 src 有交集,但是 dest 的左右边界都不能超过 src 的右边界return (srcRect.right > destRect.right || srcRect.left >= destRect.right)&& srcRect.left > destRect.left;case View.FOCUS_RIGHT:// 向右:只比较 left 和 right,就是 dest 是否整体在 src 的右侧,这里说的是整体, dest 可以与 src 有交集,但是 src 的左右边界都不能超过 dest 的右边界return (srcRect.left < destRect.left || srcRect.right <= destRect.left)&& srcRect.right < destRect.right;case View.FOCUS_UP:// 向上:只比较 top 和 bottom,就是 dest 是否整体在 src 的上面,这里说的是整体,dest 可以与 src 有交集,但是 dest 的上下边界都不能超过 src 的下边界return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)&& srcRect.top > destRect.top;case View.FOCUS_DOWN:// 向下:只比较 top 和 bottom,就是 dest 是否整体在 src 的下面,这里说的是整体,dest 可以与 src 有交集,但是 src 的上下边界都不能超过 dest 的下边界return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)&& srcRect.bottom < destRect.bottom;}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}
5 重点算法 2 beamBeats
<FocusFinder.java>// direction 方向,source 当前焦点,rect1 比较的 view1,rect2 比较的 view2(rect1 和rect2 具体看上面算法调用的顺序)// 第一次调用:rect1 当前遍历的 view,rect2 上一次符合条件的候补焦点// 第二次调用:rect1 上一次符合条件的候补焦点,rect2 当前遍历的 viewboolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {// rect1 和当前焦点在相对于导航方向的垂直方向是否有重叠,导航方向为左右 x 轴时比较 y 轴重叠final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);// rect2 和当前焦点在相对于导航方向的垂直方向是否有重叠,导航方向为上下 y 轴时比较 x 轴重叠final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);// if rect1 isn't exclusively in the src beam, it doesn't winif (rect2InSrcBeam || !rect1InSrcBeam) {// rect2 有重叠,或者 rect1 没有重叠// 第一次调用:上一次符合条件的候补焦点与当前焦点有重叠,或者当前遍历的 view 与当前焦点没有重叠return false;// 如果第一次进入此 return false,下次进来肯定跳过这里}// we know rect1 is in the beam, and rect2 is not// 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)) {return true;}// for horizontal directions, being exclusively in beam always winsif ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {return true;}// 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));}
计算相对于导航方向的垂直方向上是否有重叠
<FocusFinder.java>// direction 方向,rect1 当前焦点,rect2 待比较的 viewboolean beamsOverlap(int direction, Rect rect1, Rect rect2) {switch (direction) {case View.FOCUS_LEFT:case View.FOCUS_RIGHT:return (rect2.bottom > rect1.top) && (rect2.top < rect1.bottom);// 左右按键时比较 y 方向是否重叠case View.FOCUS_UP:case View.FOCUS_DOWN:return (rect2.right > rect1.left) && (rect2.left < rect1.right);// 上下按键时比较 x 方向是否重叠}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}
计算是否完全在当前 src 的一侧
<FocusFinder.java>// src 当前焦点,dest 待比较的 viewboolean isToDirectionOf(int direction, Rect src, Rect dest) {// 比较 dest 是否完全在当前焦点的左/右/上/下switch (direction) {case View.FOCUS_LEFT:return src.left >= dest.right;case View.FOCUS_RIGHT:return src.right <= dest.left;case View.FOCUS_UP:return src.top >= dest.bottom;case View.FOCUS_DOWN:return src.bottom <= dest.top;}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}
计算主轴方向距离
<FocusFinder.java>// 计算主轴方向距离static int majorAxisDistance(int direction, Rect source, Rect dest) {return Math.max(0, majorAxisDistanceRaw(direction, source, dest));}static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {switch (direction) {case View.FOCUS_LEFT:return source.left - dest.right;case View.FOCUS_RIGHT:return dest.left - source.right;case View.FOCUS_UP:return source.top - dest.bottom;case View.FOCUS_DOWN:return dest.top - source.bottom;}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}
计算相对于主轴方向的垂直方向距离
<FocusFinder.java>// 计算次轴方向距离static int minorAxisDistance(int direction, Rect source, Rect dest) {switch (direction) {case View.FOCUS_LEFT:case View.FOCUS_RIGHT:// the distance between the center verticalsreturn Math.abs(((source.top + source.height() 2) -((dest.top + dest.height() 2))));case View.FOCUS_UP:case View.FOCUS_DOWN:// the distance between the center horizontalsreturn Math.abs(((source.left + source.width() 2) -((dest.left + dest.width() 2))));}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}
计算相对距离,以 FOCUS_LEFT 为例,majorAxisDistance 相当于当前焦点左侧与比较view的右侧的x轴距离,minorAxisDistance 相当于在y轴方向上,当前焦点中心点与比较 view 的中心点的距离。计算 13 * x² * y²,这个 13 的权重系数不知道 google 是如何制定的,这里就理解为主轴的权重优先级更高吧。(如果是我设计的话,应该会直接计算x和y的距离平方根进行比较了。)
<FocusFinder.java>int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {return 13 * majorAxisDistance * majorAxisDistance+ minorAxisDistance * minorAxisDistance;}

该方法又得和上面的 majorAxisDistance 进行区分,以 FOCUS_LEFT 为例,同样是计算 x 轴方向,但是 majorAxisDistance 计算的是 souce 的左侧和待比较 view 的右侧距离,这个方法计算的是 source 的左侧和待比较 view 的左侧的距离:
<FocusFinder.java>static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));}// 也是计算主轴方向,但是和 majorAxisDistance 有区别static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {switch (direction) {case View.FOCUS_LEFT:return source.left - dest.left;case View.FOCUS_RIGHT:return dest.right - source.right;case View.FOCUS_UP:return source.top - dest.top;case View.FOCUS_DOWN:return dest.bottom - source.bottom;}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}



文章转载自悦专栏,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




