一、问题
我们知道,RecyclerView、ListView作为ItemView可以回收的View组建,时常伴随nofityDatasetChanged方法调用,导致removeView 触发clearFocus,出现焦点丢失的问题。那么我们应该如何解决这类问题呢?此外,我们对于焦点的丢失如何去调试呢 ?
二、关于焦点的理解
2.1 焦点的规则
/* * *【1】从根节点深度优先搜索 *【2】符合enable,visible,focusable是最基本的条件,否则无法获焦 *【3】targetSDK >= android P时,0像素View无法聚焦 *【4】正在layout的布局无法聚焦 *【5】父view 设置了FOCUS_BLOCK_DESCENDANTS,父View优先获取焦点 *【6】removeView,requestLayout,非visible,focusable=false,enable=false,notifyDatasetChanged 会或者容易失去焦点 *【7】获焦后再次requestFocus可能导致焦点丢失,因此使用焦点前最好先判断View.isFocused() *【8】没有attachToWindow的无法获取焦点 *【9】onWindowFocusChanged之前调用Request */
2.2 焦点模式和触屏模式
Android中默认支持两种模式,触发KeyEvent会自动切换为Focus模式,触发TouchEvent会自动切换为触屏模式。可以利用View.isInTouchMode()判断
2.3 Window(或Activity)焦点的生命周期
1: entry: onStart---->onResume---->onAttachedToWindow----------->onWindowVisibilityChanged--visibility=0---------->onWindowFocusChanged(true)-
2. exit: onPause---->onStop---->onWindowFocusChanged(false) ---------------------- (lockscreen)
3. exit : onPause----->onWindowFocusChanged(false)-------->onWindowVisibilityChanged--visibility=8------------>onStop(to another activity
三、焦点恢复
焦点恢复有很多种方法,但是前提保证列表获焦在刷新之后,否则可能仍然出现焦点丢失,简单的方法就是 在生命周期中保存Activity.getCurrentFocus(),Activty获焦之后在恢复回来。
四、二次定焦
为什么需要知道二次定焦呢?因为有些很特殊的需求,比如视频自动播放的那种列表,当从其他地方来查找焦点,无疑是可见的第一条或者最后一条,如果想定焦到当前播放的那一条如何做呢?这个时候就需要二次定焦,先把焦点给RecyclerView或者ListView,然后RecyclerView中查找当前播放的那一条,赋予焦点,当然这个肯定得借助OnGlobalFocusChangeListener来实现,当然也要判定焦点转移是不是在View内部,方法如下
public View getDeepestFocusedChild() { View v = this; while (v != null) { if (v.isFocused()) { return v; } v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null; } return null; }
五、列表焦点设计规范
由于RecyclerView滑动过程容易复用View导致失去焦点,因此,在ItemView移动时,务必要在焦点所在的可见Item在贴着顶部和底部,因此移动过程中,进行自动滑动操作,可以有效避免焦点的丢失,可以参考google-leanback的实现
六、焦点的调试
6.1、焦点监听
调试焦点也很容,利用onGlobalFocusChangeListener ,准确把握默焦点的转移。
6.2 用Pad或者手机模拟TV
pad和TV默认是触屏版,因此需要设置foucusableInTouchMode,或者用ADB Chrome也可以自动切换为焦点模式。
当然,触屏版本默认设置focusableInTouchMode是不够的,必须和setOnClickListener或者setClickable(true)一起使用,大多数情况下可以获取焦点的View本身时可以点击的。