如何立即切换焦点小组(通过键盘)?

How do I switch focus groups (via keyboard) immediately?

当从一个焦点组移动到下一个焦点组(使用键盘上的选项卡)时,我希望焦点移动到下一组中的第一个字段,但它似乎没有关注任何内容 - 然后另一个选项卡移动到那个组.

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          width: double.infinity,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              for (var i = 0; i < 2; i++)
                FocusableActionDetector(
                  onFocusChange: (focused) {
                    if (!focused) {
                      print('Have left focus group');
                    }
                  },
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      for (var i = 0; i < 3; i++)
                        SizedBox(
                          width: 150,
                          child: TextField(),
                        ),
                    ],
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

Dartpad example

我希望在第一组中的最后一个字段之后按 Tab 应该立即将焦点移动到下一个焦点组中的第一个项目。

我尝试了各种FocusNodeFocusScopeFocusScope.of(context).___,但是我发现 Flutter 中的焦点管理有点混乱。

前言:我不是 Focus 方面的专家,我自己也觉得它很复杂。

但我相信下面的内容可以满足您的需求,从一列遍历到另一列,而不关注“不可见”项目。至少在移动设备上。网络平台...这是一个完全不同的球赛(我怀疑它的工作原理是否相同)。

我摆脱了 FocusableActionDetector(它本身充当 FocusNode)并将每个列包装在 FocusTraversalGroup 中。我相信 Flutter 会尽可能地尝试从 TraversalGroup 转到 TraversalGroup。

包裹 TraversalGroups 的 FocusScope 可防止后退按钮和任何其他可点击项目获得焦点(一旦 FocusScope 获得焦点)。

class MyHomePage2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          width: double.infinity,
          child: FocusScope( // LIMIT FOCUS TO DESCENDANTS
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                for (var i = 0; i < 2; i++)
                  FocusTraversalGroup( // CREATE GROUPS HERE
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        for (var i = 0; i < 3; i++)
                          SizedBox(
                            width: 150,
                            child: TextField(),
                          ),
                      ],
                    ),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

总结

(据我所知,这是模糊的)

  • FocusScope限制节点遍历到它的直系后代节点
    • 焦点不会从一个FocusScope遍历到另一个FocusScope
    • 要转到另一个 FocusScope,用户需要 manually/click-focus 另一个 FocusScope,或其后代节点必须 requestFocus
    • 使用 FocusScope 而不是上面的 FocusTraversalGroup,会将节点遍历限制为一个 Column,以获得焦点为准。当到达最后一个 TextField
    • 时,它不会从一个 Column 跳到下一个
  • FocusTraversalGroup将后代收集到一个组中进行遍历
    • 但是 focus 离开这个组去另一个组
    • 在最后一个子节点和另一个 FocusTraversalGroup 可用时跳转
    • 可以调整其组内的遍历顺序

调试

使用 debugDumpFocusTree(随处可用的静态函数)转储国策树有助于调试。

我有时将它添加到 AppBar 以便于按需访问:

    return Scaffold(
      appBar: AppBar(
        title: Text('Focus Tab Page'),
        actions: [
          IconButton(icon: Icon(Icons.info_outline), onPressed: debugDumpFocusTree)
        ],
      ),

我已经复制到下面的相关部分。

└─Child 2: FocusScopeNode#c83f0(_ModalScopeState<dynamic> Focus Scope [IN FOCUS PATH])
  │ context: FocusScope
  │ IN FOCUS PATH
  │ focusedChildren: FocusScopeNode#7d536([IN FOCUS PATH])
  │
  ├─Child 1: FocusScopeNode#7d536([IN FOCUS PATH])
  │ │ context: FocusScope
  │ │ IN FOCUS PATH
  │ │ focusedChildren: FocusNode#1b886([PRIMARY FOCUS]),
  │ │   FocusNode#72c3b, FocusNode#34b25, FocusNode#3b410,
  │ │   FocusNode#02fac, FocusNode#61cd5
  │ │
  │ ├─Child 1: FocusNode#1d51e(FocusTraversalGroup)
  │ │ │ context: Focus
  │ │ │ NOT FOCUSABLE
  │ │ │
  │ │ ├─Child 1: FocusNode#3b410
  │ │ │   context: EditableText-[LabeledGlobalKey<EditableTextState>#70699]
  │ │ │
  │ │ ├─Child 2: FocusNode#34b25
  │ │ │   context: EditableText-[LabeledGlobalKey<EditableTextState>#7c822]
  │ │ │
  │ │ └─Child 3: FocusNode#72c3b
  │ │     context: EditableText-[LabeledGlobalKey<EditableTextState>#f00bc]
  │ │
  │ └─Child 2: FocusNode#bdb17(FocusTraversalGroup [IN FOCUS PATH])
  │   │ context: Focus
  │   │ NOT FOCUSABLE
  │   │ IN FOCUS PATH
  │   │
  │   ├─Child 1: FocusNode#1b886([PRIMARY FOCUS])
  │   │   context: EditableText-[LabeledGlobalKey<EditableTextState>#c5a56]
  │   │   PRIMARY FOCUS
  │   │
  │   ├─Child 2: FocusNode#61cd5
  │   │   context: EditableText-[LabeledGlobalKey<EditableTextState>#a4dd8]
  │   │
  │   └─Child 3: FocusNode#02fac
  │       context: EditableText-[LabeledGlobalKey<EditableTextState>#fe12d]
  │
  ├─Child 2: FocusNode#4886e
  │   context: Focus
  │
  └─Child 3: FocusNode#9241b
      context: Focus