【问题标题】:Shift+Tab and arrow (also D-PAD) keys don't work for focus traversal of TextFormFieldShift+Tab 和箭头(也是 D-PAD)键不适用于 TextFormField 的焦点遍历
【发布时间】:2020-01-23 10:58:03
【问题描述】:

我正在开发一款适用于 Android 移动设备和 Android 电视的应用。该应用程序在登录和注册表单中有问题焦点字段。这是最小的可重现示例:

import 'package:flutter/material.dart';

void main() => runApp(TestApp());

class TestApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Test App'),
        ),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(8.0),
          child: TestWidget(),
        ),
      ),
    );
  }
}

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: List<Widget>.generate(5, (int index) => TextFormField(
        decoration: InputDecoration(
          labelText: 'Field ${index + 1}',
        ),
      )),
    );
  }
}

使用Tab 键将焦点移至下一个字段,但按下Shift+Tab 时前一个字段未获得焦点。此外,箭头键和 Android TV 遥控器 D-PAD 键都不适用于 TextFormField 的焦点遍历,但适用于其他可聚焦的小部件。

【问题讨论】:

    标签: flutter dart focus form-fields


    【解决方案1】:

    这是一个长期悬而未决的问题,但我们开始吧:

    迄今为止,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,
            );
    }
    

    这很乏味,但在我的大多数情况下它都有效。我只需要弄清楚CheckboxListTileRadioListTile - 它们在我的(非常重的)自定义对话框中不起作用。

    希望对你有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多