作者:Cy13er
前言
最近在看一些焦点处理的问题,认真处理起来发现不跟着源码自己走一遍焦点相关的流程,对于问题的分析上会比较困难。所以本文主要对焦点流程进行一次梳理,在处理类似问题时也可以作为手册阅读。
起源
一切都要从ViewRootImpl
收到输入事件开始
// ViewRootImpl.ViewPostImeInputStage.java
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q); // KeyEvent会在此处处理
} 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);
}
}
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
。。。
// 1\. KeyEvent事件分发
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
。。。
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
// 2\. ACTION_DOWN时,寻找下一个获取焦点的View
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
焦点流程主要分成两部分问题:
- KeyEvent事件分发,寻找当前焦点需要消费事件的子View,并消费事件,见注释1处
- 若事件没有消费,而且当前是
KeyEvent.ACTION_DOWN
时,需要寻找下一个焦点,见注释2处
寻找下一个焦点
// ViewRootImpl.ViewPostImeInputStage.java
private boolean performFocusNavigation(KeyEvent event) {
int direction = 0;
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;
} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
direction = View.FOCUS_BACKWARD;
}
break;
}
if (direction != 0) {
// 1\. 获取目前拥有焦点的View
View focused = mView.findFocus();
if (focused != null) {
// 2\. 查找到下一个获取焦点的View
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);
}
// 3\. 让View获取焦点
if (v.requestFocus(direction, mTempRect)) {
boolean isFastScrolling = event.getRepeatCount() > 0;
playSoundEffect(
SoundEffectConstants.getConstantForFocusDirection(direction,
isFastScrolling));
return true;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return true;
}
} else {
if (mView.restoreDefaultFocus()) {
return true;
}
}
}
return false;
}
大概就是3步:
- 通过
findFocus
获取到当前拥有焦点的View - 通过
focusSearch
获取到下一个获取焦点的View - 让下一个获取焦点的View获取焦点
大概流程
这里大概流程如下
按键事件分发
dispatchKeyEvent
KeyEvent事件分发与触摸事件有类似,都是从顶层的DecorView开始寻找消费事件的子View。不同的是,它没有过多复杂的机制,譬如事件拦截。
ViewGroup#dispatchKeyEvent
// ViewGroup.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
。。。
// 1\. 如果父View自己拥有焦点,则判断自己是否消费事件
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
// 2\. 如果该父View包含拥有焦点的子View,将事件分发到对应子View方向
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
。。。
return false;
}
ViewGroup#dispatchKeyEvent
分两种情况:
- 注释1,如果父View自己拥有焦点,则判断自己是否消费事件,实际是调用了父类View中定义的
dispatchKeyEvent
判断。 - 注释2,如果父View当前包含了拥有焦点的子View,则将事件往拥有焦点的子View方向传递。
也就是说,KeyEvent事件分发只会往拥有焦点的子View方向传递。也就有以下两种情况:
ps:红色部分是事件传递的方向。
View#dispatchKeyEvent
// View.java
public boolean dispatchKeyEvent(KeyEvent event) {
。。。
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 1
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
// 2
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
。。。
return false;
}
View#dispatchKeyEvent
就与dispatchTouchEvent类似了,第一步
- 判断
OnKeyListener
是否为null
- 判断
OnKeyListener.onKey
是否返回true
- View是否是
Enable
状态
三者成立则认为View消费事件,否则通过View#onKeyDown或者View#onKeyUp
判断。事件的调用通过KeyEvent#dispatch
,也就是注释2。
ps:与Touch事件类似,DecorView
会先将事件传递给Activity,然后经过PhoneWindow
回传给DecorView
进行事件分发。
获取当前拥有焦点的View
findFocus
findFocus
的作用就是获取到当前拥有焦点的View,View的实现就是通过判断mPrivateFlags
来确定自己是否拥有焦点。如果自己拥有焦点则返回自己。
// View.java
public View findFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}
ViewGroup的实现会在View的基础上扩展:
- 如果自己拥有焦点则返回自己。
- 如果自己包含了拥有焦点的子View,则往拥有焦点的子View方向寻找。
// ViewGroup.java
@Override
public View findFocus() {
if (DBG) {
System.out.println("Find focus in " + this + ": flags="
+ isFocused() + ", child=" + mFocused);
}
if (isFocused()) {
return this;
}
if (mFocused != null) {
return mFocused.findFocus();
}
return null;
}
也就是下图中,红色箭头表示findFocus
的调用方向,红色区域表示当前拥有焦点的View。
下一个获取焦点的View
focusSearch
focusSearch
寻找下一个获取焦点的View,它是以当前拥有焦点的View作为起点,往上传递。View的实现中,会调用父View的focusSearch
,将自己也就是拥有焦点的View传递上去,同时携带当前需要移动的方向direction
。
// View.java
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
ViewGroup中,会分为两种情况:
- 没有到达根视图,那么与View相同直接传递上去。
- 如果到达根视图,则会通过
FocusFinder
这一单例来寻找下一个获取焦点的View。ps:这里的根视图可以理解成是View树的最上层。
// ViewGroup.java
@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);
} else if (mParent != null) {
return mParent.focusSearch(focused, direction);
}
return null;
}
ps:FocusFinder.getInstance().findNextFocus(this, focused, direction)
中的this
的意思是以当前View作为起点寻找下一个获取焦点的View。
方法的调用方向如下图红色部分:
焦点搜索控制技巧
可能会有这样一个需求:使用自定义RecyclerView做一个Tabbar效果时,不想在移动到最左或者最右时,焦点移出RecyclerView,那么可以从focusSearch
入手,拦截它的搜索过程
override fun focusSearch(focused: View?, direction: Int): View? {
if (focused == null || layoutManager == null || adapter == null || adapter?.itemCount == 0)
return super.focusSearch(focused, direction)
val view = super.focusSearch(focused, direction)
if (view != null) {
// findContainingItemView获取到该view所在的父View,如果不在RecyclerView内则返回null
val nextFocusItemView = findContainingItemView(view)
// 为null证明焦点已经移出了RecyclerView
if (nextFocusItemView == null) {
// 左右限制移出RecyclerView
if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {
return focused
}
}
}
return view
}
这里如果是左右移动到边界,则直接返回当前拥有焦点的View,使焦点还在当前View上,不会被移动到别处。
FocusFinder
// FocusFinder.java
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) {
return next;
}
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
// 2\. 通过遍历子View,可以获取焦点的备选View放入focusables集合中
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
// 3\. 通过可以获取焦点的备选view中获取到下一个获取焦点的View
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
FocusFinder.getInstance().findNextFocus
中主要做了以下3件事:
- 尝试获取用户在xml中指定的下一个获取焦点的View
- 通过遍历子View,将可以获取到下一个焦点的子View加入到备选集合
focusables
中 - 通过上述的备选集合
focusables
,筛选出下一个获取焦点的View
用户在XML指定下一个获取焦点的View
用户可以在XML中指定不同方向下,基于该View的下一个获取焦点的View。
android:nextFocusLeft="@id/xxx"
android:nextFocusRight="@id/xxx"
android:nextFocusDown="@id/xxx"
android:nextFocusUp="@id/xxx"
获取焦点获取备选集合
如果用户未指定下一个获取焦点的View,则会通过备选集合的方式筛选出下一个获取焦点的View。首先需要获取到这个备选集合。
- 先来看看View的实现:
// 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 (!canTakeFocus()) {
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
&& !isFocusableInTouchMode()) {
return;
}
views.add(this);
}
这里比较简单,如果符合获取焦点的资格,就会将自己添加到集合当中。ps:views即为获取焦点的备选集合。
- 再来看看ViewGroup的实现:
// ViewGroup.java
@Override
public 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);
// 1\. 若descendantFocusability为FOCUS_BLOCK_DESCENDANTS,则会拦截子View获取焦点
if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
if (focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
return;
}
if (blockFocusForTouchscreen) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}
// 2\. 若descendantFocusability为FOCUS_BEFORE_DESCENDANTS,则会优先将自己加入到集合
if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
int count = 0;
final View[] children = new View[mChildrenCount];
for (int i = 0; i < mChildrenCount; ++i) {
View child = mChildren[i];
// 3\. 只允许添加当前是Visible的子View
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
children[count++] = child;
}
}
FocusFinder.sort(children, 0, count, this, isLayoutRtl());
for (int i = 0; i < count; ++i) {
children[i].addFocusables(views, direction, focusableMode);
}
// 4\. 若descendantFocusability为FOCUS_AFTER_DESCENDANTS,
// 则如果没有子View符合条件时将自己添加到集合
if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
&& focusableCount == views.size()) {
super.addFocusables(views, direction, focusableMode);
}
}
首先需要先科普一下ViewGroup获取焦点的3种策略
-
FOCUS_BEFORE_DESCENDANTS
: 优先于子View获取焦点 -
FOCUS_AFTER_DESCENDANTS
: 当子View都不获取焦点时,才获取焦点 -
FOCUS_BLOCK_DESCENDANTS
: 禁止子View获取焦点
同样可以通过XML设置,譬如:
android:descendantFocusability="beforeDescendants"
而这里就是根据不同的策略对集合的添加进行控制:
- 若
descendantFocusability = FOCUS_BLOCK_DESCENDANTS
,则会拦截子View获取焦点,见注释1处 - 若
descendantFocusability = FOCUS_BEFORE_DESCENDANTS
,则会优先将自己加入到集合,见注释2处 - 遍历子View,并调用它的addFocusables,如果是ViewGroup的话就会起到往下递归目的,见注释3处
- 若
descendantFocusability = FOCUS_AFTER_DESCENDANTS
的话,则如果没有子View符合条件时将自己添加到集合,见注释4处
焦点记忆技巧
如果需要做焦点记忆的需求,就可以考虑在addFocusables
这里下手了,譬如在RecyclerView中记录当前选中的位置,在addFocusables
时只将该位置对应的View添加到集合,这样就能将焦点重新给到这个View。
override fun addFocusables(views: ArrayList<View>?, direction: Int, focusableMode: Int) {
// 根据选中位置,重新获取到View
val view: View? = layoutManager?.findViewByPosition(currentSelectedPosition)
if (hasFocus() || currentSelectedPosition < 0 || view == null) {
super.addFocusables(views, direction, focusableMode)
} else if (view.isFocusable && view.visibility == View.VISIBLE) {
// 只将这个View添加到集合,并终止方法往下递归
views?.add(view)
} else {
super.addFocusables(views, direction, focusableMode)
}
}
寻找下一个焦点
// FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList<View> focusables) {
if (focused != null) {
if (focusedRect == null) {
focusedRect = mFocusedRect;
}
// 1\. 获取到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:
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:
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:
return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
direction);
case View.FOCUS_UP:
case View.FOCUS_DOWN:
case View.FOCUS_LEFT:
case View.FOCUS_RIGHT:
// 2\. 根据focused所在区域获取到下一个获取焦点的View
return findNextFocusInAbsoluteDirection(focusables, root, focused,
focusedRect, direction);
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
}
焦点寻找是就近原则,默认会根据对比两块Rect区域的位置,来确定焦点位置。所以findNextFocus
做了两件事:
- 求出当前拥有焦点的View即focused的Rect区域,并将它转换成与根视图root相同的坐标系。ps:这样是为了后续比较时能在同一坐标系下比较。
- 根据focused所在区域获取到下一个获取焦点的View
// FocusFinder.java
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
Rect focusedRect, int direction) {
// 1\. 最佳候选View所在的区域,初始值为focused的区域
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;
// 2\. 遍历候选集合,筛选出最佳候选区域
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;
// 3\. 获取候选的View所在区域,并将它转换为与root相同坐标系
focusable.getFocusedRect(mOtherRect);
root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
// 4\. 对比两个区域,如果可行,就更新最佳候选View所在的区域
if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
mBestCandidateRect.set(mOtherRect);
closest = focusable;
}
}
return closest;
}
mBestCandidateRect
为最佳候选View的区域,这里大概分几步:
- 将
mBestCandidateRect
初始化为当前拥有焦点的区域focusedRect
- 通过遍历候选集合,筛选出最佳候选区域,与其对应的View对象
- 在对比前需要将候选的View所在区域转换成与root相同坐标系
- 对比区域可行后,会更新
mBestCandidateRect
,以及对应的View对象,整个过程类似求最大值的过程。
至此,下一个获取焦点的View就得出来了。
获取焦点
requestFocus
View#requestFocus
View获取焦点的流程
// View.java
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
}
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
if (!canTakeFocus()) {
return false;
}
// 在TouchMode下需要声明focusableInTouchMode为true才能获取焦点
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
// 如果存在descendantFocusability为FOCUS_BLOCK_DESCENDANTS的父View则不能获取焦点
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
if (!isLayoutValid()) {
mPrivateFlags |= PFLAG_WANTS_FOCUS;
} else {
clearParentsWantFocus();
}
// 获取焦点
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
mPrivateFlags |= PFLAG_FOCUSED; // 1\. 标记为获取了焦点
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
// 2\. 通知自己的父View自己获取了焦点
mParent.requestChildFocus(this, this);
updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
// 3\. 回调viewTreeObserver的OnGlobalFocusChange
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
// 4\. 如果有注册OnFocusChangeListener,会有回调
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
View#requestFocus
最后会调用到handleFocusGainInternal
方法
- 更新
mPrivateFlags
,标记为自己已经获取了焦点 - 调用
ViewGroup#requestChildFocus
,通知自己的父View自己获取了焦点 - 回调相关监听
viewTreeObserver的OnGlobalFocusChange
OnFocusChangeListener.onFocusChange
ViewGroup#requestChildFocus
// ViewGroup.java
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
// 如果descendantFocusability为FOCUS_BLOCK_DESCENDANTS则直接返回
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus(focused);
// 1\. 清空旧的焦点
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
// 2\. 继续往父View方向传递
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
ViewGroup#requestChildFocus
主要做了两件事:
- 清空旧的焦点状态
- 继续调用父View的
requestChildFocus
,目的是继续更新父View的记录状态
ViewGroup#requestFocus
ViewGroup会对requestFocus进行重写
// ViewGroup.java
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " ViewGroup.requestFocus direction="
+ direction);
}
int descendantFocusability = getDescendantFocusability();
boolean result;
switch (descendantFocusability) {
// 1\. 不允许子View获取焦点
case FOCUS_BLOCK_DESCENDANTS:
result = super.requestFocus(direction, previouslyFocusedRect);
break;
// 2\. 自己优先于子View获取焦点
case FOCUS_BEFORE_DESCENDANTS: {
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
result = took ? took : onRequestFocusInDescendants(direction,
previouslyFocusedRect);
break;
}
// 3\. 子View优先于自己获取焦点
case FOCUS_AFTER_DESCENDANTS: {
final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
break;
}
default:
throw new IllegalStateException("descendant focusability must be "
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+ "but is " + descendantFocusability);
}
if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
mPrivateFlags |= PFLAG_WANTS_FOCUS;
}
return result;
}
// 4\. 寻找可以获取焦点的子View
protected boolean onRequestFocusInDescendants(int direction,
Rect previouslyFocusedRect) {
int index;
int increment;
int end;
int count = mChildrenCount;
if ((direction & FOCUS_FORWARD) != 0) {
index = 0;
increment = 1;
end = count;
} else {
index = count - 1;
increment = -1;
end = -1;
}
final View[] children = mChildren;
for (int i = index; i != end; i += increment) {
View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
if (child.requestFocus(direction, previouslyFocusedRect)) {
return true;
}
}
}
return false;
}
onRequestFocusInDescendants
的作用是寻找一个子View获取焦点,见注释4处
这里主要是扩展了获取焦点策略的能力,也是根据定义
-
FOCUS_BLOCK_DESCENDANTS
:不允许子View获取焦点,所以这里直接令自己去获取焦点,见注释1处 -
FOCUS_BEFORE_DESCENDANTS
:自己优先于子View获取焦点,所以这里会先自己调用super.requestFocus
,再通过结果调用onRequestFocusInDescendants
,见注释2处 -
FOCUS_AFTER_DESCENDANTS
:子View优先于自己获取焦点,所以会先通过onRequestFocusInDescendants
返回的结果,再视情况令自己获得焦点,见注释3处
开发中,可以重写onRequestFocusInDescendants
,以此来控制您想希望获取焦点的子View获取焦点。
默认获取焦点
回到刚开始的流程,如果目前还没有找到拥有焦点的View,会怎样呢?
// ViewRootImpl.ViewPostImeInputStage.java#performFocusNavigation
View focused = mView.findFocus();
if (focused != null) {
。。。
} else {
if (mView.restoreDefaultFocus()) {
return true;
}
}
// View.java
public boolean restoreDefaultFocus() {
return requestFocus(View.FOCUS_DOWN);
}
如果找不到当前拥有焦点的View,则会调用View#restoreDefaultFocus
,实际上就是requestFocus
,一般其实是ViewGroup#requestFocus
,因为mView是DecorView
。那这样就有机会通过onRequestFocusInDescendants
令子View获取到焦点。
最后
本文主要对焦点流程进行了梳理,包括焦点的产生与搜索过程,事件分发的过程。
网友评论