[Android] Android TV - Study Leanback

Posted by xiuyuantech on 2025-02-09

随着智能手机和平板电脑的爆炸式增长,电视成为了下一个联网“智能”设备。对于智能电视而言,现在占有率最广的就是 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的开发。
Leanback UI

  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    dependencies {
    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")
    }
  • 官方例子
    leanback-showcase (推荐)
    androidtv-Leanback

主要类

  • 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
    32
    public class PresenterSample extends Presenter {
    // 创建BaseGridView中每一个Item的视图,如果使用ListRow则是创建每一行中的每一个Item视图
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
    return new HolderSample(...ItemView...);
    }

    // 数据绑定,item即是外层ArrayObjectAdapter中包含的数据类型
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
    if (viewHolder instanceof HolderSample) {
    HolderSample holder = (HolderSample) viewHolder;
    if (item instanceof YourDataType) {
    bindDataWithViews()
    ...
    }
    }
    }

    @Override
    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
    10
    ListRow方式:
    // 构造一个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
    20
    public class PresenterSelectorSample extends PresenterSelector {

    // 此处item就是外层ArrayObjectAdapter添加进来的数据,按添加索引排序
    @Override
    public Presenter getPresenter(Object item) {
    if (item.getClass == ListRow.class) {
    return new ListRowPresenter();
    } else if (item.getClass == CustomData.class) {
    return new PresenterCustom();
    } else {
    return ...
    }
    }

    @Override
    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和data进行关联
    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 实现懒加载,减少内存占用‌。