1. 首先简单的焦点控制在对应的布局控件里设置如下属性:

android:nextFocusUp="@id/下一个控件的id"

android:nextFocusDown=""

android:nextFocusLeft=""

android:nextFocusRight=""

分别对应该控件按下↑、↓、←、→键对应的下一个控件。

2.焦点控制逻辑:

翻看各大博客,对与AndroidTV焦点控制的理解都大同小异,接下来是我对与焦点控制的理解:

2.1Event事件机制:

在哪些对象中进行的:

Activity -> Window -> ViewGroup -> View

包含拦截、分发、响应:

拦截发生在: onInterceptTouchEvent()方法中,当用户触发event事件后,由上层传入,当此方法返回true时,则被拦截不会继续往子view传递,由当前view的 onTouchEvent()来响该事件。

返回false时,不会被拦截,事件将继续传递,由子view调用当前view的 dispatchTouchEvent()  去分发, 最后由具体的控件去消费此事件。

分发:

dispatchEvent(MotionEvent event)负责事件的调度,很多人称之为分发和传递也一个意思,主要负责将事件交由哪个控件去处理,如果自己不想处理,则可以继续往下传递,想处理则触发本身view的ontuchEvent(),

此方法也返回boolean类型,返回ture代表传递,返回false代表不传递,和我们的事件拦截恰恰相反,对于初学者来说很容易搞糊涂,本事件Activty,ViewGroup,View都拥有处理权,主要将事件负责转发,无论交由别人处理还是自己,其实都在充当调度角色,是事件的核心。

响应(消费):

安卓中事件具体处理由 onTouchEvent()  来执行,此阶段主要负责事件的消费响应,通过处理完事件后,然后逐步向上级汇报,如果消费了上级则不会再进行做响应消费处理,只会继续返回给根布局。

此方法返回布尔类型,如果消费了此事件,则会调用上级的此方法,默认返回false做处理,如果返回true,则代表不消费此时间 ,让上级调用本方法去做处理,逐步网上汇报,直到Activity得到消息为止。

过程:

97e22fc0987774bedc1145bcfc12f220.png

如图A:代表activity,B:代表ViewGroup(如:布局),C:代表View(如:button)

点击屏幕上的C时整个事件将会由A—B --C —B—A这样的顺序进行分发。

具体情况如下:

当点击C (Button)时,首先有A进行分发,然后传递到B,如果B不拦截,则继续分发,传递到C ,此时C无法继续传递 ,则执行事件,消费后继续向上反馈,

上级则不会进行消费处理,如果不消费,

则由上级B(Layout)进行处理,如果不处理,则继续交由A(Activity)处理,此时此事件结束。

2.2按键事件:

KeyEvent:位于android.view下,KeyEvent主要有以下事件类型:

KeyEvent.KEYCODE_DPAD_UP; 上

KeyEvent.KEYCODE_DPAD_DOWN; 下

KeyEvent.KEYCODE_DPAD_LEFT;左

KeyEvent.KEYCODE_DPAD_RIGHT;右

KeyEvent.KEYCODE_DPAD_CENTER;确定键

KeyEvent.KEYCODE_DPAD_RIGHT; 右

KeyEvent.KEYCODE_XXX:数字键 (xx表示你按了数字几)

KeyEvent.KEYCODE_BACK; 返回键

KeyEvent.KEYCODE_HOME;房子键

KeyEvent.KEYCODE_A: A-Z,26个字母

KeyEvent.KEYCODE_MENU菜单键。

首先看事件分发图:

7f2fac4804c639a06608946a02eeeb2d.png

如上图:

首先,KeyEvent会流转到ViewRootImpl中开始进行处理,具体方法是内部类ViewPostImeInputStage中的processKeyEvent。

代码如下:

