Android TV开发KeyEvent事件处理流程&焦点寻找

目录

前言

KeyEvent事件的传递机制

mView究竟是什么?

DecorView的dispatchKeyEvent

 activity的dispatchKeyEvent

ViewGroup的dispatchKeyEvent

KeyEvent事件分发总结

View的焦点寻找

当前焦点元素的查找

下一个焦点元素的查找

View请求焦点

View的requestFocus方法



前言

文章主旨

注意:文章分析基于ANDROID8.0

在Android手机开发中我们通过长按、点击屏幕上的某个区域触发Android系统的事件。而TV端开发一般而言是通过遥控器进行触发。并且不同于手机开发,TV开发中有一个比较重要的概念焦点。通俗电解释就是能让使用者清楚当前操作的页面元素是哪一个。

如下图焦点在 高亮元素上面。图片来自leanback框架

android系统为我们提供了几个常见的焦点相关的属性和方法

android:focusable:设置一个控件能否获得焦点
android:nextFocusDown:(当按下键时)下一个获得焦点的控件
android:nextFocusDown:(当按下键时)下一个获得焦点的控件
android:nextFocusLeft:(当按下键时)下一个获得焦点的控件
android:nextFocusRight:(当按下键时)下一个获得焦点的控

View.requestFocus()元素请求焦点。

既然系统已经提供了焦点的处理相关属性为什么我们还要自己来分析Android的焦点处理?

  1. 系统虽然提供了一些属性来处理焦点,但是在复杂的Tv页面我们不可能为每一个View指定这些属性。
  2. 理解系统的焦点处理原理有助于实现我们自己的特殊业务。

KeyEvent事件的传递机制

我们知道在手机开发中有MotionEvent的事件传递(不清楚的可自行搜索,View的事件分发机制)。 同样的在遥控器的按键处理也有自己的事件: KeyEvent。

当系统接收到遥控器的按键经过系统层的封装处理会调用到ViewRootImpl内部类ViewPostImeInputStage的processKeyevent方法。它的相关代码如下。

private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            if (mUnhandledKeyManager.preViewDispatch(event)) {
                return FINISH_HANDLED;
            }

            // Deliver the key to the view hierarchy.
//关注点1
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

           //省略了系统的部分代码

            // Handle automatic focus changes.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (groupNavigationDirection != 0) {
                    if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                        return FINISH_HANDLED;
                    }
                } else {
//关注点2
                    if (performFocusNavigation(event)) {
                        return FINISH_HANDLED;
                    }
                }
            }
            return FORWARD;
        }

可以这样来简单理解上面的代码逻辑,首先将KeyEvent事件交给View进行事件分发,如果没有进行处理那么进行后续的焦点查找处理。这个里面的方法我们要关注的有下面两个 

  • mView.dispatchKeyEvent(event)  将KeyEvent进行View的分发
  • performFocusNavigation(event) 进行页面的焦点处理

 

在这一小节我们主要分析View.dispatchKeyEvent对KeyEvent的分发过程。

mView究竟是什么?

在ViewRootImpl中唯一对mView进行非空赋值就是在ViewRootImpl的setView方法中。而对Window和WindowManager比较熟悉的同学可能会知道。在通过WindowManager添加View的时候会在WindowManagerGlobal的addView中会调用ViewRootImpl的setView方法。这里没有对WindowManager添加View的过程做太多详细的描述,因为这个不是我们的文章重点。

通过阅读源码发现在Activity的makeVisiable中调用了WindowManager的addView方法

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

可以看到addView方法将mDecor对象添加了进去。那么mDecor对象是什么呢?

阅读源码发现mDecor对象的赋值在ActivityThread的handleResumeActivity方法中。因为源码比较复杂我们直接看看关键的赋值代码

@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        //省略代码
        final Activity a = r.activity;

       //省略代码
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            //省略代码
    }
可以得出结论Activity的mDecor是Window中的DecorView。那么ViewRootImpl中的mView也是Window中的DecorView。

DecorView的dispatchKeyEvent

@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        //省略代码

        if (!mWindow.isDestroyed()) {
            final Window.Callback cb = mWindow.getCallback();
            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);
    }

可以看到在省略部分代码的情况下DecorView的dispatchKeyEvent的逻辑分厂简单。如果能够获取到mWindow.getCallback()返回不为空这次KeyEvent时间交给它处理。在cb不处理的情况下我们分别调用Window的onKeyDown和onKeyUp。我们看看Window.getCallback()返回的对象是什么。

 public final Callback getCallback() {
        return mCallback;
    }

在Activity的attach方法中会调用Window的setCallback方法设置mCallback对象为当前的activity。因此KeyEvent事件调用到了activity中。代码如下

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值