Flutter TV Android端开发技巧详细教程
前言
最近公司有了新的业务,把现有Flutter Android项目应用到TV上去,这不,Asscre的活就来了。
本文详细说明Flutter for TV的两种实现方式,能力有限,不足之处欢迎指点,哈哈哈
开发思路
在开发之前,我们先设定一下我们的思路。
即,如何对原有程序代码侵入式最小、性能最佳、可玩性更高做出设定。
那么,通过上面的设定,我们在Flutter Widget中就发现了两个东西:
- RawKeyboardListener
- InkWell和其他Android TV配置
先上效果
可玩性、可塑性更高的RawKeyboardListener解决方案效果
对原有程序修改最小的InkWell和其他Android TV配置解决方案效果
开发细节
可玩性、可塑性更高的RawKeyboardListener解决方案
使用RawKeyboardListener
RawKeyboardListener( focusNode: d.focusNode, // 配置focusNode onKey: (RawKeyEvent event) => context.read<HomePageContentWidgetProvider>().focusEventHandler(event, context, d), // 对特殊事件进行监听和处理 child: Container( height: 190, width: 190, decoration: BoxDecoration( border: Border.all( width: 2, color: d.focusNode.hasFocus ? Colors.blue : Colors.transparent), borderRadius: BorderRadius.circular(20), color: Colors.white.withAlpha(20), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( d.img, height: 80, ), SizedBox(height: 20), Text( d.name, style: TextStyle( color: Colors.white, fontSize: 32, ), ), ], ), ), ),
Provider层对事件进行处理
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:tv_test/pages/memory_page/memory_page.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class HomePageContentWidgetProvider with ChangeNotifier { bool init = false; double maxWScreen = 0; // 按钮距离屏幕右侧最大边界 double minWScreen = 60.w; // 按钮距离屏幕最左侧距离边界 final List<HomePageMakeBtn> makeBtnList = [ HomePageMakeBtn('lib/assets/img/youtube.png', 'You Tube', '', FocusNode()), HomePageMakeBtn('lib/assets/img/apple.png', 'Apple', '', FocusNode()), HomePageMakeBtn('lib/assets/img/facebook.png', 'Facebook', '', FocusNode()), HomePageMakeBtn('lib/assets/img/douyin.png', 'Tik Tok', '', FocusNode()), HomePageMakeBtn('lib/assets/img/mi.png', 'MI', '', FocusNode()), HomePageMakeBtn('lib/assets/img/huawei.png', 'Hua Wei', '', FocusNode()), HomePageMakeBtn('lib/assets/img/youtube.png', 'TTT', '', FocusNode()), HomePageMakeBtn('lib/assets/img/apple.png', 'DDDD', '', FocusNode()), HomePageMakeBtn('lib/assets/img/facebook.png', 'FFFF', '', FocusNode()), HomePageMakeBtn('lib/assets/img/douyin.png', 'AAAA', '', FocusNode()), HomePageMakeBtn('lib/assets/img/mi.png', 'QQQQQ', '', FocusNode()), HomePageMakeBtn('lib/assets/img/huawei.png', 'WWWW', '', FocusNode()), HomePageMakeBtn('lib/assets/img/youtube.png', 'EEEEE', '', FocusNode()), HomePageMakeBtn('lib/assets/img/apple.png', 'RRRRR', '', FocusNode()), HomePageMakeBtn('lib/assets/img/facebook.png', 'YYYYYY', '', FocusNode()), HomePageMakeBtn('lib/assets/img/douyin.png', 'UUUUUU', '', FocusNode()), HomePageMakeBtn('lib/assets/img/mi.png', 'SSSSS', '', FocusNode()), HomePageMakeBtn('lib/assets/img/huawei.png', 'VVVV', '', FocusNode()), ]; HomePageContentWidgetProvider(BuildContext context) { maxWScreen = MediaQuery.of(context).size.width - 246.w; // setMakeFocusAddListener(); if (!init) { makeBtnList.first.focusNode.requestFocus(); init = true; } } setMakeFocusAddListener() { for (int i = 0; i < makeBtnList.length; i++) { makeBtnList[i].focusNode.addListener(() { if (makeBtnList[i].focusNode.hasFocus) { // notifyListeners(); print( '====${makeBtnList[i].name} : ${makeBtnList[i].focusNode.hasFocus}'); } }); } } setMakeFocusDispose() { for (var item in makeBtnList) { item.focusNode.removeListener(() {}); item.focusNode.dispose(); } } focusEventHandler( RawKeyEvent event, BuildContext context, HomePageMakeBtn param) async { /// 只处理按键按下的事件 if (event.data is RawKeyEventDataAndroid && event.runtimeType.toString() == 'RawKeyDownEvent') { CustomRawKeyEventDataAndroid _d = CustomRawKeyEventDataAndroid.format(event.data); /// 对按下确定键和中心键进行处理 if (_d.keyCode == 23 || _d.keyCode == 66) { Navigator.of(context).push( MaterialPageRoute(builder: (_) => MemoryPage(title: param.name))); } else { // for (var e in makeBtnList) { // print('${e.name} : ${e.focusNode.hasFocus}'); // } /// 对左键进行处理 if (_d.keyCode == 21) { await keyCodeDpadLeft(context, param); } /// 对右键进行处理 if (_d.keyCode == 22) { await keyCodeDpadRight(context, param); } notifyListeners(); } } } /// 对左键进行处理 keyCodeDpadLeft(BuildContext context, HomePageMakeBtn param) async { /// 首位边界处理 final int _idx = makeBtnList.indexWhere((e) => e == param); if (_idx == 0) return; final int _nextIndex = _idx + 1; if ((_nextIndex % 7) == 1) { HomePageMakeBtn _nextNode = makeBtnList[_idx - 1]; print(_nextNode.name); await Future.delayed(const Duration(milliseconds: 20)); _nextNode.focusNode.requestFocus(); } } /// 对右键进行处理 keyCodeDpadRight(BuildContext context, HomePageMakeBtn param) async { final int _idx = makeBtnList.indexWhere((e) => e == param); /// 末位边界处理 if (_idx == (makeBtnList.length - 1)) return; final int _nextIndex = _idx + 1; if ((_nextIndex % 7) == 0) { HomePageMakeBtn _nextNode = makeBtnList[_nextIndex]; await Future.delayed(const Duration(milliseconds: 20)); _nextNode.focusNode.requestFocus(); } } @override void dispose() { setMakeFocusDispose(); super.dispose(); } } class HomePageMakeBtn { final String img; final String name; final String routerName; final FocusNode focusNode; HomePageMakeBtn(this.img, this.name, this.routerName, this.focusNode); } class CustomRawKeyEventDataAndroid { final int flags; final int codePoint; final int plainCodePoint; /// case 19: KEY_UP /// case 20: KEY_DOWN /// case 21: KEY_LEFT /// case 22: KEY_RIGHT /// case 23: KEY_CENTER final int keyCode; final int scanCode; final int metaState; CustomRawKeyEventDataAndroid(this.flags, this.codePoint, this.plainCodePoint, this.keyCode, this.scanCode, this.metaState); static CustomRawKeyEventDataAndroid format(d) { return CustomRawKeyEventDataAndroid(d.flags, d.codePoint, d.plainCodePoint, d.keyCode, d.scanCode, d.metaState); } }
注意
我们可以看到在处理左键和右键的时候我们用了
这是为什么呢?
那是因为在实际效果中,我们requestFocus操作的时候,Flutter的机制会首先触发一次requestFocus,然后再触发一次requestFocus,一共两次,这就与我们的预想就有冲突了。
例如:
使用按键末尾向右时,系统触发的focus到UUUUU这个按钮,我们的实际预想的是到YYYYY即可。
使用按键首位向左时,同样会跨两个focus。
目前Asscre并没有找到很好的解决方案,但使用await Future delayed可以舒缓一下这不人性的操作。
对原有程序修改最小的InkWell和其他Android TV配置解决方案
首先,我们需要在AndroidManifest.xml 设置LEANBACK_LAUNCHER告诉平台我们的程序是一个电视应用程序
<intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> // 新增这一句 <category android:name="android.intent.category.LAUNCHER"/> </intent-filter>
然后,我们在Main入口文件中添加 Shortcuts用于我们的程序响应我们的遥控器指令。
return Shortcuts( shortcuts: <LogicalKeySet, Intent>{ LogicalKeySet(LogicalKeyboardKey.select): ActivateIntent(), }, child: MaterialApp( ... );
最后,使用InkWell来获取焦点设置用户遥控点击的效果,其中focusColor帮助我们提醒用户此时的按钮位置。
return Material( color: Colors.white.withAlpha(20), child: InkWell( focusColor: Colors.deepOrange.withAlpha(80), onTap: () => Navigator.of(context) .push(MaterialPageRoute(builder: (_) => MemoryPage(title: d.name))), child: SizedBox( height: 190, width: 190, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( d.img, height: 80, ), SizedBox(height: 20), Text( d.name, style: TextStyle( color: Colors.white, fontSize: 32, ), ), ], ), ), ), );
总结
上述两种解决方案中,大家可以根据自己(boss)的喜好或者业务需求选择一种使用。
在需要复杂的自定义的业务情况下,推荐使用RawKeyboardListener的解决方案,可以做出很多酷炫的效果,譬如按键事件触发时,focus住的widget可以做出放大、渐变等等效果,这有助于提升用户的体验。
但,要是在现有的业务逻辑上,在少量调整后就可使用上述中的InkWell的解决方案。
文件参考
TV keyCode详解
name | keycode | 说明 |
---|---|---|
KEYCODE_UNKNOWN | 0 | |
--------------------------------------- | ----- | -------------- |
KEYCODE_SOFT_LEFT | 1 | |
KEYCODE_SOFT_RIGHT | 2 | |
KEYCODE_HOME | 3 | HOME键 |
KEYCODE_BACK | 4 | 返回键 |
KEYCODE_CALL | 5 | 拨号键 |
KEYCODE_ENDCALL | 6 | 挂机键 |
KEYCODE_0 | 7 | |
KEYCODE_1 | 8 | |
KEYCODE_2 | 9 | |
KEYCODE_3 | 10 | |
KEYCODE_4 | 11 | |
KEYCODE_5 | 12 | |
KEYCODE_6 | 13 | |
KEYCODE_7 | 14 | |
KEYCODE_8 | 15 | |
KEYCODE_9 | 16 | |
KEYCODE_STAR | 17 | 按键 * |
KEYCODE_POUND | 18 | 按键 # |
KEYCODE_DPAD_UP | 19 | 向上 |
KEYCODE_DPAD_DOWN | 20 | 向下 |
KEYCODE_DPAD_LEFT | 21 | 向左 |
KEYCODE_DPAD_RIGHT | 22 | 向右 |
KEYCODE_DPAD_CENTER | 23 | 确定键 |
KEYCODE_VOLUME_UP | 24 | 音量增加键 |
KEYCODE_VOLUME_DOWN | 25 | 音量减小键 |
KEYCODE_POWER | 26 | 电源键 |
KEYCODE_CAMERA | 27 | 拍照键 |
KEYCODE_CLEAR | 28 | |
KEYCODE_A | 29 | |
KEYCODE_B | 30 | |
KEYCODE_C | 31 | |
KEYCODE_D | 32 | |
KEYCODE_E | 33 | |
KEYCODE_F | 34 | |
KEYCODE_G | 35 | |
KEYCODE_H | 36 | |
KEYCODE_I | 37 | |
KEYCODE_J | 38 | |
KEYCODE_K | 39 | |
KEYCODE_L | 40 | |
KEYCODE_M | 41 | |
KEYCODE_N | 42 | |
KEYCODE_O | 43 | |
KEYCODE_P | 44 | |
KEYCODE_Q | 45 | |
KEYCODE_R | 46 | |
KEYCODE_S | 47 | |
KEYCODE_T | 48 | |
KEYCODE_U | 49 | |
KEYCODE_V | 50 | |
KEYCODE_W | 51 | |
KEYCODE_X | 52 | |
KEYCODE_Y | 53 | |
KEYCODE_Z | 54 | |
KEYCODE_COMMA | 55 | 按键 , |
KEYCODE_PERIOD | 56 | 按键 . |
KEYCODE_ALT_LEFT | 57 | |
KEYCODE_ALT_RIGHT | 58 | |
KEYCODE_SHIFT_LEFT | 59 | |
KEYCODE_SHIFT_RIGHT | 60 | |
KEYCODE_TAB | 61 | Tab键 |
KEYCODE_SPACE | 62 | 空格键 |
KEYCODE_SYM | 63 | |
KEYCODE_EXPLORER | 64 | |
KEYCODE_ENVELOPE | 65 | |
KEYCODE_ENTER | 66 | 回车键 |
KEYCODE_DEL | 67 | 退格键 |
KEYCODE_GRAVE | 68 | 按键 ` |
KEYCODE_MINUS | 69 | 按键- |
KEYCODE_EQUALS | 70 | 按键 = |
KEYCODE_LEFT_BRACKET | 71 | 按键 [ |
KEYCODE_RIGHT_BRACKET | 72 | 按键 ] |
KEYCODE_BACKSLASH | 73 | 按键 \ |
KEYCODE_SEMICOLON | 74 | 按键 , |
KEYCODE_APOSTROPHE | 75 | 按键 ''单引号 |
KEYCODE_SLASH | 76 | 按键 / |
KEYCODE_AT | 77 | 按键 @ |
KEYCODE_NUM | 78 | |
KEYCODE_HEADSETHOOK | 79 | |
KEYCODE_FOCUS | 80 | 拍照对焦键 |
KEYCODE_PLUS | 81 | 按键+ |
KEYCODE_MENU | 82 | 菜单键 |
KEYCODE_NOTIFICATION | 83 | 通知键 |
KEYCODE_SEARCH | 84 | |
KEYCODE_MEDIA_PLAY_PAUSE | 85 | 多媒体键 播放/暂停 |
KEYCODE_MEDIA_STOP | 86 | 多媒体键 暂停 |
KEYCODE_MEDIA_NEXT | 87 | 多媒体键 下一首 |
KEYCODE_MEDIA_PREVIOUS | 88 | 多媒体键 上一首 |
KEYCODE_MEDIA_REWIND | 89 | 多媒体键 快退 |
KEYCODE_MEDIA_FAST_FORWARD | 90 | 多媒体键 快进 |
KEYCODE_MUTE | 91 | 话筒静音键 |
KEYCODE_PAGE_UP | 92 | 向上翻页键 |
KEYCODE_PAGE_DOWN | 93 | 向下翻页键 |
KEYCODE_PICTSYMBOLS | 94 | |
KEYCODE_SWITCH_CHARSET | 95 | |
KEYCODE_BUTTON_A | 96 | |
KEYCODE_BUTTON_B | 97 | |
KEYCODE_BUTTON_C | 98 | |
KEYCODE_BUTTON_X | 99 | |
KEYCODE_BUTTON_Y | 100 | |
KEYCODE_BUTTON_Z | 101 | |
KEYCODE_BUTTON_L1 | 102 | |
KEYCODE_BUTTON_R1 | 103 | |
KEYCODE_BUTTON_L2 | 104 | |
KEYCODE_BUTTON_R2 | 105 | |
KEYCODE_BUTTON_THUMBL | 106 | |
KEYCODE_BUTTON_THUMBR | 107 | |
KEYCODE_BUTTON_START | 108 | |
KEYCODE_BUTTON_SELECT | 109 | |
KEYCODE_BUTTON_MODE | 110 | |
KEYCODE_ESCAPE | 111 | ESC键 |
KEYCODE_FORWARD_DEL | 112 | 删除键 |
KEYCODE_CTRL_LEFT | 113 | |
KEYCODE_CTRL_RIGHT | 114 | |
KEYCODE_CAPS_LOCK | 115 | 大写锁定键 |
KEYCODE_SCROLL_LOCK | 116 | |
KEYCODE_META_LEFT | 117 | |
KEYCODE_META_RIGHT | 118 | |
KEYCODE_FUNCTION | 119 | |
KEYCODE_SYSRQ | 120 | |
KEYCODE_BREAK | 121 | Break/Pause键 |
KEYCODE_MOVE_HOME | 122 | 光标移动到开始键 |
KEYCODE_MOVE_END | 123 | 光标移动到末尾键 |
KEYCODE_INSERT | 124 | |
KEYCODE_FORWARD | 125 | |
KEYCODE_MEDIA_PLAY | 126 | 多媒体键 播放 |
KEYCODE_MEDIA_PAUSE | 127 | 多媒体键 暂停 |
KEYCODE_MEDIA_CLOSE | 128 | 多媒体键 关闭 |
KEYCODE_MEDIA_EJECT | 129 | 多媒体键 弹出 |
KEYCODE_MEDIA_RECORD | 130 | 多媒体键 录音 |
KEYCODE_F1 | 131 | |
KEYCODE_F2 | 132 | |
KEYCODE_F3 | 133 | |
KEYCODE_F4 | 134 | |
KEYCODE_F5 | 135 | |
KEYCODE_F6 | 136 | |
KEYCODE_F7 | 137 | |
KEYCODE_F8 | 138 | |
KEYCODE_F9 | 139 | |
KEYCODE_F10 | 140 | |
KEYCODE_F11 | 141 | |
KEYCODE_F12 | 142 | |
KEYCODE_NUM_LOCK | 143 | 小键盘锁 |
KEYCODE_NUMPAD_0 | 144 | |
KEYCODE_NUMPAD_1 | 145 | |
KEYCODE_NUMPAD_2 | 146 | |
KEYCODE_NUMPAD_3 | 147 | |
KEYCODE_NUMPAD_4 | 148 | |
KEYCODE_NUMPAD_5 | 149 | |
KEYCODE_NUMPAD_6 | 150 | |
KEYCODE_NUMPAD_7 | 151 | |
KEYCODE_NUMPAD_8 | 152 | |
KEYCODE_NUMPAD_9 | 153 | |
KEYCODE_NUMPAD_DIVIDE | 154 | |
KEYCODE_NUMPAD_MULTIPLY | 155 | |
KEYCODE_NUMPAD_SUBTRACT | 156 | |
KEYCODE_NUMPAD_ADD | 157 | |
KEYCODE_NUMPAD_DOT | 158 | |
KEYCODE_NUMPAD_COMMA | 159 | |
KEYCODE_NUMPAD_ENTER | 160 | |
KEYCODE_NUMPAD_EQUALS | 161 | |
KEYCODE_NUMPAD_LEFT_PAREN | 162 | |
KEYCODE_NUMPAD_RIGHT_PAREN | 163 | |
KEYCODE_VOLUME_MUTE | 164 | 扬声器静音键 |
KEYCODE_INFO | 165 | |
KEYCODE_CHANNEL_UP | 166 | |
KEYCODE_CHANNEL_DOWN | 167 | |
KEYCODE_ZOOM_IN | 168 | 放大键 |
KEYCODE_ZOOM_OUT | 169 | 缩小键 |
KEYCODE_TV | 170 | |
KEYCODE_WINDOW | 171 | |
KEYCODE_GUIDE | 172 | |
KEYCODE_DVR | 173 | |
KEYCODE_BOOKMARK | 174 | |
KEYCODE_CAPTIONS | 175 | |
KEYCODE_SETTINGS | 176 | |
KEYCODE_TV_POWER | 177 | |
KEYCODE_TV_INPUT | 178 | |
KEYCODE_STB_POWER | 179 | |
KEYCODE_STB_INPUT | 180 | |
KEYCODE_AVR_POWER | 181 | |
KEYCODE_AVR_INPUT | 182 | |
KEYCODE_PROG_RED | 183 | |
KEYCODE_PROG_GREEN | 184 | |
KEYCODE_PROG_YELLOW | 185 | |
KEYCODE_PROG_BLUE | 186 | |
KEYCODE_APP_SWITCH | 187 | |
KEYCODE_BUTTON_1 | 188 | |
KEYCODE_BUTTON_2 | 189 | |
KEYCODE_BUTTON_3 | 190 | |
KEYCODE_BUTTON_4 | 191 | |
KEYCODE_BUTTON_5 | 192 | |
KEYCODE_BUTTON_6 | 193 | |
KEYCODE_BUTTON_7 | 194 | |
KEYCODE_BUTTON_8 | 195 | |
KEYCODE_BUTTON_9 | 196 | |
KEYCODE_BUTTON_10 | 197 | |
KEYCODE_BUTTON_11 | 198 | |
KEYCODE_BUTTON_12 | 199 | |
KEYCODE_BUTTON_13 | 200 | |
KEYCODE_BUTTON_14 | 201 | |
KEYCODE_BUTTON_15 | 202 | |
KEYCODE_BUTTON_16 | 203 | |
KEYCODE_LANGUAGE_SWITCH | 204 | |
KEYCODE_MANNER_MODE | 205 | |
KEYCODE_3D_MODE | 206 | |
KEYCODE_CONTACTS | 207 | |
KEYCODE_CALENDAR | 208 | |
KEYCODE_MUSIC | 209 | |
KEYCODE_CALCULATOR | 210 | |
KEYCODE_ZENKAKU_HANKAKU | 211 | |
KEYCODE_EISU | 212 | |
KEYCODE_MUHENKAN | 213 | |
KEYCODE_HENKAN | 214 | |
KEYCODE_KATAKANA_HIRAGANA | 215 | |
KEYCODE_YEN | 216 | |
KEYCODE_RO | 217 | |
KEYCODE_KANA | 218 | |
KEYCODE_ASSIST | 219 | |
KEYCODE_BRIGHTNESS_DOWN | 220 | |
KEYCODE_BRIGHTNESS_UP | 221 | |
KEYCODE_MEDIA_AUDIO_TRACK | 222 | |
KEYCODE_SLEEP | 223 | |
KEYCODE_WAKEUP | 224 | |
KEYCODE_PAIRING | 225 | |
KEYCODE_MEDIA_TOP_MENU | 226 | |
KEYCODE_11 | 227 | |
KEYCODE_12 | 228 | |
KEYCODE_LAST_CHANNEL | 229 | |
KEYCODE_TV_DATA_SERVICE | 230 | |
KEYCODE_VOICE_ASSIST | 231 | |
KEYCODE_TV_RADIO_SERVICE | 232 | |
KEYCODE_TV_TELETEXT | 233 | |
KEYCODE_TV_NUMBER_ENTRY | 234 | |
KEYCODE_TV_TERRESTRIAL_ANALOG | 235 | |
KEYCODE_TV_TERRESTRIAL_DIGITAL | 236 | |
KEYCODE_TV_SATELLITE | 237 | |
KEYCODE_TV_SATELLITE_BS | 238 | |
KEYCODE_TV_SATELLITE_CS | 239 | |
KEYCODE_TV_SATELLITE_SERVICE | 240 | |
KEYCODE_TV_NETWORK | 241 | |
KEYCODE_TV_ANTENNA_CABLE | 242 | |
KEYCODE_TV_INPUT_HDMI_1 | 243 | |
KEYCODE_TV_INPUT_HDMI_2 | 244 | |
KEYCODE_TV_INPUT_HDMI_3 | 245 | |
KEYCODE_TV_INPUT_HDMI_4 | 246 | |
KEYCODE_TV_INPUT_COMPOSITE_1 | 247 | |
KEYCODE_TV_INPUT_COMPOSITE_2 | 248 | |
KEYCODE_TV_INPUT_COMPONENT_1 | 249 | |
KEYCODE_TV_INPUT_COMPONENT_2 | 250 | |
KEYCODE_TV_INPUT_VGA_1 | 251 | |
KEYCODE_TV_AUDIO_DESCRIPTION | 252 | |
KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP | 253 | |
KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN | 254 | |
KEYCODE_TV_ZOOM_MODE | 255 | |
KEYCODE_TV_CONTENTS_MENU | 256 | |
KEYCODE_TV_MEDIA_CONTEXT_MENU | 257 | |
KEYCODE_TV_TIMER_PROGRAMMING | 258 | |
KEYCODE_HELP | 259 | |
KEYCODE_NAVIGATE_PREVIOUS | 260 | |
KEYCODE_NAVIGATE_NEXT | 261 | |
KEYCODE_NAVIGATE_IN | 262 | |
KEYCODE_NAVIGATE_OUT | 263 | |
KEYCODE_STEM_PRIMARY | 264 | |
KEYCODE_STEM_1 | 265 | |
KEYCODE_STEM_2 | 266 | |
KEYCODE_STEM_3 | 267 | |
KEYCODE_DPAD_UP_LEFT | 268 | |
KEYCODE_DPAD_DOWN_LEFT | 269 | |
KEYCODE_DPAD_UP_RIGHT | 270 | |
KEYCODE_DPAD_DOWN_RIGHT | 271 | |
KEYCODE_MEDIA_SKIP_FORWARD | 272 | |
KEYCODE_MEDIA_SKIP_BACKWARD | 273 | |
KEYCODE_MEDIA_STEP_FORWARD | 274 | |
KEYCODE_MEDIA_STEP_BACKWARD | 275 | |
KEYCODE_SOFT_SLEEP | 276 | |
KEYCODE_CUT | 277 | |
KEYCODE_COPY | 278 | |
KEYCODE_PASTE | 279 | |
KEYCODE_SYSTEM_NAVIGATION_UP | 280 | |
KEYCODE_SYSTEM_NAVIGATION_DOWN | 281 | |
KEYCODE_SYSTEM_NAVIGATION_LEFT | 282 | |
KEYCODE_SYSTEM_NAVIGATION_RIGHT | 283 |
以上就是Flutter TV Android端开发技巧详细教程的详细内容,更多关于Flutter TV Android端开发的资料请关注脚本之家其它相关文章!
相关文章
Android编程实现自动调整TextView字体大小以适应文字长度的方法
这篇文章主要介绍了Android编程实现自动调整TextView字体大小以适应文字长度的方法,涉及Android基于TextView类的继承及Paint属性操作实现字体大小自适应的相关技巧,需要的朋友可以参考下2016-01-01Android 中ListView和GridView赋值错位
这篇文章主要介绍了Android 中ListView和GridView赋值错位的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下2017-10-10
最新评论