private int processKeyEvent(QueuedInputEvent q) {

final KeyEvent event = (KeyEvent)q.mEvent;

...

// Deliver the key to the view hierarchy.

// 1. 先去执行mView的dispatchKeyEvent

if (mView.dispatchKeyEvent(event)) {

return FINISH_HANDLED;

}

...

// Handle automatic focus changes.

if (event.getAction() == KeyEvent.ACTION_DOWN) {

int direction = 0;

...

if (direction != 0) {

View focused = mView.findFocus();

if (focused != null) {

// 2. 之后会通过focusSearch去找下一个焦点视图

View v = focused.focusSearch(direction);

if (v != null && v != focused) {

...

if (v.requestFocus(direction, mTempRect)) {

...

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

// 3. 如果当前本来就没有焦点视图,也会通过focusSearch找一个视图

View v = focusSearch(null, direction);

if (v != null && v.requestFocus(direction)) {

return FINISH_HANDLED;

}

}

}

}

return FORWARD;

}

看上面的代码可以了解:

先执行mView的dispatchKeyEvent()方法,再通过focusSearch()去找下一个焦点视图,如果当前没由焦点视图也会执行focusSearch()找一个视图。

2.2.1 dispatchKeyEvent()执行流程

DecorView →Activity→ViewGroup→view。

DecorView 的 dispatchKeyEvent ():

public booleandispatchKeyEvent(KeyEvent event) {

... ...if (!mWindow.isDestroyed()) {//Activity实现了Window.Callback接口,具体可以参考 Activity.java 源码.

final Window.Callback cb =mWindow.getCallback();//mFeatureId < 0,表示为 application 的 DecorView.//cb.dispatchKeyEven 调用的是 Activity 的 dispatchKeyEven.

final boolean handled = cb != null && mFeatureId < 0 ?cb.dispatchKeyEvent(event)

:super.dispatchKeyEvent(event);//是否消耗掉事件.

if(handled) {return true;

}

}return isDown ?mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)

: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);

}

这里将会调用Activty的dispatchKeyEvent();

Activity 的 dispatchKeyEvent ():

//补充知识点://这就是为何在 Activity 直接 return true,事件被消耗,就不执行焦点搜索等等操作了.//所以这里也是可以做 焦点控制的,最好是在 event.getAction() == KeyEvent.ACTION_DOWN 进行.//因为android 的 ViewRootlmpl 的 processKeyEvent 焦点搜索与请求的地方 进行了判断//if (event.getAction() == KeyEvent.ACTION_DOWN)

public booleandispatchKeyEvent(KeyEvent event) {

... ...

Window win=getWindow();//调用 PhoneWindow 的 superDispatchKeyEvent//里面又调用 mDecor.superDispatchKeyEvent(event)//mDecor 为 DecorView.

if(win.superDispatchKeyEvent(event)) {return true;

}

View decor=mDecor;if (decor == null) decor =win.getDecorView();//onKeyDown,onKeyUp,onKeyLongPress 等等回调的处理.//只有 onKeyDown return true 可以进行焦点控制,//因为android 的 ViewRootlmpl 的 processKeyEvent 焦点搜索与请求的地方 进行了判断//if (event.getAction() == KeyEvent.ACTION_DOWN)

return event.dispatch(this, decor != null

? decor.getKeyDispatcherState() : null, this);

}

ViewGroup的dispatchKeyEvent():

@Overridepublic booleandispatchKeyEvent(KeyEvent event) {

...if ((mPrivateFlags & (PFLAG_FOCUSED |PFLAG_HAS_BOUNDS))== (PFLAG_FOCUSED |PFLAG_HAS_BOUNDS)) {//1.1 以View的身份处理KeyEvent

if (super.dispatchKeyEvent(event)) {return true;

}

}else if (mFocused != null && (mFocused.mPrivateFlags &PFLAG_HAS_BOUNDS)==PFLAG_HAS_BOUNDS) {//1.2 以ViewGroup的身份把KeyEvent交给mFocused处理

if(mFocused.dispatchKeyEvent(event)) {return true;

}

}

...return false;

}

通过flag的判断,有两个处理路径,也可以看到在处理keyEvent时,ViewGroup扮演两个角色:

