Android TV屏 开发、RecyclerView焦点处理等

387 阅读2分钟

image.png TV屏使用遥控器控制,通过焦点操作界面,跟电视投屏类似

一共两个核心,焦点的处理,按键的监听处理

按键原生提供了onKeyDown 来监听,通过不同的 keyCode 区分不同的按键

image.png 一般如果没有遥控器,可以通过电脑键盘测试,使用投屏软件投屏后,对键盘按键效果跟遥控器类似

有时候没有实体按键(比如电脑没有返回键等),可以直接使用 adb 命令控制

adb shell input keyevent keyCode

至于长按事件,通过监听的 KeyEvent 参数中 repeatCount 判断,这里最好等于某个数字时触发,防止多次重复触发

image.png 主动获取焦点使用方法 requestFocus() ,但是可能会失败,所以需要注意等待UI刷新完后在调用

并且在需要获取焦点的view中设置属性,否则没法落焦

android:focusable="true"
android:focusableInTouchMode="true"

如果实在没法控制刷新后的时机,那就只能延迟(postDelayed)获取了,这个万能钥匙,但是尽量少用,影响效率,减低体验

对于焦点,上面的只是基本操作,实际开发中吭比较多,比较如果界面复杂,焦点是很不好控制的,加上列表各种刷新,跨界面恢复焦点等,懂得都懂

所以为了更好的定位问题,需要借助一些系统监听来获取焦点的状态,才能知道问题在哪

比如可以重写 requestChildFocus 方法,每次获取焦点时打印信息,看看是否被其它 view 抢占焦点

override fun requestChildFocus(child: View?, focused: View?) {
        child?.let {
            val position = getChildViewHolder(child).absoluteAdapterPosition
            Logger.d("requestChildFocus $position ")
        }
    }

或者使用全局监听 decorView.viewTreeObserver.addOnGlobalFocusChangeListener ,判断哪些 view 获取了焦点

针对获取焦点无效怎么处理?

估计很多时候会发现,调用了 requestFocus 方法没反应,这是因为没有对上个 view 的焦点进行 clear

你需要在监听中,把获取焦点的 view 赋值给你定义的变量 lastFocusView,然后每次调用 requestFocus 前先调用 lastFocusView.clearFocus()

当 tab 或者 列表 切换界面,这个时候操作遥控器子页面自动抢占了焦点,怎么处理?

可以通过对 onKeyDown 事件的拦截(return true),这样子页面是无法响应按键事件的,也就无法获取焦点

image.png 针对一些个别 view 可以设置屏蔽焦点的属性

android:focusable="false"
android:focusableInTouchMode="false"

焦点边框样式统一处理

可以通过全局监听 addOnGlobalFocusChangeListener ,对 view 进行统一绘制边框,或者一些逻辑控制等

image.png

window.decorView.viewTreeObserver
            .addOnGlobalFocusChangeListener { oldFocus, newFocus ->
                (newFocus ?: window.decorView.findFocus())?.let {
                    mainUpView.setFocusView(newFocus, oldFocus, 1.0f)
                }
            }

弹框无法获取焦点怎么处理?

一般 dialog 等只要设置了 focus 属性,然后在初始化调用 requestFocus,是没问题的,但是不排除一些个别情况导致的

比如 PopupWindow,因为 window 弹出后,activity 的 onKeyDown 会无法响应,所以需要单独监听,前提是这个window获取到焦点

如果碰到焦点不好处理,或者落焦后绘制边框等不方便,这里建议手动控制,因为落焦绘制是统一在全局监听里处理的,window 上需要额外监听,没有对这块逻辑封装好,就表示需要把整套逻辑搬到 window 上处理,这显然很冗余

所以何不直接监听 onKeyDown ,然后通过代码手动绘制边框焦点

contentView.apply {
            requestFocus()
            binding.flMove.setOnKeyListener { _, keyCode, _ ->
                Logger.d("setOnKeyListener keyCode $keyCode")
                when (keyCode) {
                    KeyCode.KEYCODE_DPAD_LEFT -> {
                        switchMenu(1)
                    }

                    KeyCode.KEYCODE_DPAD_RIGHT -> {
                        switchMenu(0)
                    }

                    KeyCode.KEYCODE_BACK -> {
                        dismiss()
                    }
                }
                false
            }
        }

image.png 如果不太喜欢这个方案,还有另外一个方案

可以在xml中添加布局,去仿造弹框,这样焦点就可以统一处理,不过需要对这个布局进行一些显示隐藏的操作

Android Launcher apk 授信安装 - 掘金 (juejin.cn)