[Flutter翻译]Flutter: 用`FocusableActionDetector`构建自定义控件

原文地址:blog.gskinner.com/archives/20…

原文作者:blog.gskinner.com/archives/au…

发布时间:2021年6月11日

当在Flutter中构建一个自定义的UI控件时,很有可能只是使用一个GestureDetector就结束了,但这样做将是一个错误!尤其是当您打算支持用户使用鼠标或键盘等其他输入方式时。尤其是当您打算支持用户使用鼠标或键盘等其他输入方式时。

事实证明,制作一个对各种输入都表现 “正确 “的UI控件要比仅仅检测敲击动作复杂得多。一般来说,你创建的每个控件都需要以下功能。

  • 悬停状态
  • 一个焦点状态(用于TAB键或箭头键的遍历)。
  • 一个可以改变鼠标光标的区域
  • 用于[空格]和[回车]键(或者其他键)的处理程序

传统上,你可以通过组成一个大的部件块来创建,包括Focus、Actions、Shortcuts和MouseRegion。这很有效,但有很多模板和缩进。幸运的是,Flutter为这个目的提供了一个专门的小部件。FocusableActionDetector

FocusableActionDetector将Focus、Actions、Shortcuts和MouseRegion融为一体,在Flutter SDK中被大量使用,包括Material DatePicker、Switch、Checkbox、Radio和Slider控件。

自己使用它是相当简单的。让我们来看看我们如何使用这个小部件建立一个完全自定义的按钮。

注意:要跟上代码,请查看这里的gist:gist.github.com/esDotDev/04…

MyCustomButton

首先,创建一个StatefulWidget,它可以保存你的_isFocused和_isHovered状态。我们还将创建一个FocusNode,我们可以用它来请求按下时的焦点,这是按钮的一个常见行为。还要创建一些典型的onPressed和标签字段来配置按钮。

class MyCustomButton extends StatefulWidget {
  @override
  State<MyCustomButton> createState() => _MyCustomButtonState();
}
class _MyCustomButtonState extends State<MyCustomButton> {
  bool _isHovered = false;
  bool _isFocused = false;
  FocusNode _focusNode = FocusNode();
  @override
  Widget build(BuildContext context) {
     // TODO:
  }
  void _handlePressed() {
    _focusNode.requestFocus();
    widget.onPressed();
  }
}
复制代码

因为我们正在制作一个按钮,所以我们将添加一些基本的配置选项。

  const MyCustomButton({Key? key, required this.onPressed, required this.label}) : super(key: key);
  final VoidCallback onPressed;
  final String label;
复制代码

现在剩下的就是填写build()方法了。