View的角色,也就是此时keyEvent需要在自己与其他View之间流转。:调用自身的dispathKeyEvent()。

ViewGroup的角色,此时keyEvent需要在自己的子View之间流转 。:调用当前焦点子View的dispatchKeyEvent()。

再来看看view的dispatchKeyEvent():

public booleandispatchKeyEvent(KeyEvent event) {

...

ListenerInfo li=mListenerInfo;//1.3 如果设置了mOnKeyListener,则优先走onKey方法

if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) ==ENABLED&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {return true;

}//1.4 把View自己当作参数传入,调用KeyEvent的dispatch方法

if (event.dispatch(this, mAttachInfo != null

? mAttachInfo.mKeyDispatchState : null, this)) {return true;

}

...return false;

}

View这里,会优先处理OnKeyListener的onKey回调。然后才可能会走KeyEvent的dispatch,最终走到View的OnKeyDown或者OnKeyUp。

大体的流转顺序总结如下图:

cb544562d4430098ba3b332590c00fce.png

其中任何一步都可以通过return true的方式来消费掉这个KeyEvent,结束这个分发过程。

按键事件分发结束,接下来让我们看看如和查找焦点。

3.焦点查找方法。

如果dispatchKeyEvent没有消耗掉KeyEvent,会由系统来处理焦点移动。

通过view的focusSearch方法找到下一个获取焦点的View,然后调用requestFocus设置焦点。

3.1:focusSearch()

//View.java

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

}else{return null;

}

}

由上面的代码可以看出,View不会直接去查找,而是会交给其parent的focusSearch方法去查找,也就是ViewGroup的focusSearch()方法去查找。

ViewGroup的focusSearch()方法:

//ViewGroup.java

public View focusSearch(View focused, intdirection) {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) {returnmParent.focusSearch(focused, direction);

}return null;

}

这里会判断是否为根布局,也就是顶层布局,如果是则最后交给FocusFinder去查找。

如果不是则会接调用上层parent的focusSearch()。

isRootNamespace的()

/*** {@hide}

*

*@paramisRoot true if the view belongs to the root namespace, false

* otherwise*/

public void setIsRootNamespace(booleanisRoot) {if(isRoot) {

mPrivateFlags|=PFLAG_IS_ROOT_NAMESPACE;

}else{

mPrivateFlags&= ~PFLAG_IS_ROOT_NAMESPACE;

}

}

3.2:findNextFocus():

位于顶层的ViewGroup把自己和当前焦点(View)以及方向传入。

findNextFocus()代码:

//FocusFinder.java

public final View findNextFocus(ViewGroup root, View focused, intdirection) {return findNextFocus(root, focused, null, direction);

}private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, intdirection) {

View next= null;if (focused != null) {//2.1 优先从xml或者代码中指定focusid的View中找

next =findNextUserSpecifiedFocus(root, focused, direction);

}if (next != null) {returnnext;

}

ArrayList focusables =mTempList;try{

focusables.clear();

root.addFocusables(focusables, direction);if (!focusables.isEmpty()) {//2.2 其次,根据算法去找,原理就是找在方向上最近的View

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

}

}finally{

focusables.clear();

}returnnext;

}

这里root是上面isRootNamespace()为true的ViewGroup,focused是当前焦点视图

优先找开发者指定的下一个focus的视图 ,就是在xml或者代码中指定NextFocusDirection Id的视图。

其次,根据算法去找,原理就是找在方向上最近的视图。

4.按键焦点查找流程

4.1界面第一次进入的时候,是如何获取到焦点的

先看下DecoreView的流程图:

c466e7e9598593f09b346f2e137a4245.png

上图ViewRootImpl类中 performTraversals方法:

