[TOC]

焦点分发基础知识

获取焦点的前提

View#isFocusable返回true, 如果在触摸模式, 则View#isFocusableInTouchMode也要返回true

控件必须可见

控件相关的父控件, 包括祖父控件等, ViewGroup#getDescendantFocusability()不能为ViewGroup#FOCUS_BLOCK_DESCENDANTS

焦点相关api

requestFocus

主动请求焦点

clearFocus

主动清除焦点

focusSearch

递归搜索需要焦点的View

ViewGroup.setDescendantFocusability

三个参数含义:

FOCUS_BEFORE_DESCENDANTS

ViewGroup本身相对焦点进行处理,如果沒有处理则分发给child View进行处理

FOCUS_AFTER_DESCENDANTS

先分发给Child View进行处理,如果所有的Child View都沒有处理,則自己再处理

FOCUS_BLOCK_DESCENDANTS

ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理

onRequestFocusInDescendants

可以传入方向来改变遍历的顺序, 默认是从0递增,遍历子控件,调用子控件的View#requestFocus来尝试把焦点给可见的子控件, 某个子控件成功获取到焦点后, 停止遍历

注: 重写该方法可以改变ViewGroup分发焦点给子控件的行为, 例如遍历顺

requestChildFocus

当子控件主动放弃焦点的时候,回调这个方法通知父控件.

clearChildFocus

当子控件获取了焦点后, 回调这个方法通知父控件

focusableViewAvailable

通知父控件, 子控件的状态发生改变, 从不能获取焦点, 变成可能可以获取焦点.

hasFocus

当前视图是否是焦点视图或子视图里面有焦点视图

isFocused()

当前视图是否是焦点视图

findFocus

查找焦点控件

getFocusedChild

获取子控件的焦点view

焦点分发源码分析

在Android中任何事件都会经由ViewRootImpl$ViewPostImeInputStage的onProcess处理.在onProcess方法中会判断不同事件类型做不同的处理.这里我们只分析触发焦点转移的KeyEvent.事件分发机制参考

当我们触发按键,最终会由底层把事件传入onProcess,在onProcess中如果是KeyEvent类型就进入processKeyEvent方法中进行处理.

processKeyEvent方法源码如下:

private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (event.getAction() != KeyEvent.ACTION_UP) {
// If delivering a new key event, make sure the window is
// now allowed to start updating.
handleDispatchDoneAnimating();
}
//1.向View树分发KeyEvent事件
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
//........................此处省略部分代码
//2.处理焦点的转移
// Handle automatic focus changes.
//........................此处省略部分代码
if (direction != 0) {
//step1 找到当前View树中的焦点View
View focused = mView.findFocus();
if (focused != null) {
//step2 当前焦点View根据方向搜索下一个需要焦点的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);
}
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
return FINISH_HANDLED;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return FINISH_HANDLED;
}
} else {
// find the best view to give focus to in this non-touch-mode with no-focus
//step3 ViewRootImpl根据方向搜索下一个需要焦点的View并主动请求焦点
View v = focusSearch(null, direction);
if (v != null && v.requestFocus(direction)) {
return FINISH_HANDLED;
}
}
}
}

这个方法中主要做了两件事,1.向View树分发KeyEvent事件 2.处理焦点的转移,下面主要分析处理焦点的转移这条逻辑.

1.向View树分发KeyEvent事件

// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}

这就是我们熟悉View层的事件分发,同理还有

dispatchHoverEvent
dispatchTouchEvent

2.处理焦点的转移

step1 : 当我们的按键有一个确定的方向,首先找到当前View树中的焦点view.

if (direction != 0) {
View focused = mView.findFocus();
}

step2 : 如果找到了当前焦点View,则调用当前焦点View的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

View v = focused.focusSearch(direction);
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
return FINISH_HANDLED;
}

最终View#focusSearch直接调用的是ViewPaent#focusSearch

ViewParent是一个接口,最后在ViewGroup中实现,后文分析ViewGroup#focusSearch,View的focusSearch方法如下:

public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}

step3 : 如果未找到当前焦点View,则调用ViewRootImpl的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

View v = focusSearch(null, direction);
if (v != null && v.requestFocus(direction)) {
return FINISH_HANDLED;
}

本质上ViewRootImpl#focusSearch方法重写的是ViewPaent#focusSearch,此方法内部直接调用焦点帮助类,返回一个需要焦点的View.方法如下:

@Override
public View focusSearch(View focused, int direction) {
checkThread();
if (!(mView instanceof ViewGroup)) {
return null;
}
return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
}
至此,焦点分发的逻辑就进入了View层,下面分析View层的逻辑.
ViewGroup#focusSearch
因为View的focusSearch直接调用的是ViewParent的focusSearch,所以我们只分析ViewGroup的focusSearch
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的focusSearch方法非常简单,如果是root View就调用焦点帮助类返回一个需要焦点的View,反之递归调用ViewGroup的focusSearch,直到找到一个需要焦点的View.

requestFocus

了解完focusSearch我们继续分析requestFocus,requestFocus在View和ViewGrope有各自不同的实现.

ViewGroup#requestFocus
@Override
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);
}
}
}

ViewGroup的requestFocus会根据三种分发策略决定是自己先请求还是child先请求,最终都会调用View的requestFocus

View#requestFocus

View的requestFocus会调用到requestFocusNoSearch

在requestFocusNoSearch中做一些位运算后最终调用handleFocusGainInternal

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) {
mParent.requestChildFocus(this, this);
}
if (mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}

handleFocusGainInternal中通知父容器自己请求了焦点,回调onFocusChanged,并刷新View