2024年8月24日发(作者:)

Android焦点事件分发与传递机制

下面我们就从源码来带大家进行安卓TV焦点事件的传递

这里先给出Android系统View的绘制流程:

依次执行View类里面的如下三个方法:

measure(int ,int) :测量View的大小

layout(int ,int ,int ,int) :设置子View的位置

draw(Canvas) :绘制View内容到Canvas画布上

ViewRootImpl的主要作用如下(此处不多讲,如有意图,看源码):

A:链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的

纽带。

B:完成View的绘制过程,包括measure、layout、draw过程。

C:向DecorView分发收到的用户发起的event事件,如按键,触屏等事件。

ViewRootImpl不再多余叙述,进入正题:

Android焦点分发的主要方法以及拦截方法的讲解。

在RootViewImpl中的函数通道是各种策略(InputStage)的组合,各策略负责的任务不同,

如SyntheticInputStage、ViewPostImeInputStage、NativePostImeInputStage等等,这些策略以

链表结构结构起来,当一个策略者没有消费事件时,就传递个下一个策略者。其中触摸和按

键事件由ViewPostImeInputStage处理。

@Override

protected int onProcess(QueuedInputEvent q) {

if ( instanceof KeyEvent) {

return processKeyEvent(q);//如果是按键事件走此处,处理按键和焦点问题

} else {

final int source = rce();

if ((source & _CLASS_POINTER) != 0) {

return processPointerEvent(q);//如果是触摸事件走此处

} else if ((source & _CLASS_TRACKBALL) != 0) {

return processTrackballEvent(q);

} else {

return processGenericMotionEvent(q);

}

}

}

processKeyEvent(QueuedInputEvent q)源码如下:

@Override

protected void onDeliverToNext(QueuedInputEvent q) {

if (mUnbufferedInputDispatch

&& instanceof MotionEvent

&& ((MotionEvent)).isTouchEvent()

&& isTerminalInputEvent()) {

mUnbufferedInputDispatch = false;

scheduleConsumeBatchedInput();

}

verToNext(q);

}

private int processKeyEvent(QueuedInputEvent q) {

final KeyEvent event = (KeyEvent);

// Deliver the key to the view hierarchy.

if (chKeyEvent(event)) {

return FINISH_HANDLED;

}

if (shouldDropInputEvent(q)) {

return FINISH_NOT_HANDLED;

}

// If the Control modifier is held, try to interpret the key as a shortcut.

if (ion() == _DOWN

&& Pressed()

&& eatCount() == 0

&& !fierKey(Code())) {

if (chKeyShortcutEvent(event)) {

return FINISH_HANDLED;

}

if (shouldDropInputEvent(q)) {

return FINISH_NOT_HANDLED;

}

}

// Apply the fallback event policy.

if (chKeyEvent(event)) {

return FINISH_HANDLED;

}

if (shouldDropInputEvent(q)) {

return FINISH_NOT_HANDLED;

}

// Handle automatic focus changes.

if (ion() == _DOWN) {

int direction = 0;

switch (Code()) {

case E_DPAD_LEFT:

if (odifiers()) {

direction = _LEFT;

}

break;

case E_DPAD_RIGHT:

if (odifiers()) {

direction = _RIGHT;

}

break;

case E_DPAD_UP:

if (odifiers()) {

direction = _UP;

}

break;

case E_DPAD_DOWN:

if (odifiers()) {

direction = _DOWN;

}

break;

case E_TAB:

if (odifiers()) {

direction = _FORWARD;

} else if (ifiers(_SHIFT_ON)) {

direction = _BACKWARD;

}

break;

}

if (direction != 0) {

View focused = cus();

if (focused != null) {

View v = earch(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

usedRect(mTempRect);

if (mView instanceof ViewGroup) {

((ViewGroup)

mView).offsetDescendantRectToMyCoords(

focused, mTempRect);

((ViewGroup) mView).offsetRectIntoDescendantCoords(

v, mTempRect);

}

if (tFocus(direction, mTempRect)) {

playSoundEffect(SoundEffectConstants

.getContantForFocusDirection(direction));

return FINISH_HANDLED;

}

}

// Give the focused view a last chance to handle the dpad key.

if (chUnhandledMove(focused, direction)) {

return FINISH_HANDLED;

}

} else {

// find the best view to give focus to in this non-touch-mode with

no-focus

View v = focusSearch(null, direction);

if (v != null && tFocus(direction)) {

return FINISH_HANDLED;

}

}

}

}

return FORWARD;

}