.. ...if(mFirst) {if (mView != null) {if (!mView.hasFocus()) {//调用 View 的 requestFocus(int direction)

mView.requestFocus(View.FOCUS_FORWARD);

}

... ...

}

... ...

整体的过程:

05a95922bacd2b34a976e49f285fba2a.png

ViewRootlmpl.performTraversals→DecoreView.requestFocus→ActionBarOverlayLayout.requestFocus→FrameLayout(android:id/content).requestFocus→FrameLayout(activity_test.xml).requestFocus→Button1(activity_test.xml).requestFocus

代码步骤:

View.javapublic final boolean requestFocus(intdirection) {//因为 DecoreView 继承 ViewGroup//ViewGroup 重写了此函数,//会调用 ViewGroup 的 requestFocus(int direction, Rect previouslyFocusedRect)

return requestFocus(direction, null);

}

ViewGroup.javapublic boolean requestFocus(intdirection, Rect previouslyFocusedRect) {//关注内容://处理 DescendantFocusabilit//1)FOCUS_AFTER_DESCENDANTS 先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理//2)FOCUS_BEFORE_DESCENDANTS ViewGroup先对焦点进行处理,如果没有处理则分发给child View进行处理//3)FOCUS_BLOCK_DESCENDANTS ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理//setDescendantFocusability 可以设置.

int descendantFocusability =getDescendantFocusability();switch(descendantFocusability) {caseFOCUS_BLOCK_DESCENDANTS:return super.requestFocus(direction, previouslyFocusedRect);caseFOCUS_BEFORE_DESCENDANTS: {//其它的 ActionBarOverlayLayout,Content等继承ViewGroup//默认进入 FOCUS_BEFORE_DESCENDANTS,因为 ViewGroup 初始化的时候设置了//setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);//mViewFlags 判断 FOCUSABLE_MASK,FOCUSABLE_IN_TOUCH_MODE.//Button 以上的父布局,不满足以上条件判断,全部都是 直接 return false.

final boolean took = super.requestFocus(

direction, previouslyFocusedRect);//took=false, 调用 onRequestFocusInDescendants 遍历子控件进行请求

return took ?took : onRequestFocusInDescendants(direction, previouslyFocusedRect);

}caseFOCUS_AFTER_DESCENDANTS: {//DecoreView 进入这里,因为 PhoneWindow 给 DecoreView 初始化 设置//setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);//setIsRootNamespace(true);//像 RecyclerView, Leanback 也会进入这里.//遍历子控件进行请求

final boolean took =onRequestFocusInDescendants(

direction, previouslyFocusedRect);//took=true,子控件有焦点,不调用 super.request...,反之.

return took ? took : super.requestFocus(

direction, previouslyFocusedRect);

}

... ...

}

}

View.javapublic boolean requestFocus(intdirection, Rect previouslyFocusedRect) {returnrequestFocusNoSearch(direction, previouslyFocusedRect);

}

ViewGroup.java//补充知识点: onRequestFocusInDescendants 是可以做焦点记忆控制的.

protected boolean onRequestFocusInDescendants(intdirection,

Rect previouslyFocusedRect) {

.. ...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;

}

Button1获取焦点:

063555d849166b69cd6770513e9edf0a.png

关键代码是 View.java 的函数 handleFocusGainInternal : mPrivateFlags |= PFLAG_FOCUSED 和 mParent.requestChildFocus(this, this)

View.javaprivate boolean requestFocusNoSearch(intdirection, Rect previouslyFocusedRect) {//need to be focusable//Button 默认 android:focusable="true"//button1 以上的父布局都没有设置此类属性,进入这里,直接就 return false.

if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||(mViewFlags& VISIBILITY_MASK) !=VISIBLE) {return false;

}//need to be focusable in touch mode if in touch mode//当 button1 没有设置 android:focusableInTouchMode="true" 的时候,//直接 return false,那么界面上是没有任何控件获取到焦点的.//鼠标|触摸支持的属性.

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;

}//关键函数

