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

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

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

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

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

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

adb shell input keyevent keyCode

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

主动获取焦点使用方法 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),这样子页面是无法响应按键事件的,也就无法获取焦点

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

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

焦点边框样式统一处理

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

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 上处理,这显然很冗余

所以何不直接监听 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
            }
        }

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

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

热门相关:豪门蜜爱:独宠天后小萌妻   异世修真邪君   异世修真邪君   一个火辣女人的屁股   宠物小精灵之庭树