这是一个长期悬而未决的问题,但我们开始吧:
迄今为止,Android TV 支持有点不稳定。但是有一些更简单(也有一些不太容易)的解决方法可以帮助我们。
基本上从阅读这篇 Medium 文章开始:
Adding Android TV support to your Flutter App
要做的第一件事是添加对电视遥控器 Dpad 的支持,方法是将您的应用程序包装在 Shortcut 小部件中:
return Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select): ActivateIntent(),
},
child: MaterialApp(
...
);
这应该将select 事件从键盘转发到activate 意图。 ?️
到目前为止,您所有可选择的东西(按钮等)都应该可以使用。不过TextFormFields 可能还是不会。
那就去 GitHub 上查看这个 Flutter 问题:Focus gets lost on Android TV #49783
嗯...我不知道。这是一个冗长的问题,除了基于RawKeyboardListener 的方法外,我在那里找不到简单的解决方案。这是我(无意中)采取的方式。
当我几天前开始这项工作时,我不知道 Shortcut 方法,所以我在 pub.dev focusnode_widgets 上查看了这个包。我检查了代码并了解到,这样做的人手头有一个非常具体的问题。所以我剥离了他的例子并重新实现了他的课程_FocusNodeEnterTapActionableWidget(他似乎真的很喜欢looooooooooong类名......)。
注意:我不会在此处粘贴此代码,因为没有太大区别,但是我所做的是,我创建了一个包装小部件,它创建了所需的小部件并且能够处理所有必要的 dpad 事件 - 包括走过 TextFormFields 的能力。
在以下示例中,_FocusableWidget 是 _FocusNodeEnterTapActionableWidget 的替代品。
/// get the type from a generic parameter
Type typeOf<T>() => T;
/// if you have large an scrollable pages with large gaps in between of focusable widgets
/// you need something to go to...
/// I used this to navigate through a help/about page
class FocusableAnchor extends StatelessWidget {
@override
Widget build(BuildContext context) => _FocusableWidget(
autoFocus: false,
handleEnterTapAction: (ctx) {},
child: Container(),
focusedBackgroundColor: Colors.transparent,
nonFocusedBackgroundColor: Colors.transparent,
);
}
/// there's another class I will leave out - it's just a wrapper for a given child
/// but I use a custom Android plugin to determine, whether I'm on a TV or not
/// TV: wrap the child widget, No TV: just use the child widget as-is.
/// this is the base wrapper, that is quite large, because it basically has all
/// (some renamed) parameters, needed to create the following widgets:
/// IconButton, FloatingActionButton, MaterialButton, FlatButton, RaisedButton
/// DropdownButton (doesn't really work though, I don't understand why), ListTile
/// CheckboxListTile, RadioListTile (these both are shaky as well)
/// Switch, ActionChip, TextFormField
class FocusableValue<T extends Widget, U> extends StatelessWidget {
FocusableValue({
Key key,
// the key of the widget to create
this.widgetKey,
// IconButton, FloatingActionButton
this.icon, // DropdownButton
this.onPressed,
this.onLongPress,
this.tooltip,
// ... many more
// explicitly set widget
this.widget,
this.colorFocused = true,
}) : super(key: key);
final Key widgetKey;
final Widget icon;
final void Function() onPressed;
final void Function() onLongPress;
final String tooltip;
final T widget;
final bool colorFocused;
// a former colleague decided to use Kiwi for DI...
final _uiMode = inject<UiModePlugin>();
@override
Widget build(BuildContext context) => FutureBuilder(
future: _uiMode.getUiModeType(),
initialData: null,
builder: (ctx, snap) {
if (!snap.hasData || snap.hasError || snap.data == null) {
return Container();
}
var widget = this.widget;
Function(BuildContext) onAction;
Color focusBgColor;
if (widget == null) {
final type = typeOf<T>();
switch (type) {
case IconButton:
widget = IconButton(
key: widgetKey,
icon: icon,
onPressed: onPressed,
tooltip: tooltip,
// ignore: avoid_as
) as T;
onAction = (ctx) => onPressed();
focusBgColor = colorAltDarkTransparent;
break;
// basically create your target here, e.g. TextFormField
// TextFormField doesn't need to set the onAction function, though
}
}
Decoration _decorate(Color color) {
if (color == null) return null;
return BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(3.0),
boxShadow: kElevationToShadow[1],
);
}
EdgeInsetsGeometry _padding(bool apply) {
if (!apply) return null;
return EdgeInsets.only(
top: 5.0,
right: 5.0,
bottom: 5.0,
left: 5.0,
);
}
BoxConstraints _constraints(bool apply) {
if (!apply) return null;
return BoxConstraints.tightFor(height: 37.0);
}
return UIMode.tv == snap.data
? _FocusableWidget(
autoFocus: false,
handleEnterTapAction: onAction ?? (ctx) {},
child: widget,
focusedBackgroundColor: null,
nonFocusedBackgroundColor: null,
focusedBackgroundDecoration:
colorFocused ? _decorate(focusBgColor) : null,
nonFocusedBackgroundDecoration: null,
padding: colorFocused ? _padding(focusBgColor != null) : null,
constraints:
colorFocused ? _constraints(focusBgColor != null) : null,
)
: widget;
},
);
}
/// for the most widgets, except for DropdownButton, RadioListTile I can use this wrapper
class Focusable<T extends Widget> extends FocusableValue<T, dynamic> {
Focusable({
Key key,
Key widgetKey,
Widget icon,
Function() onPressed,
Function() onLongPress,
String tooltip,
// more parameter to create other widgets
T widget,
bool colorFocused = true,
}) : super(
key: key,
widgetKey: widgetKey,
icon: icon,
onPressed: onPressed,
onLongPress: onLongPress,
tooltip: tooltip,
// pass all the other parameters here
widget: widget,
colorFocused: colorFocused,
);
}
这很乏味,但在我的大多数情况下它都有效。我只需要弄清楚CheckboxListTile 和RadioListTile - 它们在我的(非常重的)自定义对话框中不起作用。
希望对你有所帮助。