handleFocusGainInternal(direction, previouslyFocusedRect);return true;

}void handleFocusGainInternal(@FocusRealDirection intdirection,

Rect previouslyFocusedRect) {if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {//关键代码,设置 有焦点的标志位.//这个时候 button1 已经标志上焦点

mPrivateFlags |=PFLAG_FOCUSED;//获取父布局的老焦点.

View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;//调用此函数,告诉上一层父布局,让它做一些事情.

if (mParent != null) {

mParent.requestChildFocus(this, this);

}//此函数是全局焦点监听的回调.//调用方式: View.getViewTreeObserver().addOnGlobalFocusChangeListener

if (mAttachInfo != null) {

mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus,this);

}//回调处理.

onFocusChanged(true, direction, previouslyFocusedRect);//刷新按键的 selector drawable state状态

refreshDrawableState();

}

}

ViewGroup.javapublic voidrequestChildFocus(View child, View focused) {if (getDescendantFocusability() ==FOCUS_BLOCK_DESCENDANTS) {return;

}//Unfocus us, if necessary

super.unFocus(focused);//We had a previous notion of who had focus. Clear it.

if (mFocused !=child) {if (mFocused != null) {

mFocused.unFocus(focused);

}//保存上一级的焦点view.

mFocused =child;

}//一层层调用回去父布局,相当于//FrameLayout(activity_test.xml) 的 mFocused 是 Button1.//FrameLayout(android:id/content) 的 mFocused 是 FrameLayout(activity_test.xml)//ActionBarOverlayLayout 的 mFocused 是 FrameLayout(android:id/content)//最后 DecoreView 的 mFocused 是 ActionBarOverlayLayout//在最后的后面,ViewRootImpl 会调用//requestChildFocus,又会再次调用//performTraversals刷新界面.(再执行 layout, draw)//形成了一个关联, dispatchKeyEvent 的 mFocused 也在使用.

if (mParent != null) {

mParent.requestChildFocus(this, focused);

}

}//ViewRootImpl.java

@Overridepublic voidrequestChildFocus(View child, View focused) {

checkThread();

scheduleTraversals();

}

初步获取焦点已经了解,接下来看看焦点是如何从 view2 →view2的。

4.2按键焦点的搜索过程

a0b49ce422a283de053f157e91f34d79.png

focusView(2) 按下右键后:由上面的3.焦点查找方法可以得出下图:

6609760ef0bfb9ba7d72d9359fae4cc2.png

在没有消耗 dispatchKeyEvent的情况下:

FocusSearch 一层层上去,调用 FocusFinder.getInstance().findNextFocus… … 后,在 …addFocusables 下,将所有带焦点属性的 view 全部加到数组里面去,然后通用方向,位置等查找相近的view. 最后找到的是  focusView(3).

private intprocessKeyEvent(QueuedInputEvent q) {

... ...//以上代码不消耗事件.//判断 action 为 ACTION_DOWN 才处理焦点搜索以及请求.

if (event.getAction() ==KeyEvent.ACTION_DOWN) {//根据按键判断,设置 direction 属性.

if (direction != 0) {//一层层查找(根据mFocused),最后获取到 button1.

View focused =mView.findFocus();if (focused != null) {//button1_view 调用 focusSearch(), 右键,direction=66

View v =focused.focusSearch(direction);//最终返回 v = button2

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 instanceofViewGroup) {

((ViewGroup) mView).offsetDescendantRectToMyCoords(

focused, mTempRect);

((ViewGroup) mView).offsetRectIntoDescendantCoords(

v, mTempRect);

}//button2 View 调用 requestFocus//这里的过程 和 第一次获取焦点button1请求是一样的.

if(v.requestFocus(direction, mTempRect)) {//播放音效

playSoundEffect(SoundEffectConstants

.getContantForFocusDirection(direction));returnFINISH_HANDLED;

}

}//进行最后的垂死挣扎,//这里其实可以处理一些焦点问题或者滚动翻页问题.//滚动翻页的demo可以参考 原生 Launcher 的 Workspace.java//Give the focused view a last chance to handle the dpad key.

if(mView.dispatchUnhandledMove(focused, direction)) {returnFINISH_HANDLED;

}

}else{//这里处理第一次无焦点 view 的情况.//基本上和有焦点view 的情况差不多.

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

}

}

}

}