Widget build(BuildContext context) {
// Change visuals based on focus/hover state
Color outlineColor = _isFocused ? Colors.black : Colors.transparent;
Color bgColor = _isHovered ? Colors.blue.shade100 : Colors.white;
// Use `GestureDetector` to handle taps
return GestureDetector(
  onTap: _handlePressed,
  // Focus Actionable Detector
  child: FocusableActionDetector(
    focusNode: _focusNode,
    // Set mouse cursor
    mouseCursor: SystemMouseCursors.click,
    // Rebuild with hover/focus changes
    onShowFocusHighlight: (v) => setState(() => _isFocused = v),
    onShowHoverHighlight: (v) => setState(() => _isHovered = v),
    // Button Content
    child: Container(
      padding: const EdgeInsets.all(8),
      child: Text(widget.label),
      decoration: BoxDecoration(
        color: bgColor,
        border: Border.all(color: outlineColor, width: 1),
      ),
    ),
  ),
);
复制代码

在上面的代码中,注意你仍然需要包裹一个GestureDetector来检测轻触。此外,注意你如何根据悬停/焦点状态来改变你的ui的外观。

键盘动作

任何控件的最后一步是键盘绑定。默认情况下,大多数操作系统控件支持[Space]和[Enter]来 “提交”。你可以通过在.actions字段中连接内置的ActivateIntent来做到这一点。

FocusableActionDetector(
  // Hook up the built-in `ActivateIntent` to submit on [Enter] and [Space]
  actions: {
    ActivateIntent: CallbackAction<Intent>(onInvoke: (_) => _handlePressed()),
  },
  child: ...
),
复制代码

注意:你可以创建你自己的自定义动作和意图,但大多数时候,默认的ActivateIntent已经足够了。

你也可以使用.shortcuts参数添加额外的键绑定。这里我们将[Ctrl + X]绑定到同一个ActivateIntent上。现在,我们的控件将在[Enter]、[Space]和[Ctrl + X]时提交!

FocusableActionDetector(
  // Add 'Ctrl + X' key to the default [Enter] and [Space]
  shortcuts: {
    SingleActivator(LogicalKeyboardKey.keyX, control: true): ActivateIntent(),
  },
  child: ...
),
复制代码

有了这些,你就有了一个完成的控件,它在桌面、网页和手机上都能完全按照你的期望工作

blog.gskinner.com/wp-content/…

Tab键、回车键、空格键、轻击键都能如期工作!

下面是你的新自定义按钮的完整代码。

class MyCustomButton extends StatefulWidget {
  const MyCustomButton({Key? key, required this.onPressed, required this.label}) : super(key: key);
  final VoidCallback onPressed;
  final String label;
  @override
  State<MyCustomButton> createState() => _MyCustomButtonState();
}
class _MyCustomButtonState extends State<MyCustomButton> {
  bool _isHovered = false;
  bool _isFocused = false;
  FocusNode _focusNode = FocusNode();
  @override
  Widget build(BuildContext context) {
// Change visuals based on focus/hover state
    Color outlineColor = _isFocused ? Colors.black : Colors.transparent;
    Color bgColor = _isHovered ? Colors.blue.shade100 : Colors.white;
    return GestureDetector(
      onTap: _handlePressed,
      child: FocusableActionDetector(
        actions: {
          ActivateIntent: CallbackAction<Intent>(onInvoke: (_) => _handlePressed()),
        },
        shortcuts: {
          SingleActivator(LogicalKeyboardKey.keyX, control: true): ActivateIntent(),
        },
        child: Container(
          padding: const EdgeInsets.all(8),
          child: Text(widget.label),
          decoration: BoxDecoration(
            color: bgColor,
            border: Border.all(color: outlineColor, width: 1),
          ),
        ),
      ),
    );
  }
  void _handlePressed() {
    _focusNode.requestFocus();
    widget.onPressed();
  }
}
复制代码

要查看一个运行中的例子,以及一个自定义复选框的例子,请查看这里:gist.github.com/esDotDev/04…

更进一步…

虽然FocusableActionDetector处理了一些模板,但对于像按钮、复选框、开关等常见的用例来说,它仍然留下了很多东西。具体来说。

  • 添加GestureDetector
  • 添加ActivateIntent映射
  • 维护isHovered和isFocused状态
  • 管理提交时的焦点请求

在我们看来,所有这些常见的行为都可以用某种构建器来处理。所以我们继续做了一个

它被称为FocusableControlBuilder,在这里有一个软件包:pub.dev/packages/fo…

FocusableControlBuilder支持GestureDetector,管理FocusNode,为键盘支持添加了一个ActivateIntent,并提供了一个构建方法,可以访问control.isFocused和control.isHovered,以便在视图的构建中使用。呵!

对于大多数使用情况,这将消除大部分的模板。按钮、复选框、切换器、滑块等都可以以这个构建器为基础,用不同的手势或状态对其进行扩展。

如果我们转换前面的例子,它看起来像。

class MyCustomButton extends StatelessWidget {
  const MyCustomButton(this.label, {Key? key, required this.onPressed}) : super(key: key);
  final String label;
  final VoidCallback onPressed;
  @override
  Widget build(BuildContext context) {
    return FocusableControlBuilder(
        onPressed: onPressed,
        builder: (_, FocusableControlState control) {
          Color outlineColor = control.isFocused ? Colors.black : Colors.transparent;
          Color bgColor = control.isHovered ? Colors.blue.shade100 : Colors.grey.shade200;
          return Container(
            padding: const EdgeInsets.all(8),
            child: Text(label),
            decoration: BoxDecoration(
              color: bgColor,
              border: Border.all(color: outlineColor, width: 1),
            ),
          );
        });
  }
}
复制代码

不错啊! 我们希望你觉得这个包有用,如果你觉得有用,请在下面告诉我们


www.deepl.com 翻译

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享