这次打算来梳理一下 Android Tv 中的按键点击事件 KeyEvent 的分发处理流程。一谈到点击事件机制,网上资料已经非常齐全了,像什么分发、拦截、处理三大流程啊;或者 dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 啊;再或者返回 true 表示消费,返回 false 不处理啊;还有说整个流程是个 U 型分发处理,什么总经理发布任务到员工处理反馈啊之类的。前辈们早已为我们梳理了一篇篇干货,也在尽可能的写得通俗、易懂。
但是今天这篇的主题是:KeyEvent 的分发处理流程 说得明白点就是:Tv 上的遥控器按键的点击事件分发处理流程,也许你还没反应过来。想想,手机上都是触屏点击事件,而遥控器则是按键点击事件,两种事件类型的分发处理机制自然有所不同,所以,如果不搞清楚这点,很容易在 Tv 应用开发中将这两类事件分发机制混淆起来。
最简单的区别就是,在 Tv 开发中已经不是再像触屏手机那样通过 dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 来分发处理了,取而代之的则是需要使用 dispatchKeyEvent、onKeyDown/Up、onKeyLisenter 等来分发处理。
dispatchKeyEvent事件分发传递流程图.jpg
这次梳理的就只是 KeyEvent 在一个 View 树内部的分发处理流程,简单点说,也就是,你在某个 Activity 界面点击了遥控器的某个按键,然后这个按键事件在当前这个 Activity 里是如何分发处理的。
流程图涉及的主要方法和类:
硬件层、框架层那些按键事件的获取、分发、处理太深奥了,啃不透。应用层的一部分事件分发流程也还暂时没啃透,这次梳理的是在一个 View 树内部的分发处理流程。
ps:当我们在某个 Activity 界面中点击了某个遥控器按键时,会有 Action_Down 和 Action_Up 两个 KeyEvent 进行分发处理,分发流程都一样,区别就是最后交给 Activity 或 View 的 onKeyDown 或 onKeyUp 处理。
DecorView_dispatchKeyEvent.png
activity_dispatchKeyEvent.png
PhoneWindow_superDispatchKeyEvent.png
DecorView_superDispatchKeyEvent.png
ps:KeyEvent 事件的处理只有两个地方,一个是 Activity,另一个则是具体的 View。ViewGroup 只负责分发,不会消耗事件。同 TouchEvent 一样,返回 true 表示事件已消耗掉,返回 false 则表示事件还在。
view_dispatchKeyEvent.png
keyEvent_dispatch.png
View_onKeyUp.png
dispatchKeyEvent事件分发传递流程图_LI.jpg
整体的分发处理流程就如上图(手抖了,不然是直线的)所示,有些较重要的点我们可以来总结下:
为什么我说 Activity 不能拦截事件交由自己处理呢? 在触屏的 TouchEvent 点击事件机制中,我们可以通过重写 onInterceptTouchEvent() 返回 true 来停止拦截事件的分发并自己处理事件,但在 KeyEvent 中并没有这个方法,所以如果 dispatchKeyEvent() 只干事件分发的事,事件处理都在 onKeyDwon/Up、onKey()、onClick() 中完成,这样的话,Activity 确实没办法拦截事件分发交由自己的 onKeyDown/Up() 来处理。
但谁规定 dispatchKeyEvent() 只能干事件传递的事呢,所以理论上按标准来说,Activity 无法拦截事件分发自己处理,但实际编程中,我经常碰见有人在 Activity 里重写 dispatchKeyEvent() 来处理事件,然后让其返回 true 或 false,停止事件的分发。
KeyEvent 事件的分发处理流程大体上知道是怎么走的就行了,有兴趣的可以再去看看源码,然后自己画画流程图,就会更明白了。先把分发处理流程梳理清楚了,我们才知道该怎么用,怎么去重写分发处理的方法,下面就讲些使用场景:
1. 在 Activity 里重写 dispatchKeyEvent()----最常用 举个栗子:
homeActivity_dispatchKeyEvent.png
这在 Tv 开发中是很常见的,经常会在 Activity 里重写 dispatchKeyEvent(),然后要么去预先处理一些工作,要么就是对特定的按键进行拦截。
上面这段代码能看懂么?如果你已经清楚这代码是对左右方向按键的拦截,那么你清楚各种 return 的作用么,为什么又有 return true,又有 return false,还有 return super.dispatchKeyEvent() 的?
先说结论:这里的 return true 和 return false 都能起到按键拦截的作用,也就是子 View 不会接收到事件的分发或处理,Activity 的 onKeyDown/Up() 也不会收到任何消息。
要明白这点,先得搞清楚什么是 return, return 是返回的意思,什么情况下需要返回,不就是调用你的那个方法需要你给个反馈,所以 return 的消息是给上一级的调用者的,所以 return 只会对上一级的调用者的行为有影响。调用 Activity.dispatchKeyEvent() 的是 DecorView 的 dispatchKeyEvent() 里,如下图:
DecorView_dispatchKeyEvent行为.png
那么,既然 Activity 返回 true 或 false 都只对 DecorView 的行为有影响,那么为什么都能起到拦截事件分发的作用呢?
这是因为,事件的分发逻辑其实是在 Activity.java 的 dispatchKeyEvent() 里实现的,如果你重写了 Activity 的 dispatchKeyEvent() 方法,那么根据 Java 的特性程序就会执行你写的 dispatchKeyEvent(),而不会执行基类 Activity.java 的方法,因此你在重写的方法里没有自己实现事件的分发逻辑,事件当然就停止分发了啊。这也是为什么返回 super.dispatchKeyEvent() 时事件会继续分发,因为这最终会调用到基类 Activity.java 的 dispatchKeyEvent() 方法来执行事件分发的逻辑。
既然在 Activity 里返回 true 或 false 都表示拦截,那么有什么区别么?
当然有,因为会影响 DecorView 的行为,比如我们点击遥控器的方向键时界面上的焦点会跟随着移动,这部分逻辑其实是在 DecorView 的上一级调用者中实现的,Activity 返回 true 的话,会导致 DecorView 也返回 true,那么上一级将根据 DecorView 返回 true 的结果停止焦点的移动,这就是我们常见的在 Activity 里重写 dispatchKeyEvent() 返回 true 来实现停止焦点移动的原理。那么,如果 Activity 返回的是 false,DecorView 也跟随着返回 false,那么上一级会继续执行焦点移动的逻辑,表现出来的效果就是,界面上的焦点仍然会移动,但不会触发 Activity 和 View 的事件分发和处理方法,因为已经被 Activity 拦截掉了。
最后,还有一个问题,在 View 或 ViewGroup 里面重写 dispatchKeyEvent() 作用会跟 Activity 一样么?
return true 或 false 或 super 的含义还是一样的,但这里要明白一个层次结构。上层:Activity,中层:ViewGroup,下层:View。
不管在哪一层重写 dispatchKeyEvent(),如果返回 true 或 false,那么它下层包括它本层都不会接收到事件的分发处理,但是它的上层会接收。因为拦截的效果只作用于该层及下层,而上层只会根据你返回的值,行为受到影响。
比如在 ViewGroup 中返回 true,Activity 的 onKeyDown/Up() 就不会被触发,因为被消费了;如果返回 false,那么事件就交由 Activity 处理。但不管返回 true 或 false,子 View 的 dispatchKeyEvent()、各种 onClick() 等事件处理方法都不会被触发到了。
2. 在 Activity 里重写 onKeyDown/Up()----最常用 事件能走到这里表示没有被子 View 消费掉,这里是我们能接触到的层次里面最后对事件进行处理的地方。而且就算我们在这里做了一些工作,也没有必要一定要返回 true。比如如果是方向键事件的话,你在这里返回 true 会影响到上级停止焦点的移动,所以视情况而定。
3. 为某个具体的 View (如 TextView) 设置 OnKeyListener()----一般常用 这个应该也挺常见的,在 Activity 里获取某个控件的对象,然后设置点击事件监听,然后去做一些事。
4. 为某个具体的 View (如 Button) 设置 OnClickListener()----一般常用 这个应该是更常见的了,setOnClickListener,很多场景都需要监听某个控件的点击事件,明确一点就是:该监听器监听的是 ok(确认)键的 Action_Up 事件。
小结一下: