Android版本:7.0(API27)算法
[TOC]数组
按键事件分发须要根据控件树对焦点的管理进行事件分发,那控件树是如何管理焦点的呢?就是指经过视图的根View(例如Activity的DecorView)如何能找到控件树中当前获取焦点的控件。咱们将核心内容分为以下三部分:bash
咱们经过View.requestFoucs()的实现来揭示控件树对焦点的管理方式。ide
public final boolean requestFocus() {
return requestFocus(View.FOCUS_DOWN);
}
public final boolean requestFocus(int direction) {
return requestFocus(direction, null);
}
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
}
复制代码
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
// 1.控件可见而且能够得到焦点
if ((mViewFlags & FOCUSABLE) != FOCUSABLE
|| (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// need to be focusable in touch mode if in touch mode
// 2.在触摸模式下能够获取焦点
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
// need to not have any parents blocking us
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
/* 4.调用handleFocusGainInternal使此控件获取焦点 */
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
复制代码
标记3:
若是该View的任意一父类控件(父亲、爷爷、祖爷爷等等)的mGroupFlags设置了FOCUS_BLOCK_DESCENDANTS时,则阻止该控件获取焦点。hasAncestorThatBlocksDescendantFocus会沿着控件树一路回溯到整个控件树的根控件并逐一检查mGroupFlags的设置。
mGroupFlags的设置能够有三种取值,其中一种为FOCUS_BLOCK_DESCENDANTS。当父控件的这一特性取值为FOCUS_BLOCK_DESCENDANT时,父控件将会阻止其子控件、孙子控件等获取焦点。在介绍ViewGroup.requestFocus()时会详细介绍。
标记4:
调用handleFocusGainInternal使此控件获取焦点ui
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
// 1
mPrivateFlags |= PFLAG_FOCUSED;
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
// 2
mParent.requestChildFocus(this, this);
updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
// 3
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
// 4
onFocusChanged(true, direction, previouslyFocusedRect);
// 5
refreshDrawableState();
}
}
复制代码
handleFocusGainInternal让该View获取焦点:
标记1:
设置mPrivateFlags标志为PFLAG_FOCUSED,表示该控件获取焦点。
标记2:
将这一变化通知其父控件,以将焦点从上一个焦点控件夺走并转移到本控件身上,并在ViewRootImpl中触发一次"遍历"以便对控件树进行重绘。
标记3:
触发经过以下代码添加的监听this
view.getViewTreeObserver().addOnGlobalFocusChangeListener(this);
复制代码
标记4:
触发mOnFocusChangeListener的监听
标记5:
刷新drawable获取焦点时的状态spa
上述全部的工做都是在改变旧的焦点体系的状态,设置新的焦点体系;PFLAG_FOCUSED是一个控件拥有焦点的最直接体现,然而这并非焦点管理的所有。这一标记仅仅体现了焦点在个体上的特性;而mParent.requestChildFocus则体现了焦点在控件树级别上的特性。rest
mParent.requestChildFocus是一个定义在ViewParent接口中的方法,其实现者为ViewGroup和ViewRootImpl。
ViewGroup的实现目的有两个:code
ViewGroup.requestChildFocusorm
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
// 1.
super.unFocus(focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
// 2.
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
// 3.
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
复制代码
标记1:
若是以前的焦点控件就是这个ViewGroup,则释放其焦点;
标记2:
标记3:
将这一操做继续向控件树的根部回溯;
此方法中的mFocused是一个View类型的变量,它是控件树焦点管理的核心所在,围绕着mFocused,ViewGroup.requestChildFocus方法包含了新的焦点体系的创建过程,以及旧的焦点体系的销毁过程。
新的焦点体系的创建过程是经过在ViewGroup.requestChildFocus方法的回溯过程当中进行mFocused = child这一赋值操做完成的。当回溯完成后,mFocused = child将会创建起一个单向链表,使得从根控件开始经过mFocused成员能够沿着这一单向链表找到实际拥有焦点的控件,即实际拥有焦点的控件位于这个单向链表的尾端,以下图所示:
旧的焦点体系的销毁过程则是经过在回溯过程当中调用mFocused.unFocus完成的。unFocus有ViewGroup和View两种实现。首先看一下ViewGroup.unfocus的实现:
void unFocus(View focused) {
if (DBG) {
System.out.println(this + " unFocus()");
}
if (mFocused == null) {
super.unFocus(focused);
} else {
mFocused.unFocus(focused);
mFocused = null;
}
}
复制代码
可见ViewGroup.unfocus将unfocus调用沿着mFocused所描述的链表沿着控件树向下遍历,知道焦点的实际拥有者。焦点的实际拥有者会调用View.unFocus(),它会将PFLAG_FOCUSED移除,固然也少不了更新DrawableState以及onFocusChanged()方法的调用。 View.unfocus()
void unFocus(View focused) {
if (DBG) {
System.out.println(this + " unFocus()");
}
clearFocusInternal(focused, false, false);
}
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
mPrivateFlags &= ~PFLAG_FOCUSED;
if (propagate && mParent != null) {
mParent.clearChildFocus(this);
}
onFocusChanged(false, 0, null);
refreshDrawableState();
if (propagate && (!refocus || !rootViewRequestFocus())) {
notifyGlobalFocusCleared(this);
}
}
}
复制代码
控件树最终会调用根控件的requestChildFocus,这里的根控件其实就是ViewRootImpl
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Request child focus: focus now " + focused);
}
checkThread();
scheduleTraversals();
}
复制代码
前面的文章咱们分析过View的绘制流程,ViewRootImpl.scheduleTraversals会触发整个控件树从新绘制,这样整个焦点变化的过程就完成了。
总而言之,控件树的焦点管理分为两部分:
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
int descendantFocusability = getDescendantFocusability();
switch (descendantFocusability) {
case FOCUS_BLOCK_DESCENDANTS:
return super.requestFocus(direction, previouslyFocusedRect);
case FOCUS_BEFORE_DESCENDANTS: {
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
case FOCUS_AFTER_DESCENDANTS: {
final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
return took ? took : super.requestFocus(direction, previouslyFocusedRect);
}
default:
throw new IllegalStateException("descendant focusability must be "
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+ "but is " + descendantFocusability);
}
}
复制代码
根据descendantFocusability不一样的取值,执行的处理方式不一样:
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实际上是一种最简单的焦点查找算法。它按照direction方向,在mChildren列表中依次调用子控件的requestFocus()方法,直到有一个子控件获取焦点。另外,须要注意子控件也多是一个ViewGroup,因此这里将会有一个递归的过程。
当一个控件获取焦点后,用户每每会经过方向键来移动焦点,这时控件系统须要在控件树中指定的方向上寻找距离当前控件最近的一个控件,并将焦点赋予它。与ViewGroup.onRequestFocusInDescendants()方法按照控件在mChildren数组中的顺序查找不一样,这一查找依赖于控件在窗口中的位置。这一工做由View.focusSearch()方法完成。
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
复制代码
又会调用父类的focusSearch,父类就是ViewGroup
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;
}
复制代码
若是此ViewGroup不是根控件,则继续向控件树的根部回溯,直到根控件,而后使用FocusFinder的findNextFocus方法查找下一个焦点。这个方法三个参数的意义以下:
public final 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) {
next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
}
if (next != null) {
return next;
}
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
复制代码
findNextFocus会首先尝试经过findNextUserSpecifiedFocus获取由开发者设置的下一个焦点控件。有时候控件系统内置的焦点查找算法并不能知足开发者的需求,所以须要开发者能够经过View.setNextFocusXXXId()方法或XML中的nextFocusXXX属性设置此控件的下一个可获取焦点的控件Id。其中XXX能够是Left、Right、Top、Bottom和Forword,分别用来设置不一样方向上的下一个焦点控件。
假若开发者在指定方向上没有设置下一个焦点控件,则经过内置的搜索算法进行查找。这个内置算法会首先将控件树中全部可获取焦点的控件添加到一个名为focusables的列表中,并以这个列表做为焦点控件的候选集合。这样作的目的并不只仅是提升效率,更重要的是这个列表打破了控件在控件树中的层次关系。它在必定程度上体现了焦点查找的一个原则,即控件在窗口上的位置是惟一查找依据,与控件在控件树中的层次无关。
至于具体的查找算法,咱们就不深刻去研究了,有兴趣的同窗能够自行阅读。
在“Android事件分发”分析了事件分发的分水岭:
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处理按键事件
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;
}
复制代码
processKeyEvent的代码较长,不过咱们的摸底是抽离核心步骤进行分析便可,processKeyEvent有两个核心内容:
这里是调用ViewRootImpl内部字段mView.dispatchPointerEvent就行触摸事件分发,mView是什么呢?经过前面文章的分析咱们能知道mView实际上是PhoneWindow内部维护的DecorView,而DecorView继承FrameLayout,因此最终调用的是ViewGroup.dispatchKeyEvent
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
// 1.
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
// 2.
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
复制代码
标记1
若是此ViewGroup拥有焦点,则调用super.dispatchKeyEvent尝试消费事件;
标记2
若是此ViewGroup不拥有焦点,则将事件沿折mFocused链表进行传递;若是mFocused为ViewGroup,那么进入递归过程;
View.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;
}
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
复制代码
首先由mOnKeyListener监听者尝试处理事件,若是mOnKeyListener返回true,那么整个案件事件结束。而后经过event.dispatch方法将事件发给View的指定回调,如onKeyDown()/onKeyUp()等。
performFocusNavigation方法:
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) {
View focused = mView.findFocus();
if (focused != null) {
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);
}
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
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;
}
复制代码