醉南桥 發表於 2023-9-1 10:42:00

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

<p><img src="https://img2023.cnblogs.com/blog/583064/202311/583064-20231117111409350-368382266.png"></p>
<p>&nbsp;</p>
<p>TV屏使用遥控器控制,通过焦点操作界面,就跟电视投屏类似</p>
<p>一共两个核心,焦点的处理,按键的监听处理</p>
<p>按键原生提供了<strong>onKeyDown</strong> 来监听,通过不同的 keyCode 区分不同的按键</p>
<p><img src="https://img2023.cnblogs.com/blog/583064/202309/583064-20230901094639982-1475966525.png"></p>
<p>一般如果没有遥控器,可以通过电脑键盘测试,使用投屏软件投屏后,对键盘按键效果跟遥控器类似</p>
<p>有时候没有实体按键(比如电脑没有返回键等),可以直接使用 adb 命令控制</p>
<div class="document">
<div class="section">
<p class="paragraph text-align-type-left"><span data-font-family="default"><strong>adb&nbsp;</strong>shell&nbsp;input&nbsp;keyevent keyCode</span></p>
<p class="paragraph text-align-type-left"><span data-font-family="default">至于长按事件,通过监听的&nbsp;</span>KeyEvent 参数中&nbsp;<strong>repeatCount</strong> 判断,这里最好等于某个数字时触发,防止多次重复触发</p>
<p><img src="https://img2023.cnblogs.com/blog/583064/202309/583064-20230901095138346-307134924.png"></p>
<p>主动获取焦点使用方法 <strong>requestFocus()</strong>,但是可能会失败,所以需要注意等待UI刷新完后在调用</p>
<p>并且在需要获取焦点的view中设置属性,否则没法落焦</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">android:focusable="true"
android:focusableInTouchMode="true"</span></pre>
</div>
<p>如果实在没法控制刷新后的时机,那就只能延迟(postDelayed)获取了,这个万能钥匙,但是尽量少用,影响效率,减低体验</p>
<p class="paragraph text-align-type-left">对于焦点,上面的只是基本操作,实际开发中吭比较多,比较如果界面复杂,焦点是很不好控制的,加上列表各种刷新,跨界面恢复焦点等,懂得都懂</p>
<p class="paragraph text-align-type-left">所以为了更好的<strong>定位问题</strong>,需要借助一些系统监听来获取焦点的状态,才能知道问题在哪</p>
<p class="paragraph text-align-type-left">比如可以重写&nbsp;<strong>requestChildFocus</strong> 方法,每次获取焦点时打印信息,看看是否被其它 view 抢占焦点</p>
<div class="cnblogs_code">
<pre>override fun requestChildFocus(child: View?, focused: View?<span style="color: rgba(0, 0, 0, 1)">) {
      child</span>?<span style="color: rgba(0, 0, 0, 1)">.let {
            val position </span>=<span style="color: rgba(0, 0, 0, 1)"> getChildViewHolder(child).absoluteAdapterPosition
            Logger.d(</span>"requestChildFocus $position "<span style="color: rgba(0, 0, 0, 1)">)
      }
    }</span></pre>
