1、前言
大家应该非常熟悉在手机App
开发中使用TabLayout
和ViewPager
组件,但是手机的操作模式与电视上通过遥控器操作的方式不同,能否直接在TV
开发中使用上述组件?如果不能直接使用是因为什么问题?
我没有测在TV
开发中直接使用TabLayout
和ViewPager
的情况,但阅读其源码之后猜测可能存在如下问题:
TabLayout
在手机中使用通过Tab的点击触发响应,而在TV
中,谷歌建议应该使用Tab
的Focus
变化来触发响应TabLayout
的item view
应该不能获取焦点(每个Tab
本质对应的View
是一个TabView
,TabLayout
内部基于LinearLayout
实现的)。ViewPager
默认会根据按键的点击进行翻页,而在谷歌的一些文档或者代码中建议ViewPager
在TV
上如果跟TabLayout
联用(或者有其他导航操作行为)的场景下应该禁止ViewPager
通过左右键翻页(大致是这个意思,后面我贴原文)。
大致有以上问题,也没什么严重性问题,经过简单修改基本就能解决,但直觉告诉我官方应该有对应的库支持,不然TabLayout
只能用在Phone
上也太鸡肋了。 果不其然,经过一番搜索找到了,JitPack
提供了leanback-tab,但显然本文的重点不是这个,而是自定义的leanback-tab
2、leanback-tab
官方提供了该库,用于提供TV
开发使用TabLayout+ViewPager
的联动场景,代码不多,大致功能如下:
2.1 LeanbackViewPager
在ViewPager
的基础上增加了以下内容:
- 设置启用/禁用
TouchEvent
(默认禁用) - 设置启用/禁用
KeyEvent
(默认禁用)
代码非常简单,可以查看源码自行了解;
2.2 LeanbackTabLayout
在基于TabLayout
的基础上增下了以下内容
- 与
ViewPager
联动的情况下,Tab
通过Focus
改变切换ViewPager
- 焦点记忆:即
TabLayout
在垂直方向上获取焦点时,会优先让之前已选中的Tab
获取焦点
2.2 不足
虽然官方提供了该库用于支持,但是也是从非常基础的层面让TabLayout
和ViewPager
在TV开发中能够联动使用,但并不意味着能够很好的使用和丰富的功能,有一些交互场景还待优化。
存在以下内容可以进一步优化:
2.2.1 ViewPager
内部寻找下一个焦点View
时会转移到ViewPager
之外的View
上
有些情况在ViewPager
内部响应遥控器左右按键时,我们希望焦点只是在ViewPager
内部转移。
2.2.2 TabLayout
左右边界Tab
响应横向遥控器按键方向的寻焦问题
有些情况在TabLayout
内部响应遥控器左右键时,我们希望焦点只在TabLayout
内部转移。
2.2.3 TabLayout
跟ViewPager
联用时Tab
自定义View
的问题
这是一个众所周知的问题吧(在手机开发中也是一样):我们可以通过tabLayout.newTab().setCustomView()
设置Tab
的自定义效果,但是通过TabLayout
的setupWithViewPager
方法与ViewPager
联用时,TabLayout
默认会移除当前的所有已添加的Tab
,然后通过ViewPager
提供的PagerAdapter#getPageTitle
去创建Tab
,无法自定义Tab
效果,当然也可以不调用setupWithViewPager
方法采用手动绑定的方式实现TabLayout
与ViewPager
的联动,但是这样子也的自己处理tabView
的focus
切换逻辑(LeanbackTabLayout
是在setupWithViewPager
方法关联的viewpager
,在focus
监听中触发viewpager
翻页的)
如果在配上TabLayout
的动态刷新,Tab
样式的动态变化,处理起来还是挺打脑壳的。
所以这些问题基本导致TabLayout
和ViewPager
不是很方便的联用在一起;
3、bas-leanback-tab
本库在结合bas-leanback
的基础上做了更多的扩展以解决TV
上常规需求。
3.1 LeanbackViewPager
3.1.1 设置启用/禁用TouchEvent
(默认禁用)
对应xml属性:app:touchEnabled_lbt
fun setTouchEnabled(enableTouch: Boolean)
复制代码
3.1.2 设置启用/禁用KeyEvent
(默认禁用)
对应xml属性:app:keyEventEnabled_lbt
fun setKeyEventsEnabled(enableKeyEvent: Boolean)
复制代码
3.1.3 设置边界焦点移出与否(默认不允许)
对应xml属性:app:focusOutEnabled_lbt
/**
* 优化边界寻焦规则:触发遥控器左右按键时,是否允许[ViewPager]内的焦点转移到[ViewPager]之外
*/
fun setFocusOutEnabled(enableFocusOut: Boolean) {
focusOutEnabled = enableFocusOut
}
复制代码
3.2 LeanbackTabLayout
LeanbackTabLayout
是基于TabLayout
进行扩展的,所以TabLayout
本身支持的功能基本上都是支持的,比如设置fixed tab
或者scroll tab
、设置tab
选中的indicator
(tab
下面的横线)等。
3.2.1 TV
操作支持
对应xml属性:app:isLeanbackMode_lbt
在TV
上运行时,通过Tab
的Focus
触发Tab
的选中事件(手机上是通过Tab
的点击)
fun setLeanbackMode(isLeanback: Boolean)
复制代码
默认支持TV
操作,因为即便开启TV
操作支持,也不影响在手机上运行。
注:本库的焦点事件绑定较为简单明了。
3.2.2 边界寻焦规则优化
对应xml属性:app:focusOutEnabled_lbt
/**
* 优化边界寻焦规则:触发遥控器左右按键时,是否允许[TabLayout]内的焦点转移到[TabLayout]之外
*/
fun setFocusOutEnabled(enableFocusOut: Boolean) {
focusOutEnabled = enableFocusOut
}
复制代码
3.2.3 焦点记忆
对应xml属性:app:focusMemoryEnabled_lbt
TabLayout
获取焦点时,之前已选中的tab
优先获取焦点
fun setFocusMemoryEnabled(enableFocusMemory: Boolean)
复制代码
3.2.4 ViewPager
联动使用场景、自定义Tab
处理等
支持系统的setupWithViewPager
方法
fun setupWithViewPager(viewPager: ViewPager?, autoRefresh: Boolean)
复制代码
支持与ViewPager
联用时自定义Tab
样式,可以轻易做到文本、文本图标、自定义、甚至完全混合样式的Tab使用场景。
/**
* 根据ViewPager进行初始化;
* @param tabConfigurationStrategy tab配置策略,用于自定义Tab样式;
* [TabConfigurationStrategy.TextStrategy]:文本tab策略
* [TabConfigurationStrategy.ViewPagerStrategy]:(系统TabLayout与ViewPager联用时的规则相似) 使用ViewPager的adapter提供的getPageTitle方法创建文本Tab策略
* [TabConfigurationStrategy.TextIconStrategy]:文本+图标策略
* [TabConfigurationStrategy.CustomViewStrategy]:自定义view策略,[TabConfigurationStrategy.CustomViewFactory]
* 如果以上策略不能满足需求,可以自定义实现[TabConfigurationStrategy]。
* @param autoRefresh [ViewPager.getAdapter]数据改变时是否自动刷新Tab
*/
fun setupWithViewPager(
viewPager: ViewPager?,
tabConfigurationStrategy: TabConfigurationStrategy,
autoRefresh: Boolean = true
)
复制代码
3.2.5 Adapter与Tab的刷新
只要ViewPager
更改了Adapter
,或者调用了Adapter
的notifyDataSetChanged
方法通知刷新更新,TabLayout
将同步更新。
3.2.6 其他说明
TabLayout
与ViewPager
的联动实现已完全封装到LeanbackTabLayoutMediator
类中,该类也可以单独使用。
3.3 使用
该库使用非常简单,查看LeanbackTabLayout的setupWithViewPager
方法即可明白;另外支持的功能前面已列出对应的api和属性,随时调用设置即可。
//最简单的使用方式:Tab会根据Adapter的getPageTitle方法显示文本Tab效果
binding.tabLayout.setupWithViewPager(
binding.tabViewPager
)
//自定义Tab配置策略使用方式:库内置了几种Strategy,ViewPagerStrategy对应系统的处理方式,也就是跟前面的使用方式是一样的,其他策略见TabConfigurationStrategy
binding.tabLayout.setupWithViewPager(
binding.tabViewPager,TabConfigurationStrategy.ViewPagerStrategy(binding.tabViewPager)
)
复制代码