主要涉及到的類:ViewRootImp筹裕,ViewGroup抖僵,View,F(xiàn)ocusFinder
當事件發(fā)生時镰绎,最主要是從ViewRootImpl的processKeyEvent開始處理分發(fā)脓斩。
1.ViewRootImpl
? ? 1.連接WindowManager和DecorView的紐帶
? ? 2.完成view的measure,layout,draw
? ? 3.向DecorView分發(fā)按鍵畴栖、觸摸事件等随静。
? ? ? 1.先判斷是否有按鍵事件處理
? ?? 1.若返回true,則打斷該方向上的焦點尋找。
? ??2.若返回fasle燎猛,則根據(jù)指定的方向?qū)ふ易罱铱色@取焦點的view
? ?? ? 1.如果mView.findFocus()找到了focused
? ?? ? ?1.1判斷mView的類型恋捆,是否為ViewGroup。
? ?? ? ? 1.2判斷該focused是否是mView內(nèi)的view
offsetDescendantRectToMyCoords()該方法判斷
? ?? ? 2.如果沒有找到則調(diào)用自身的focusSearch() ===此處后文會展開==
補充:ViewRootImpl中的mView指的是Activity中的DecorView后文中會頻繁的對mView進行判斷或調(diào)用mView的方法重绷。
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);
}
}
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
//首先由dispatchKeyEvent進行分發(fā)昭卓,如果返回true的愤钾,則不再繼續(xù)。
//未被處理KeyEvent處理候醒,則進入尋找下一個焦點的流程能颁。
if (mView.dispatchKeyEvent(event)) {
//該方法內(nèi)部首先判斷擁有focus的view,是否重寫了onKeyDown火焰、onKeyUp方法劲装,事件會交給它優(yōu)先處理。
//當它返回true時昌简,那么事件不再繼續(xù)傳遞占业。也就是說我們可以通過重寫返true來攔截。
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.isCtrlPressed()
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
if (mView.dispatchKeyShortcutEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
}
//若未被子view攔截纯赎,開始處理按鍵谦疾,根據(jù)direction進行處理
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int direction = 0;
switch (event.getKeyCode()) {
//根據(jù)code設置direction的值...............
}
if (direction != 0) {
//DecorView會一層一層往下調(diào)用findFocus方法找到當前獲取焦點的View
View focused = mView.findFocus();
if (focused != null) {
========= 1. 展開分析focused.focusSearch() ===========
View v = focused.focusSearch(direction);
if (v != null && v != focused) ;
focused.getFocusedRect(mTempRect);
//若是ViewGroup類型,計算被聚焦的view犬金,是否在mView內(nèi)部
if (mView instanceof ViewGroup) {
((ViewGroup) mView).offsetDescendantRectToMyCoords(
focused, mTempRect);
((ViewGroup) mView).offsetRectIntoDescendantCoords(
v, mTemRect);
}
========= 2.此處下面展開分析 =========
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
return FINISH_HANDLED;
}
}
if (mView.dispatchUnhandledMove(focused, direction)) {
return FINISH_HANDLED;
}
} else {
========= 3.如果focused為null念恍,以下展開分析 ===========
View v = focusSearch(null, direction);
if (v != null && v.requestFocus(direction)) {
return FINISH_HANDLED;
}
}
}
}
return FORWARD;
}
1.1展開分析ViewRootImpl,調(diào)用了View.focusSearch()
作用:1.尋找指定方向上的view
2.判斷是否有mParent,即其父view晚顷,交給父view處理峰伙,如其父view是RecyclerView,則先讓RecylerView的focusSearch()執(zhí)行该默,若內(nèi)部調(diào)用了super.focusSearch()瞳氓,則還會交給ViewGroup處理。
如下圖:
ViewGroup內(nèi)部會不斷地向上調(diào)父View的focusSearch()(如下圖),具體代碼可以看后文關于ViewGroup的分析栓袖。
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
關于mParent的由來匣摘,如果view存在mParent,則其父view是ViewGroup裹刮。
子view被add在ViewGrop中音榜,調(diào)用addView()---->addViewInne()時,會為子view賦值parent為this捧弃。
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
addInArray(child, index);
//此處省略N行.......
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
//此處省略N行.......
}
1.2展開分析ViewRootImpl內(nèi)部調(diào)用View的requestFocus()
requestFocus() ----> requestFocusNoSearch() ----->handleFocusGainInternal()
1.調(diào)用mParent.requestChildFocus()通知父控件赠叼,即將獲取焦點。
2.通知其他部件,焦點即將發(fā)生變化梅割。
3.通知回調(diào)霜第。
4.強制布局更新繪制。
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
mPrivateFlags |= PFLAG_FOCUSED;
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
//此時調(diào)用view的parent的requestChildFocus的回調(diào)户辞,
//可重寫RecyclerView的requestChildFocus做一些處理泌类。
mParent.requestChildFocus(this, this);
updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
//調(diào)用globalFocus回調(diào) mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
1.3展開分析當mView沒有找到focused時,ViewRootImp調(diào)用自身requestFocus
最終會調(diào)用DecorView的focusSearch方法底燎。而DecorView的focusSearch方法找到的焦點view是通過FocusFinder來找到的.
1.檢查線程
2.判斷mView是否為ViewGroup
3.使用FocusFinder尋找焦點 ----->后文會分析如何尋找焦點
public View focusSearch(View focused, int direction) {
checkThread();
if (!(mView instanceof ViewGroup)) {
return null;
}
return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
}
2.ViewGroup內(nèi)的dispatchKeyEvent方法
規(guī)則:
1.如果這個viewGroup持有焦點, 那么就會直接調(diào)用super.dispatchKeyEvent()
2.如果是它的子控件持有焦點, 那么就會調(diào)用子控件的view.dispatchKeyEvetn()
關于其分發(fā)策略的標記:
FOCUS_BLOCK_DESCENDANTS: 攔截焦點, 直接自己嘗試獲取焦點
FOCUS_BEFORE_DESCENDANTS: 首先自己嘗試獲取焦點, 如果自己不能獲取焦點, 則嘗試讓子控件獲取焦點
FOCUS_AFTER_DESCENDANTS: 首先嘗試把焦點給子控件, 如果所有子控件都不要, 則自己嘗試獲取焦點
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
//mInputEventConsistencyVerifier是調(diào)試用的刃榨,暫時不理會。
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
//如果viewgroup持有焦點双仍,先調(diào)用其自身的dispacthKeyevent()
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
//如果子view持有焦點枢希,先將事件傳給子view。
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
3.View內(nèi)的dispatchKeyEvent方法
1.先處理當前view的onKey監(jiān)聽
2.再處理其他監(jiān)聽的回調(diào)
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
ListenerInfo li = mListenerInfo;
//判斷view是否注冊了onKeyListener監(jiān)聽朱沃,先判斷其返回值苞轿,若為true,則事件處理到此為止逗物。
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
//dispatch()內(nèi)部處理其他的回調(diào)事件搬卒,判斷是否被攔截處理。
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
4.FocusFinder
實現(xiàn)根據(jù)給定的按鍵方向翎卓,通過已獲取焦點的View契邀,查找下一個獲取焦點的view規(guī)則算法的類。焦點沒有被攔截的情況下失暴,F(xiàn)ocusFinder的查找規(guī)則來查找坯门。
關鍵方法findNextFocus,通過給定的矩形坐標逗扒,尋找根視圖的子view中可以獲取focus的view
規(guī)則:
1.優(yōu)先尋找用戶在direction上已經(jīng)指定獲取focus的view古戴。
如果有,則直接返回該view矩肩。如果不存在现恼,則進入2.
2.把根root中所有可以獲取focus的view添加到focusables列表中。
根root一般是viewGroup蛮拔,則調(diào)用其addFocusablse述暂,其會遍歷所有child痹升,調(diào)用child的addFocusable建炫。
【 這里有一個誤區(qū),認為會取direction方向上的view疼蛾,實際上未以direction來確定添加肛跌,而是將所有的v可focus的view都add到列表中】
3.根據(jù)現(xiàn)有focused,所有可focus的focusables,尋找下一個合適的view
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
//若當前focus的不為空
if (focused != null) {
//優(yōu)先一層一層尋找該用戶已經(jīng)指定的可獲取焦點的view
//執(zhí)行當前focus的view的findUserSetNextFocus方法
//如果該方法返回的View不為空衍慎,且isFocusable = true && isInTouchMode() = true的話转唉。
//FocusFinder找到的焦點就是findNextUserSpecifiedFocus()返回的View。
next = findNextUserSpecifiedFocus(root, focused, direction);
}
if (next != null) {
//若能找到稳捆,則直接返回赠法。
return next;
}
ArrayList<View> focusables = mTempList;
try {
//賦值后,先清空該對象的歷史值
focusables.clear();
//添加任何可聚焦的view乔夯,這些view是root的子view(可能)
//包括這個視圖砖织,如果它本身可以聚焦到視圖。如果我們處于觸摸模式末荐,添加在觸摸模式中也是可聚焦的視圖侧纯。
root.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
//根據(jù)root,當前focus的view甲脏,其坐標矩形眶熬,按鍵方向,所有可獲取焦點的view块请,尋找下一個符合條件的view
next = findNextFocus(root, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
4.1優(yōu)先尋找用戶在direction上已經(jīng)指定獲取focus的view
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
if (userSetNextFocus != null && userSetNextFocus.isFocusable()
&& (!userSetNextFocus.isInTouchMode()
|| userSetNextFocus.isFocusableInTouchMode())) {
return userSetNextFocus;
}
return null;
}
調(diào)用view中的findUserSetNextFocus()
View findUserSetNextFocus(View root, @FocusDirection int direction) {
switch (direction) {
case FOCUS_LEFT:
if (mNextFocusLeftId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusLeftId);
case FOCUS_RIGHT:
if (mNextFocusRightId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusRightId);
case FOCUS_UP:
if (mNextFocusUpId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusUpId);
case FOCUS_DOWN:
if (mNextFocusDownId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusDownId);
case FOCUS_FORWARD:
if (mNextFocusForwardId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusForwardId);
case FOCUS_BACKWARD: {
if (mID == View.NO_ID) return null;
final int id = mID;
return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
@Override
public boolean apply(View t) {
return t.mNextFocusForwardId == id;
}
});
}
}
return null;
}
4.2尋找所有可focus的child中合適的view規(guī)則
具體沒有獲取指定view后娜氏,尋找該方向上,可獲取view的findNextFocus方法
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList<View> focusables) {
if (focused != null) {
if (focusedRect == null) {
focusedRect = mFocusedRect;
}
// 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:
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:
return findNextFocusInAbsoluteDirection(focusables, root, focused,
focusedRect, direction);
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
}
5.小結(jié)
ViewRootImpl接收按鍵事件负乡,并對其進行分發(fā)處理牍白。
1.DecorView會調(diào)用dispatchKey逐層進行焦點的分發(fā),若某個view的dispatchKeyEvent方法返回true抖棘。則按鍵不再傳遞茂腥,焦點都不再繼續(xù)處理。(可對其設置OnKeyListener監(jiān)聽切省,返true即可達到不再傳遞目的)
2.如果焦點沒有被攔截的話最岗,則進入查找流程。首先判斷當前mView是否有可獲取focus的View朝捆。
???? 2.1若有:根據(jù)方向查找該View內(nèi)是否有符合條件的view般渡。若找到,先判斷其mView是否為ViewGroup芙盘,然后判斷該view是否在mView內(nèi)部驯用。然后請求獲取焦點requestFocus()
???? 2.2若無:直接調(diào)用ViewRootImp內(nèi)的focusSearch()方法,該方調(diào)用FocusFinder的findNextFocus來查找合適的控件儒老。
4.FocusFinder優(yōu)先尋找開發(fā)者指定該方向上下一個可獲取的view(比如在XML文件中指定了下一個可獲取焦點的View的ID蝴乔。如果沒有,則使用FocusFinder類內(nèi)的方法findNextFocus()來查找驮樊。