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得到消息为止。
过程:
如图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菜单键。
首先看事件分发图:
如上图:
首先,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 boolean dispatchKeyEvent(KeyEvent event) { ... ... if (!mWindow.isDestroyed()) { // Activity实现了Window.Callback接口,具体可以参考 Activity.java 源码. final Window.Callback cb = mWindow.getCallback(); // mFeatureId < 0,表示为 application 的 DecorView. // cb.dispatchKeyEven 调用的是 Activity 的