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;
}
複製代碼