进入源码讲解:

(1) 首先由dispatchKeyEvent进行焦点的分发

如果dispatchKeyEvent方法返回true,那么下面的焦点查找步骤就不会继续了。

dispatchKeyEvent方法返回true代表事件(包括焦点和按键)被消费了。

dispatchKeyEvent(event)如果不了解,看我上一篇文章安卓TounchEvent事件分发机制。

mView的dispatchKeyEvent方法,

mView是是Activity的顶层容器DecorView,它是一FrameLayout。

所以这里的dispatchKeyEvent方法应该执行的是ViewGroup的dispatchKeyEvent()方法,而

不是View的dispatchKeyEvent方法。

@Override

public boolean dispatchKeyEvent(KeyEvent event) {

if (mInputEventConsistencyVerifier != null) {

vent(event, 1);

}

if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))

== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {

if (chKeyEvent(event)) {

return true;

}

} else if (mFocused != null && (teFlags & PFLAG_HAS_BOUNDS)

== PFLAG_HAS_BOUNDS) {

if (chKeyEvent(event)) {

return true;

}

}

if (mInputEventConsistencyVerifier != null) {

ndledEvent(event, 1);

}

return false;

}

ViewGroup的dispatchKeyEvent简略执行流程

首先ViewGroup会执行父类的dispatchKeyEvent方法,如果返回true那么父类的

dispatchKeyEvent方法就会返回true,也就代表父类消费了该焦点事件,那么焦点事件自然

就不会往下进行分发。

然后ViewGroup会判断mFocused这个view是否为空,如果为空就会****return false,

焦点继续往下传递;如果不为空,那就会return mFocused的dispatchKeyEvent方法返回的

结果。这个mFocused是ViewGroup中当前获取焦点的子View,这个可以从requestChildFocus

方法中得到答案。

requestChildFocus()的源码如下:

@Override

public void requestChildFocus(View child, View focused) {

if (DBG) {

n(this + " requestChildFocus()");

}

if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {

return;

}

// Unfocus us, if necessary

s(focused);

// We had a previous notion of who had focus. Clear it.

if (mFocused != child) {

if (mFocused != null) {

s(focused);

}

mFocused = child;

}

if (mParent != null) {

tChildFocus(this, focused);

}

}

View的dispatchKeyEvent简略执行流程

public boolean dispatchKeyEvent(KeyEvent event) {

if (mInputEventConsistencyVerifier != null) {

vent(event, 0);

}

// Give any attached key listener a first crack at the event.

//noinspection SimplifiableIfStatement

ListenerInfo li = mListenerInfo;

if (li != null && Listener != null && (mViewFlags &

ENABLED_MASK) == ENABLED

&& (this, Code(), event)) {

return true;

}

if (ch(this, mAttachInfo != null

? spatchState : null, this)) {

return true;

}

if (mInputEventConsistencyVerifier != null) {

ndledEvent(event, 0);

}

return false;

}

要修改ViewGroup焦点事件的分发:

重写view的dispatchKeyEvent方法

给某个子view设置onKeyListener监听

焦点没有被dispatchKeyEvent拦截的情况下的继续代码中的处理过程,还是进入

ViewRootImpl源码

// Handle automatic focus changes.