</div>
<p class="paragraph text-align-type-left">或者使用全局监听&nbsp;decorView.viewTreeObserver.<strong>addOnGlobalFocusChangeListener</strong> ,判断哪些 view 获取了焦点</p>
<p class="paragraph text-align-type-left"><strong>针对获取焦点无效怎么处理?</strong></p>
<p class="paragraph text-align-type-left">估计很多时候会发现,<strong><span style="font-family: 黑体, &quot;Heiti SC&quot;; font-size: 12px">调用了 requestFocus 方法没反应</span></strong>,这是因为没有对上个 view 的焦点进行 clear</p>
<p class="paragraph text-align-type-left">你需要在监听中,把获取焦点的 view 赋值给你定义的变量 lastFocusView,然后每次调用&nbsp;requestFocus 前先调用&nbsp;lastFocusView.clearFocus()</p>
<p class="paragraph text-align-type-left">当 <strong>tab</strong> 或者 列表 <strong>切换界面</strong>,这个时候操作遥控器<strong>子页面</strong>自动<strong>抢占了焦点</strong>,怎么处理?</p>
<p class="paragraph text-align-type-left">可以通过对&nbsp;onKeyDown 事件的拦截(return true),这样子页面是无法响应按键事件的,也就无法获取焦点</p>
<p><img src="https://img2023.cnblogs.com/blog/583064/202309/583064-20230901101618612-2090304189.png"></p>
<p>针对一些个别 view 可以设置屏蔽焦点的属性</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">android:focusable="false"
android:focusableInTouchMode="false"</span></pre>
</div>
<p><strong>焦点边框</strong>样式统一处理</p>
<p>可以通过全局监听&nbsp;<strong>addOnGlobalFocusChangeListener</strong>&nbsp;,对 view 进行统一绘制边框,或者一些逻辑控制等</p>
<p><img src="https://img2023.cnblogs.com/blog/583064/202309/583064-20230901103550825-1689164887.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">window.decorView.viewTreeObserver
            .addOnGlobalFocusChangeListener { oldFocus, newFocus </span>-&gt;<span style="color: rgba(0, 0, 0, 1)">
                (newFocus </span>?: window.decorView.findFocus())?<span style="color: rgba(0, 0, 0, 1)">.let {
                  mainUpView.setFocusView(newFocus, oldFocus, </span>1.0f<span style="color: rgba(0, 0, 0, 1)">)
                }
            }</span></pre>
</div>
<p><strong>弹框无法获取焦点怎么处理?</strong></p>
<p>一般 dialog 等只要设置了 focus 属性,然后在初始化调用&nbsp;requestFocus,是没问题的,但是不排除一些个别情况</p>
<p>比如 PopupWindow,因为 window 弹出后,activity 的 onKeyDown 会无法响应,所以<strong>需要单独监听</strong>,前提是获取到焦点</p>
<p>如果碰到焦点不好处理,或者落焦后绘制边框等不方便,这里<strong>建议手动控制</strong>,因为落焦绘制是统一在全局监听里处理的,window 上需要额外监听,没有对这块逻辑封装好,就表示需要把整套逻辑搬到 window 上处理,这显然很冗余</p>
<p>所以何不直接监听&nbsp;onKeyDown ,然后通过代码手动绘制边框焦点</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">contentView.apply {
            requestFocus()
            binding.flMove.setOnKeyListener { _, keyCode, _ </span>-&gt;<span style="color: rgba(0, 0, 0, 1)">
                Logger.d(</span>"setOnKeyListener keyCode $keyCode"<span style="color: rgba(0, 0, 0, 1)">)
                when (keyCode) {
                  KeyCode.KEYCODE_DPAD_LEFT </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                        switchMenu(</span>1<span style="color: rgba(0, 0, 0, 1)">)
                  }

                  KeyCode.KEYCODE_DPAD_RIGHT </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                        switchMenu(</span>0<span style="color: rgba(0, 0, 0, 1)">)
                  }

                  KeyCode.KEYCODE_BACK </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                        dismiss()
                  }
                }
                </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
            }
      }</span></pre>
</div>
<p><img src="https://img2023.cnblogs.com/blog/583064/202309/583064-20230901103830919-1082718116.png"></p>
<p>如果不太喜欢这个方案,还有<strong>另外一个方案</strong></p>
<p>可以在xml中<strong>添加布局</strong>,去<strong>仿造弹框</strong>,这样<strong>焦点就可以统一处理</strong>,不过需要对这个布局进行一些显示隐藏的操作</p>
</div>
</div><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/17671196.html
頁: [1]
查看完整版本: Android TV屏 开发、RecyclerView焦点处理等