Flutter开发Android TV应用竟如此简单

Flutter开发Android TV应用竟如此简单

在安卓开发中,为了区别 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:name=".MainActivity"
            ...>
            ...
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
            </intent-filter>
        </activity>
        ...
    </application>
    ...
</manifest>

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

Flutter开发Android TV应用竟如此简单

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-feature android:name="android.software.leanback" android:required="false" />
    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
    <application
        ...
        android:banner="@mipmap/banner">
        ...
    </application>
    ...
</manifest>

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

在 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(),
)

Flutter开发Android TV应用竟如此简单

记住一点,如果不需要使用 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 中,每次按下都会调用两遍我们的方法。

Flutter开发Android TV应用竟如此简单

这是因为每次按下都会调用两个事件: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('没长按');
      }
}

Flutter开发Android TV应用竟如此简单

我们不需要在 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应用竟如此简单

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

原文链接:https://juejin.cn/post/7353280369359896627 作者:菠萝橙子丶

(0)
上一篇 2024年4月2日 下午5:15
下一篇 2024年4月3日 上午10:05

相关推荐

发表评论

登录后才能评论