if (ion() == _DOWN) {

int direction = 0;

switch (Code()) {

case E_DPAD_LEFT:

if (odifiers()) {

direction = _LEFT;

}

break;

case E_DPAD_RIGHT:

if (odifiers()) {

direction = _RIGHT;

}

break;

case E_DPAD_UP:

if (odifiers()) {

direction = _UP;

}

break;

case E_DPAD_DOWN:

if (oModifiers()) {

direction = _DOWN;

}

break;

case E_TAB:

if (odifiers()) {

direction = _FORWARD;

} else if (ifiers(_SHIFT_ON)) {

direction = _BACKWARD;

}

break;

}

if (direction != 0) {

View focused = cus();

if (focused != null) {

View v = earch(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

usedRect(mTempRect);

if (mView instanceof ViewGroup) {

((ViewGroup)

mView).offsetDescendantRectToMyCoords(

focused, mTempRect);

((ViewGroup)

mView).offsetRectIntoDescendantCoords(

v, mTempRect);

}

if (tFocus(direction, mTempRect)) {

playSoundEffect(SoundEffectConstants

.getContantForFocusDirection(direction));

return FINISH_HANDLED;

}

}

// Give the focused view a last chance to handle the dpad key.

if (chUnhandledMove(focused, direction)) {

return FINISH_HANDLED;

}

} else {

// find the best view to give focus to in this non-touch-mode

with no-focus

View v = focusSearch(null, direction);

if (v != null && tFocus(direction)) {

return FINISH_HANDLED;

}

}

}

}

dispatchKeyEvent方法返回false后,先得到按键的方向direction一个int值。direction

值是后面来进行焦点查找的。

接着会调用DecorView的findFocus()方法一层一层往下查找已经获取焦点的子View。

DecorView则是PhoneWindow类的一个内部类,继承于FrameLayout,由此可知它是一

个ViewGroup。

那么,DecroView到底充当了什么样的角色呢?

其实,DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表

了整个应用的界面。在该布局下面,有标题view和内容view这两个子元素。

@Override

public View findFocus() {

if (DBG) {

n("Find focus in " + this + ": flags="

+ isFocused() + ", child=" + mFocused);

}

if (isFocused()) {

return this;

}

if (mFocused != null) {

return cus();

}

return null;

}

View的findFocus方法

/**

* Find the view in the hierarchy rooted at this view that currently has

* focus.

*

* @return The view that currently has focus, or null if no focused view can

* be found.

*/

public View findFocus() {

return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;

}

View的hasFocus()方法和isFocused()方法对比

Stackoverflow解释来了:

hasFocus() is different from isFocused(). hasFocus() == true means that the View or one of

its descendants is focused. If you look closely, there’s a chain of hasFocused Views till you reach

the View that isFocused.

/**

* Returns true if this view has focus itself, or is the ancestor of the

* view that has focus.

*

* @return True if this view has or contains focus, false otherwise.

*/

@edProperty(category = "focus")

public boolean hasFocus() {

return (mPrivateFlags & PFLAG_FOCUSED) != 0;

}

/**

* Returns true if this view has focus

*

* @return True if this view has focus, false otherwise.

*/

@edProperty(category = "focus")

public isFocused() {

return (mPrivateFlags & PFLAG_FOCUSED) != 0;

}

接着,如果cus()方法返回的mFocused不为空,说明找到了当前获取焦点

的view(mFocused),接着focusSearch会把direction(遥控器按键按下的方向)作为参数,

找到特定方向下一个将要获取焦点的view,最后如果该view不为空,那么就让该view获

取焦点。

我们来看一下focusSearch方法的源码以及具体实现。

@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 tance().findNextFocus(this, focused, direction);

} else if (mParent != null) {

return earch(focused, direction);

}

return null;

}

focusSearch其实是一层一层地网上调用父View的focusSearch方法,直到当前view是

根布局(isRootNamespace()方法),通过注释可以知道focusSearch最终会调用DecorView的

focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来

找到的。

FocusFinder是什么?

根据给定的按键方向,通过当前的获取焦点的View,查找下一个获取焦点的view这样

