扯淡
在Android TV开发中,最常处理的事情就是焦点的控制了,就像手机APP开发中的触摸事件的处理一样。但两者的处理有很大的区别,手机上是用手指触摸,可以随意点击任意的位置。而TV主要是通过遥控器上下左右移动焦点来操作,下一个焦点在哪,并不是随意的,都是由系统默认的规则或我们的设置来控制的。系统默认的规则是,将焦点交给在该方向上,离当前焦点view最近的,且是可获得焦点的View。
下面是我们常用到的焦点相关方法:
焦点监听
//全局焦点监听
view.viewTreeObserver.addOnGlobalFocusChangeListener(object :ViewTreeObserver.OnGlobalFocusChangeListener{
override fun onGlobalFocusChanged(oldFocus: View?, newFocus: View?) {
//TODO
}
})
//view的焦点监听
view.setOnFocusChangeListener(object :View.OnFocusChangeListener{
override fun onFocusChange(v: View?, hasFocus: Boolean) {
//TODO
}
})
判断view和它的子view是否有焦点
view.hasFocus()
设置view可获得焦点
isFocusableInTouchMode = true
如果是button,或者设置了点击事件的view,则默认就是可获得焦点的,不需要设置。
默认获取焦点
android:focusedByDefault="true"
请求焦点
view.requestFocus()
控制上下左右的焦点
view.nextFocusUpId = R.id.view1
view.nextFocusDownId = R.id.view2
view.nextFocusLeftId = R.id.view3
view.nextFocusRightId = R.id.view4
也可以在布局中指定。若要控制焦点不能出当前View,可以指定上下左右的id全部为自己。
监听方向键进行操作
view.setOnKeyListener(object : View.OnKeyListener {
override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
when(keyCode){
KeyEvent.KEYCODE_DPAD_UP->{//TODO}
KeyEvent.KEYCODE_DPAD_DOWN->{//TODO}
KeyEvent.KEYCODE_DPAD_LEFT->{//TODO}
KeyEvent.KEYCODE_DPAD_RIGHT->{//TODO}
}
return false
}
})
若事件处理完成,应该返回true。
焦点恢复
Android TV应用中,常见的设计都是上面有一个tab栏,显示不同的内容标签。当焦点切换到tab栏下方的内容展示区域中,再切换回tab栏时,焦点恢复到上次选中的标签上。如果tab栏使用的是leanback库中的 VerticalGridView 或者 HorizontalGridView ,那焦点会自动恢复,不需要我们额外处理。但如果使用的普通的ViewGroup,那我们就需要自己处理了。
class TabContainerView : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun addFocusables(views: ArrayList<View>?, direction: Int, focusableMode: Int) {
if (!hasFocus()) {
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child.visibility == View.VISIBLE
&& child.getTag(R.id.tab_focus_flag) as? Boolean == true
) {
views?.add(child)
return
}
}
}
super.addFocusables(views, direction, focusableMode)
}
}
自定义一个ViewGroup继承FrameLayout,并重写 addFocusables 方法。系统在处理焦点时,会调用ViewGroup的 findFocus 查找下一个焦点view,findFocus 里面 会调用 addFocusables 方法将所有可以获取焦点的View收集起来,然后在根据他们的位置,决定将焦点给谁。addFocusables 直接影响findFocus 的结果,我们可以重写该方法,将要恢复的标签添加到views中,其他的就不添加。这里通过子view是否有TAG来找到要恢复焦点的view,TAG在外部设置,这样就只让这个view获取到焦点,从而恢复tab栏的焦点。