从事Android TV应用开发有一段日子了,今天开始讲述如何利用Google提供的Leanback库实现Android TV开发,欢迎感兴趣的朋友一起交流。
其实TV应用使用的结构与手机和平板电脑的应用相同,这也意味着您可以根据自身对 Android 应用的既有知识创建新的 TV 应用,也可以扩展现有应用,使其支持 TV 设备。不过,TV 与手机和平板电脑设备在用户交互模式上差异很大。首先就是操控方式的改变,从触摸变成了遥控器;其次就是距离的变化。为了让您的应用在 TV 设备上取得成功,您必须设计新的布局,使用户能在距离电视 10 英尺的地方轻松看清楚屏幕内容,并且只需使用方向键和选择按钮便可完成导航。
设置TV项目
本部分介绍如何将现有 Android 应用改造成能在 TV 设备上运行,或创建新应用。如果您已有 Android 应用,那么添加 Android TV 支持后,您就能在现有的应用架构基础上设计适用于 TV 的界面。
申明TVActivity
目标平台为 TV 设备的应用必须在其清单中声明 TV 启动器 Activity。它使用 CATEGORY_LEANBACK_LAUNCHER
intent 过滤器来执行此操作。此过滤器可将您的应用标识为支持 TV 平台,并让 Google Play 将其识别为 TV 应用。当用户在其 TV 主屏幕上选择您的应用时,此 intent 可确定要启动的 Activity。
<application
android:banner="@drawable/banner" >
...
<activity
android:name="com.example.android.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.example.android.TvActivity"
android:label="@string/app_name"
android:theme="@style/Theme.Leanback">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
</application>
本例中的第二个 Activity 清单条目将该 Activity 指定为要在 TV 设备上启动的 Activity。如果手机和TV都是用MainActivity作为启动页面的话,可以直接增加带有CATEGORY_LEANBACK_LAUNCHER
的intent-filter标签,代码如下:
<application
android:banner="@drawable/banner" >
...
<activity
android:name="com.example.android.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
</application>
注意:如果您不在应用中包含 CATEGORY_LEANBACK_LAUNCHER
intent 过滤器,那么用户在 TV 设备上运行 Google Play 时将看不到您的应用。此外,如果您的应用没有此过滤器,那么当您使用开发者工具将其加载到 TV 设备上时,该应用不会出现在 TV 界面中。
声明 Leanback 支持
声明您的应用使用 Android TV 所要求的 Leanback 界面。如果您要开发一款在移动设备(手机、穿戴式设备、平板电脑等)以及 Android TV 上都可运行的应用,请将 required 属性值设为 false。如果您将 required 属性值设为 true,您的应用将只能在使用 Leanback 界面的设备上运行。
<manifest>
<uses-feature android:name="android.software.leanback"
android:required="false" />
...
</manifest>
将触摸屏声明为非必备条件
目标平台为 TV 设备的应用不依赖于触摸屏进行输入。为明确这一点,您的 TV 应用的清单必须声明 android.hardware.touchscreen 功能为非必备功能。此设置会将您的应用标识为能够在 TV 设备上工作,这也是您的应用在 Google Play 中被视为 TV 应用的必要条件。以下代码示例展示了如何添加此清单声明:
<manifest>
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
...
</manifest>
注意:您必须在应用清单中声明触摸屏并非必要条件(如本示例代码中所示),否则您的应用将不会出现在 TV 设备上的 Google Play 中。
提供主屏幕横幅
如果应用包含 Leanback 启动器 intent 过滤器,那么它必须针对每种本地化语言提供一张主屏幕横幅图片。横幅是显示在主屏幕的应用和游戏行中的应用启动点。如需向您的应用添加横幅,请在清单中描述横幅,如下所示:
<application
...
android:banner="@drawable/banner" >
...
</application>
您可以将 android:banner
属性与 <application>
标记一起使用,为所有应用 Activity 提供默认横幅,也可以将其与 <activity>
标记一起使用,为特定 Activity 提供横幅。横幅应该是 xhdpi 资源,尺寸为 320 x 180 像素。文本必须包含在图片中。
如果您的应用支持多种语言,对于带文本的横幅,您必须针对支持的每种语言提供单独的版本。
更改启动器颜色
当 TV 应用启动时,系统会显示动画,就像一个不断膨胀的实心圆。要自定义此动画的颜色,请将 TV 应用或 Activity 的 android:colorPrimary 属性设为特定颜色。此外,还应将另外两个过渡重叠属性设为 true,如主题背景资源 XML 文件中的以下代码段所示:
<resources>
<style ... >
<item name="android:colorPrimary">@color/primary</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
</style>
</resources>
添加TV库
Jetpack 包含用于 TV 应用的androidx
软件包库。这些库为 TV 设备提供了 API 和界面微件。
androidx.leanback.app
androidx.leanback.database
androidx.leanback.graphics
androidx.leanback.media
androidx.leanback.preference
androidx.leanback.system
androidx.leanback.widget
androidx.leanback.widget.picker
处理TV硬件
TV 硬件与其他 Android 设备截然不同。TV 不提供其他 Android 设备上提供的一些硬件功能,如触摸屏、相机和 GPS 接收器。此外,TV 还完全依赖于辅助硬件设备。用户必须使用遥控器或游戏手柄才能与 TV 应用进行交互。构建 TV 应用时,您必须仔细考虑在 TV 硬件上运行应用的硬件限制和要求。
检查TV设备
如果您在开发一款在 TV 设备和其他设备上都能运行的应用,可能需要检查运行您的应用的设备类型,并相应调整应用的工作方式。例如,如果您开发的应用可通过 Intent
启动,您的应用应通过检查设备属性来确定它是应该启动面向 TV 的 Activity 还是手机 Activity。
如需确定您的应用是否正在 TV 设备上运行,推荐的做法是使用 UiModeManager.getCurrentModeType()
方法来检查该设备是否正在电视模式下运行。以下示例代码向您展示了如何检查您的应用是否正在 TV 设备上运行:
public static final String TAG = "DeviceTypeRuntimeCheck";
UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
Log.d(TAG, "Running on a TV Device");
} else {
Log.d(TAG, "Running on a non-TV Device");
}
上述的API只能用于判断系统是基于Android TV源码编译生成的ROM,那对于那些Android TV或者机顶盒是基于手机或者平板模式修改而来的应该怎么判断呢?彩蛋在篇尾!!!
处理不支持的硬件功能
根据应用的设计和功能,您或许能够解决某些硬件功能无法使用的问题。本部分介绍哪些硬件功能通常不适用于 TV,以及如何检测缺少的硬件功能,并就这些功能的替代方案提供了建议。
不支持的 TV 硬件功能
TV 的用途不同于其他设备,因此它们没有其他 Android 设备通常具备的硬件功能。因此,Android 系统在 TV 设备上不支持以下功能:
- 触摸屏
android.hardware.touchscreen
- 触摸屏模拟器
android.hardware.faketouch
- 电话
android.hardware.telephony
- 相机
android.hardware.camera
- 近距离无线通信 (NFC)
android.hardware.nfc
- GPS
android.hardware.location.gps
- 麦克风
android.hardware.microphone
- 传感器
android.hardware.sensor
- 纵向屏幕
android.hardware.screen.portrait
声明对 TV 的硬件要求
Android 应用可以在应用清单中声明硬件功能要求,以确保用户不会将其安装在不提供这些功能的设备上。如果您要扩展现有应用以便在 TV 上使用,请仔细检查应用的清单中是否声明了任何可能会导致该应用无法安装在 TV 设备上的硬件要求。
如果您的应用使用了 TV 上不提供的硬件功能(如触摸屏或相机),但不使用这些功能仍可运行,请修改应用的清单文件,以指明这些功能并非应用必需的功能。以下这段清单文件代码演示了如何声明您的应用不要求具备 TV 设备上不提供的硬件功能,尽管您的应用可能会在非 TV 设备上使用这些功能:
<uses-feature android:name="android.hardware.touchscreen"
android:required="false"/>
<uses-feature android:name="android.hardware.faketouch"
android:required="false"/>
<uses-feature android:name="android.hardware.telephony"
android:required="false"/>
<uses-feature android:name="android.hardware.camera"
android:required="false"/>
<uses-feature android:name="android.hardware.nfc"
android:required="false"/>
<uses-feature android:name="android.hardware.location.gps"
android:required="false"/>
<uses-feature android:name="android.hardware.microphone"
android:required="false"/>
<uses-feature android:name="android.hardware.sensor"
android:required="false"/>
注意:如果将硬件功能的值设为 true 来将其声明为必需功能,就会导致您的应用无法安装在 TV 设备上或出现在 Android TV 主屏幕启动器中。
检查硬件功能
如果运行您的应用的设备不具备某些硬件功能,Android 框架可以告诉您。您可以使用 hasSystemFeature(String)
方法在运行时检查特定功能。此方法带有一个字符串参数,用于指定您要检查的功能。
以下代码示例演示了如何在运行时检测硬件功能的可用性:
// Check if the telephony hardware feature is available.
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
Log.d("HardwareFeatureTest", "Device can make phone calls");
}
// Check if android.hardware.touchscreen feature is available.
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
Log.d("HardwareFeatureTest", "Device has a touch screen.");
}
TV导航和焦点处理
在 TV 设备上,用户通过遥控器设备上的控件进行导航,即使用方向键或箭头键来导航。这种类型的控件将移动限定在上、下、左、右四个方向。如需构建一款针对 TV 优化的出色应用,您提供的导航架构必须能让用户快速学会如何利用这些有限的控件在您的应用内导航。
Android 框架会自动处理布局元素之间的方向导航,因此您通常无需对应用执行任何额外的操作。不过,您应该对通过方向键控制器导航进行全面测试,以发现任何导航问题。请遵循以下准则来测试您应用的导航系统是否能够在 TV 设备上与方向键很好地搭配使用:
- 确保用户可使用方向键控制器导航到屏幕上的所有可见控件。
- 对于获得焦点的滚动列表,请确保可使用方向键的向上键和向下键滚动列表,并可使用 Enter 键选择列表中的项目。验证用户是否可选择列表中的元素,以及选择元素时是否列表仍会滚动。
- 确保控件间的切换简单明了并且可以预测。
Android 框架会根据布局中可聚焦元素的相对位置自动应用方向导航架构。您应使用方向键控制器在您的应用内测试生成的导航架构。测试后,如果您决定希望用户以特定方式在布局中导航,可以为控件设置显式方向导航。
注意:只有在系统应用的默认顺序不太奏效时,才应使用这些属性来修改导航顺序。
下表列出了 Android 界面微件的所有可用导航属性:
-
nextFocusDown
: 定义当用户向下导航时下一个获得焦点的视图。 -
nextFocusLeft
:定义当用户向左导航时下一个获得焦点的视图。 -
nextFocusRight
: 定义当用户向右导航时下一个获得焦点的视图。 -
nextFocusUp
: 定义当用户向上导航时下一个获得焦点的视图。
以下代码示例展示了如何定义下一个获得TextView
布局对象焦点的控件:
<TextView android:id="@+id/Category1"
android:nextFocusDown="@+id/Category2"\>
明确的焦点和选择
应用的导航架构能否在 TV 设备上发挥作用取决于用户判定屏幕上获得焦点的界面元素方面的便利性。如果您不能明确指示获得焦点的项目(因此也就无法明确指示用户可以采取操作的项目),用户可能很快就会因失望而退出您的应用。出于同一原因,在您的应用启动后或者处于闲置状态的任何时间,必须始终有获得焦点的项目可供用户采取操作。
Android 提供了[可绘制对象状态列表资源StateList
]来实现针对获得焦点的控件和选定控件的突出显示。以下代码示例演示了如何通过为按钮启用视觉行为来指示用户已导航到该控件而且随后还选择了它:
<!-- res/drawable/button.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/button_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/button_focused" /> <!-- focused -->
<item android:state_hovered="true"
android:drawable="@drawable/button_focused" /> <!-- hovered -->
<item android:drawable="@drawable/button_normal" /> <!-- default -->
</selector>
以下布局 XML 示例代码会将上一状态列表可绘制对象应用于 Button
:
<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/button" />
UiModeManager只能判断系统是基于标准的Android TV框架编译得到的设备是不是TV设备,而对于系统是采用手机或者平板框架编译得到的设备是无效的。那我们该怎么处理呢?解决方案如下:
// Check if android.hardware.touchscreen feature is available.
boolean isTouchScreen = getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
boolean isFakeScreen = getPackageManager().hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
boolean isTvDevice = uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
Log.d(TAG, "isFakeScreen:" + isFakeScreen);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
if (isTvDevice) {
isTvModel = !isFakeScreen;
} else {
isTvModel = (isTouchScreen != isFakeScreen);
}
isTvModel = isTvModel && (uiModeManager.getCurrentModeType() != Configuration.UI_MODE_TYPE_WATCH); //TV mode不是手表模式
} else {
if (isTvDevice) {
isTvModel = !isFakeScreen;
} else {
isTvModel = (isTouchScreen != isFakeScreen);
}
}
这里基本是根据是触摸来决定是不是TV设备,虽然同时取了FEATURE_TOUCHSCREEN
和FEATURE_FAKETOUCH
两个属性,但是经过测试发现FEATURE_FAKETOUCH
会更准确。
到此,我们已经做好了开发Android TV应用的准备工作,包括如何申明TV启动页、添加TV横幅、添加Leanback支持等。接下来让我们具体学习如何开发一款TV应用吧!