算法的类。焦点没有被拦截的情况下,Android焦点的查找最终都是通过FocusFinder类来

实现的。

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;

if (focused != null) {

next = findNextUserSpecifiedFocus(root, focused, direction);

}

if (next != null) {

return next;

}

ArrayList focusables = mTempList;

try {

();

usables(focusables, direction);

if (!y()) {

next = findNextFocus(root, focused, focusedRect, direction, focusables);

}

} finally {

();

}

return next;

}

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,

int direction, ArrayList focusables) {

if (focused != null) {

if (focusedRect == null) {

focusedRect = mFocusedRect;

}

// fill in interesting rect from focused

usedRect(focusedRect);

DescendantRectToMyCoords(focused, focusedRect);

} else {

if (focusedRect == null) {

focusedRect = mFocusedRect;

// make up a rect at top left or bottom right of root

switch (direction) {

case _RIGHT:

case _DOWN:

setFocusTopLeft(root, focusedRect);

break;

case _FORWARD:

if (utRtl()) {

setFocusBottomRight(root, focusedRect);

} else {

setFocusTopLeft(root, focusedRect);

}

break;

case _LEFT:

case _UP:

setFocusBottomRight(root, focusedRect);

break;

case _BACKWARD:

if (utRtl()) {

setFocusTopLeft(root, focusedRect);

} else {

setFocusBottomRight(root, focusedRect);

break;

}

}

}

}

private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction)

{

// check for user specified next focus

View userSetNextFocus = erSetNextFocus(root, direction);

if (userSetNextFocus != null && sable()

&& (!uchMode()

|| sableInTouchMode())) {

return userSetNextFocus;

}

return null;

}

FocusFinder类通过findNextFocus来找焦点的。一层一层往寻找,后面会执行

findNextUserSpecifiedFocus()方法,这个方法会执行focused(即当前获取焦点的View)的

findUserSetNextFocus方法,如果该方法返回的View不为空,

且isFocusable = true && isInTouchMode() = true的话。

FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。

findNextFocus会优先根据XML里设置的下一个将获取焦点的View的ID值来寻找将

要获取焦点的View。

View findUserSetNextFocus(View root, @FocusDirection int direction) {

switch (n) {

case FOCUS_LEFT:

if (mNextFocusLeftId == _ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusLeftId);

case FOCUS_RIGHT:

if (mNextFocusRightId == _ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusRightId);

case FOCUS_UP:

if (mNextFocusUpId == _ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusUpId);

case FOCUS_DOWN:

if (mNextFocusDownId == _ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusDownId);

case FOCUS_FORWARD:

if (mNextFocusForwardId == _ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusForwardId);

case FOCUS_BACKWARD: {

if (mID == _ID) return null;

final int id = mID;

return ewByPredicateInsideOut(this, new Predicate()

{

@Override

public boolean apply(View t) {

return ocusForwardId == id;

}

});

}

}

return null;

}

焦点事件分发步骤:

DecorView会调用dispatchKey一层一层进行焦点的分发,如果dispatchKeyEvent方法

返回true的话,那么焦点或者按键事件就不会往下分发了。

如果你想拦截某个子View,对其设置OnKeyListener进行焦点的拦截。

如果焦点没有被拦截的话,那么焦点就会交给系统来处理,还是会继续分发,直到找到

那个获取焦点的View

Android底层先会记录按键的方向,后面DecorView会一层一层往下调用findFocus方

法找到当前获取焦点的View

后面系统又会根据按键的方向,执行focusSearch方法来寻找下一个将要获取焦点的

View

focusSearch内部其实是通过FocusFinder来查找焦点的。FocusFinder会优先通过View

在XML布局设置的下一个焦点的ID来查找焦点。

最终如果找到将要获取焦点的View,就让其requestFocus。如果请求无效,将其放在

onWindowFocusChanged()这个方法中去请求。这是在Activity寻找到焦点的时候。