稀土掘金技术社区 01月25日
Flutter开发Android TV应用竟如此简单
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了Flutter开发Android TV应用的相关内容,包括在AndroidManifest.xml中声明TV Activity、解决添加后的错误、banner的概念、获取焦点的方法、遥控器按键事件的处理等

在「AndroidManifest.xml」中声明TV Activity并解决添加后出现的错误

介绍banner是Android开发中的自适应图标及相关设置

阐述获取焦点的方法,如为元素添加事件,处理TabBar的切换等

说明遥控器按键事件的获取与处理,包括多种按键事件及长按事件的判断

原创 菠萝橙子丶 2025-01-24 08:31 重庆

点击关注公众号,“技术干货”及时达!

点击关注公众号,“技术干货” 及时达!

在安卓开发中,为了区别 TV 应用和手机应用,我们需要在 「AndroidManifest.xml」 中声明 TV Activity。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="flutter_tv_tmdb"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:
...>
...
<intent-filter>
<action android:/>
<category android:/>
<category android:/>
</intent-filter>
</activity>
...
</application>
...
</manifest>

即在 <intent-filter> 中添加 <category android:/>。在添加了该行后,我们可以发现 「AndroidManifest.xml」 会提示有两个错误:

我们把鼠标箭头移动错误处,会提示解决方法,让它自己解决后会添加如下内容:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android: />
<uses-feature android: />
<application
...
android:banner="@mipmap/banner">
...
</application>
...
</manifest>

banner 是什么?banner 是 Android 开发中的自适应图标,我们在 Android Studio 中创建的 TV 虚拟机中,默认 APP 显示的图标就是 banner 设置的值。官网 TV 应用图标设计准则[1] 有关于这个的详细说明。

在 TV 应用开发中,最重要的是获取焦点,而决定一个元素是否能够获取焦点最简单的一点就是,该元素是否添加了事件。当一个元素添加了事件,它就能被遥控器的方向键选中。

在 TV 应用中,最基本的 TabBar 无法实现我们预想的效果,要想 TabBar 选中就切换,我们需要在外面包裹一层 FocusFocusableActionDetector,使用它们的 onFocusChange 事件来切换 TabBarView :

TabBar(
controller: controller,
tabs: List.generate(
tabs.length,
(index) => FocusableActionDetector(
focusNode: tabs[index].focusNode,
autofocus: index == 0,
onFocusChange: (focus) {
controller.animateTo(index, duration: const Duration(milliseconds: 300), curve: Curves.ease);
},
child: Tab(text: tabs[index].text),
),
).toList(),
)

记住一点,如果不需要使用 AppBar 的话,要把 TabBar 放到 body 里面,如果直接把 TabBar 放到 AppBartitle 中,onFocusChange 只有前几(我测试时只有前 3)个被选中才会调用。

为了获取用户的遥控器按键事件,我们需要使用 KeyboardListener 这个组件,对我们来说主要有 onKeyEvent 中的以下几个事件可以获取:

focusEventHandle(KeyEvent e) {
switch (e.logicalKey) {
case LogicalKeyboardKey.arrowRight:
print('按了右键');
break;
case LogicalKeyboardKey.arrowLeft:
print('按了左键');
break;
case LogicalKeyboardKey.arrowUp:
print('按了上键');
break;
case LogicalKeyboardKey.arrowDown:
print('按了下键');
break;
case LogicalKeyboardKey.select:
print('按了确认键');
break;
case LogicalKeyboardKey.goBack:
print('按了返回键');
break;
case LogicalKeyboardKey.contextMenu:
print('按了菜单键');
break;
case LogicalKeyboardKey.audioVolumeUp:
print('按了音量加键');
break;
case LogicalKeyboardKey.audioVolumeDown:
print('按了音量减键');
break;
case LogicalKeyboardKey.f5:
// 不同品牌的可能不一样
print('按了语音键');
break;
}
}

对于遥控器按键事件的一大难点——并不是所有遥控器都有菜单键(例如 Android Studio 中的 TV 模拟器就没有),即使菜单键在有些需求开发时很重要。好在国内的智能电视都有菜单键,如果只注重国内市场的话可以不用担心。不过上下左右和确定键是所有遥控器的标配。

KeyboardListener 中,每次按下都会调用两遍我们的方法。

这是因为每次按下都会调用两个事件:KeyDownEventKeyUpEvent

通过这两个事件,我们可以监听到用户是否长按了某个键,以此来添加长按事件。

late DateTime start;
void updateStart(DateTime time) {
start = time;
notifyListeners();
}
focusEventHandle(KeyEvent e) {
if (e is KeyDownEvent) {
updateStart(DateTime.now());
print('按下了$start');
}
if (e is KeyUpEvent) {
int duration = DateTime.now().difference(start).inMilliseconds;
bool longPress = duration > 500;
print('${DateTime.now()}');
print('持续了$duration');
if (longPress) {
print('长按了');
} else {
print('没长按');
}
}
}

我们不需要在 focusEventHandle 中添加上下左右的选择事件,因为当一个组件拥有焦点时(所有添加了点击事件的组件都会拥有焦点),系统通过按钮的选择会自动切换到有焦点的组件上。我们唯一要关心的是组件选中时应该呈现的样式。

class ContentView extends StatelessWidget {
const ContentView({super.key});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (context, provider, _) {
List<HomeBtn> list = context.watch<AppProvider>().btnList;
return Column(
children: [
TextButton(
onPressed: () => context.read<AppProvider>().toTabView(context),
child: const Text('下一个界面'),
),
InkWell(onTap: () {}, child: const Text('这是自定义按钮')),
Expanded(
child: GridView.builder(
itemCount: context.watch<AppProvider>().btnList.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 16 / 9,
),
itemBuilder: (context, index) {
HomeBtn btn = list[index];
return KeyboardListener(
focusNode: btn.focusNode,
onKeyEvent: (e) => context
.read<AppProvider>()
.focusEventHandle(e, context, btn),
child: AnimatedContainer(
duration: const Duration(milliseconds: 120),
padding: EdgeInsets.all(btn.focusNode.hasFocus ? 6 : 12),
decoration: BoxDecoration(
color: list[index].focusNode.hasFocus
? Colors.deepOrangeAccent.withOpacity(.5)
: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Image.asset(
list[index].image,
fit: BoxFit.cover,
),
),
);
},
),
),
],
);
},
);
}
}

对于 Flutter 关于开发 Android TV 应用要分享的知识点就这么多,希望对你有帮助。

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Flutter Android TV 焦点获取 遥控器按键
相关文章