随着智能手机和平板电脑的爆炸式增长,电视成为了下一个联网“智能”设备。对于智能电视而言,现在占有率最广的就是 Android 系统了。
Android TV 是谷歌开发的互动电视平台,于 2014 年在其 I/O 大会上发布。
而 Leanback 是谷歌为 Android TV 应用设计的专用 UI 框架,基于 androidx.leanback 包提供组件和架构模式,优化大屏、遥控器交互体验,开发出来的界面也符合 Material Design 规范。
前言
不知道有多少人接触过 Android TV 或者叫 Android 智能电视/盒子相关的开发。Android TV应用使用与手机和平板电脑相同的架构,这就意味着我们可以根据已经了解的有关构建Android应用的方法去构建新的电视应用程序,或者将现有的应用程序拓展到电视设备上去运行。
其实可以把 Android TV 的 App,当成一个横屏显示的普通 App 来开发。在交互上面,也是不同于手机的点点点,它需要命中焦点,然后来触发点击。通常的做法就是在手机上开发,开发完成之后再在电视上进行测试。为了在手机上模拟电视遥控器的操作,推荐一个 Chrome 插件:ChromeADB。只需要保证开发设备和调试设备,ADB 连接通畅,通过 ChromeADB可以实现一些遥控器的简单上下左右的操作。
Leanback
Leanback 库内提供的 RecyclerView 把一些很头疼的焦点记忆、焦点项目放大、滚动时焦点块居中等问题都封装好了,简单到可以拿来即用,帮助开发者快速实现TV的开发。
-
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14dependencies {
val leanback_version = "1.2.0-beta01"
implementation("androidx.leanback:leanback:$leanback_version")
// leanback-preference is an add-on that provides a settings UI for TV apps.
implementation("androidx.leanback:leanback-preference:$leanback_version")
// leanback-paging is an add-on that simplifies adding paging support to a RecyclerView Adapter.
implementation("androidx.leanback:leanback-paging:1.1.0-beta01")
// leanback-tab is an add-on that provides customized TabLayout to be used as the top navigation bar.
implementation("androidx.leanback:leanback-tab:1.1.0-beta01")
}
主要类
-
BaseGridView 继承RecyclerView,重写所有焦点逻辑,Leanback页面根布局容器。
-
HorizontalGridView 继承BaseGridView,提供水平布局能力。
-
VerticalGridView 继承BaseGridView,提供垂直布局能力。
-
ArrayObjectAdapter 数据适配器,继承ObjectAdapter,内部可包含数据和视图结构内容信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 一般用于每行或列数据的创建
/**
* Constructs an adapter with the given {@link PresenterSelector}.
*/
public ArrayObjectAdapter(PresenterSelector presenterSelector) {
super(presenterSelector);
}
// 一般为ItemBridgeAdapter创建构造参数时使用
/**
* Constructs an adapter that uses the given {@link Presenter} for all items.
*/
public ArrayObjectAdapter(Presenter presenter) {
super(presenter);
} -
ListRow行视图提供者,Android原生封装好了,支持子视图焦点动效及行标题展示。
-
Presenter 提供视图创建及数据绑定,类似RecyclerView.Adapter的功能,注意是类似,下面的ItemBridgeAdapter才是填充到BaseGridView中真正的Adapter。目前暂且称之为视图加载器。
1
2
3
4
5
6
7
8
9/**
* Creates a new {@link View}.
*/
public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
/**
* Binds a {@link View} to an item.
*/
public abstract void onBindViewHolder(ViewHolder viewHolder, Object item); -
PresenterSelector 根据不同的Item Object类型提供不同的Presenter对象,进行不同的布局视图创建和数据绑定,目前暂且称之为视图构造筛选器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* Returns a presenter for the given item.
*/
public abstract Presenter getPresenter(Object item);
// 这个方法需要重写,根据item返回对应的presenter
/**
* Returns an array of all possible presenters. The returned array should
* not be modified.
*/
public Presenter[] getPresenters() {
return null;
}
// 这个方法需要返回所有的presenters数组,中途不可改变数据 -
ItemBridgeAdapter 填充至BaseGridView的适配器,继承RecyclerView.Adapter。
1
2
3
4
5
6
7
8
9
10//主要有两个构造方法,需要传递一个ObjectAdapter
public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
setAdapter(adapter);
mPresenterSelector = presenterSelector;
}
public ItemBridgeAdapter(ObjectAdapter adapter) {
this(adapter, null);
}
基本使用
以VerticalGridView为例:
-
自定义Presenter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class PresenterSample extends Presenter {
// 创建BaseGridView中每一个Item的视图,如果使用ListRow则是创建每一行中的每一个Item视图
public ViewHolder onCreateViewHolder(ViewGroup parent) {
return new HolderSample(...ItemView...);
}
// 数据绑定,item即是外层ArrayObjectAdapter中包含的数据类型
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
if (viewHolder instanceof HolderSample) {
HolderSample holder = (HolderSample) viewHolder;
if (item instanceof YourDataType) {
bindDataWithViews()
...
}
}
}
public void onUnbindViewHolder(ViewHolder viewHolder) {
releaseSource()
...
}
// 自定义Holder,处理视图点击,焦点事件等
static class HolderSample extends ViewHolder {
HolderSample(View view) {
super(view);
}
}
} -
构造每一行的数据
1
2
3
4
5
6
7
8
9
10ListRow方式:
// 构造一个ArrayObjectAdapter,填充一个Presenter
ArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(new PresenterSample());
// 填充数据
rowAdapter.add(...Data...);
// 构造一个ListRow
ListRow listRow = new ListRow(rowAdapter);
普通方式:
// 构造一个指定数据类型对象
CustomData data = new CustomData(); -
自定义PresenterSelector
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class PresenterSelectorSample extends PresenterSelector {
// 此处item就是外层ArrayObjectAdapter添加进来的数据,按添加索引排序
public Presenter getPresenter(Object item) {
if (item.getClass == ListRow.class) {
return new ListRowPresenter();
} else if (item.getClass == CustomData.class) {
return new PresenterCustom();
} else {
return ...
}
}
public Presenter[] getPresenters() {
return mPresenters.toArray(new Presenter[]{});
}
} -
构造ArrayObjectAdapter装载数据
1
2
3
4
5
6
7// 构建一个自定义的PresenterSelectorSample
PresenterSelectorSample presenterSelector = new PresenterSelectorSample();
// 构建一个装载每行数据的ArrayObjectAdapter
ArrayObjectAdapter verticalAdapter = new ArrayObjectAdapter(presenterSelector);
// 填充数据
verticalAdapter.add(listRow);
verticalAdapter.add(CustomData); -
构造ItemBridgeAdapter填充给VerticalGridView
1
2
3// 该Adapter只是作为一个桥梁作用,将每一行结构对应的presenter和 进行关联
ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(verticalAdapter);
VerticalGridView.setAdapter(bridgeAdapter);
选中放大的效果需要使用 FocusHighlightHelper
注意事项
- 自动焦点控制:VerticalGridView 支持属性 focusOutEnd,避免导航溢出。
- 自定义焦点策略:可通过 OnItemViewSelectedListener 监听选中项,实现动态高亮效果。
- GuidedStepSupportFragment:创建多步骤任务流程(如首次启动向导),支持返回栈管理。
- 版本兼容性:最低支持 API 17(Android 4.2),部分特效需 API 21+(如 Material 过渡动画)。
- 主题限制:禁止使用 AppCompatActivity:需继承 FragmentActivity,避免与 Leanback 主题冲突。
- 复用 Presenter:避免频繁创建对象,提升列表滚动流畅度。
- 分页预加载:结合 PagingData 实现懒加载,减少内存占用。