焦点处理是从Android手机开发到TV开发必须要经历的第一个关卡,最近从手机开发转到Tv开发的小王就遇到了这个问。
小王经过一个月的努力,XXTV版项目1.0版本终于迭代完成,本以为可以轻松过一个周末,但是版本刚发出去,客户便提出了问题。为什么我们的列表焦点不能像爱奇艺一样在最左边按左键的时候焦点向上,在最右边按右键的时候焦点向下?为什么我们的导航不具备焦点记忆的功能呢?
为了解决客户的问题小王花了一个周末的时间来学学Android对 KeyEvent事件的处理,并且通过技术笔记的方式来检验学习效果。因为输出是最好的学习方式。
KeyEvent事件总结:
-
KeyEvent事件分为前后两个阶段,以当前焦点View接收到dispatchKeyEvent为分界点,前半部分为KeyEvent事件处理,后半部分为焦点查找。
-
KeyEvent事件默认会交给当前拥有焦点的View进行处理。ViewGroup只是起到一个事件传递的作用。
-
只有直接或间接包含当前焦点View的ViewGroup才有机会调用到dispatchKeyEvent和focusSearch方法
-
只有直接或间接包含当前焦点View的ViewGroup,设置的OnKeyListener才可能有效果。
-
我们可以重写ViewGroup的dispatchKeyEvent方法来实现自己的KeyEvent事件拦截处理。
-
对于跨ViewGroup的焦点查找我们可以在addFocusable中进行处理,对于ViewGroup的内部焦点查找,在不打破原有的事件处理归则的情况下优先考虑重写focusSearch,其次才是dispatchKeyEvent。
什么是焦点记忆?什么是焦点内部搜索?
我们假设当前焦点 在C上,按键向右,D获取焦点,对于ViewGroup N 而言这个是焦点的内部搜索
我们假设当前焦点 在C上,按键向上,A获取焦点,这个是跨容器进行焦点查找,如果M的焦点自A离开,且只要ViewGroup M 只要有子View获取焦点,它一定在A上我们称ViewGroup M 具有焦点记忆功能。
那么应该如何解决客户的问题呢?小王主要面对下面的两个问题
-
如何记录上一次的焦点元素 ?
-
在什么时候介入焦点搜索的过程进行自己处理?
View的获取焦点时发生了什么
为了了解详细的过程,小王从View的requestFocus方法开始,到requestChildFocus发现了View获取焦点比较重要的信息。从当前焦点View起到 根ViewGroup的mFocused都记录了 含有焦点的ViewGroup。链式的保存了焦点信息。
ViewGroup:
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
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 (mFo