... ...

}

button1下一个焦点搜索流程图:

080131b924a155fd8e95c52acc7a5ee8.png

View v = focused.focusSearch(direction); # focused=>button1 direction=>66

Button1_View→focusSearch(int direction)→FrameLayout(activity_test.xml)_ViewGroup→focusSearch(View focused, int direction)→。。。→FrameLayout(activity_test.xml)_ViewGroup→

focusSearch(View focused, int direction)→DecoreView_ViewGroup→FocusFinder.getInstance().findNextFocus(this, focused, direction)→FocusFinder.findNextFocus()→ViewGroup.addFocusables()->。。。

代码流程:

View.javapublic View focusSearch(@FocusRealDirection intdirection) {if (mParent != null) {//button1 的父布局ViewGroup调用 focusSearch

return mParent.focusSearch(this, direction);

}else{return null;

}

}

ViewGroup.java//像 RecyclerView 会重写 focusSearch 进行焦点搜索.//也是调用的 FocusFinder.getInstance().findNextFocus//leanback 的 GridLayoutmanger 也重写了 onAddFocusables.

public View focusSearch(View focused, intdirection) {//只有 DecoreView 设置了 setIsRootNamespace//最终由 DecoreView 进入这里.

if(isRootNamespace()) {//传入参数(this: DecoreView focused: button1 direction: 66)

return FocusFinder.getInstance().findNextFocus(this, focused, direction);

}else if (mParent != null) {returnmParent.focusSearch(focused, direction);

}return null;

}

FocusFinder.java

findNextFocus(ViewGroup root, View focused,int direction)->findNextFocus(root, focused, null, direction)->

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, intdirection) {

View next= null;if (focused != null) {//关于XML布局中的 android:nextFocusRight 等等的查找.

next =findNextUserSpecifiedFocus(root, focused, direction);

}if (next != null) {returnnext;

}

ArrayList focusables =mTempList;try{

focusables.clear();//要进行 findNextFocus,关键在于 addFocusables,一层层调用下去.//DecorView_View.addFocusables//DecorView_ViewGroup.addFocusables//ActionBarOverlayLayout_ViewGroup.addFocusables//FrameLayout(android:id/content)_ViewGroup.addFocusables//FrameLayout(activity_test.xml)_ViewGroup.addFocusables//到最后 button1, button2 添加到 views 数组中,也就是 focusables .

root.addFocusables(focusables, direction);if (!focusables.isEmpty()) {//关键函数 findNextFocus,想深入了解是如何查找到下一个焦点的,//可以去看看源码,这里不进行过多篇幅的讲解.//focusables 数组有 button1, button2//内部调用 findNextFocusInAbsoluteDirection,这里进行了一些判断,查找某个方向比较近的view.

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

}

}finally{

focusables.clear();

}returnnext;

}

ViewGroup.javapublic void addFocusables(ArrayList views, int direction, intfocusableMode) {final int focusableCount =views.size();final int descendantFocusability =getDescendantFocusability();

... ...for (int i = 0; i < count; i++) {final View child =children[i];//循环 child view 调用 addFocusables,一层层调用下去,将满足条件的添加进 views 数组.

if ((child.mViewFlags & VISIBILITY_MASK) ==VISIBLE) {

child.addFocusables(views, direction, focusableMode);

}

}

}if... ...//调用 view 的 addFocusables,父布局是不满足条件的,直接返回了.

super.addFocusables(views, direction, focusableMode);

}

}

View.javapublic void addFocusables(ArrayList views, int direction, intfocusableMode) {if (views == null) {return;

}if (!isFocusable()) {return;

}if ((focusableMode & FOCUSABLES_TOUCH_MODE) ==FOCUSABLES_TOUCH_MODE&& isInTouchMode() && !isFocusableInTouchMode()) {return;

}//button1 以上条件满足,加入views数组.//button2 以上条件也满足,加入views数组.//同理,焦点记忆的原理就很简单了,后续会讲解.

views.add(this);

}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