用户输入与辅助功能
仅仅调整应用的外观是不够的,您还需要支持各种用户输入。鼠标和键盘引入了触控设备上不存在的输入类型,例如滚轮、右键单击、悬停交互、Tab 遍历和键盘快捷键。
其中一些功能在 Material Widget 上默认可用。但是,如果您创建了自定义 Widget,则可能需要直接实现它们。
一些包含精心设计的应用的功能,也帮助了使用辅助技术的用户。例如,除了是**良好的应用设计**之外,一些功能(如 Tab 遍历和键盘快捷键)对于**使用辅助设备的用户至关重要**。除了有关创建无障碍应用的标准建议外,此页面还介绍了有关创建既自适应又无障碍的应用的信息。
自定义 Widget 的滚轮
#像ScrollView
或ListView
这样的滚动 Widget 默认支持滚轮,并且由于几乎每个可滚动的自定义 Widget 都是使用其中之一构建的,因此它也适用于这些 Widget。
如果您需要实现自定义滚动行为,可以使用Listener
Widget,它允许您自定义 UI 对滚轮的反应方式。
return Listener(
onPointerSignal: (event) {
if (event is PointerScrollEvent) print(event.scrollDelta.dy);
},
child: ListView(),
);
Tab 遍历和焦点交互
#使用物理键盘的用户期望他们可以使用 Tab 键快速导航应用程序,而有运动或视觉障碍的用户通常完全依赖键盘导航。
Tab 交互有两个方面需要考虑:焦点如何在 Widget 之间移动,称为遍历,以及 Widget 获得焦点时显示的视觉高亮。
大多数内置组件(如按钮和文本字段)默认支持遍历和高亮。如果您有自己的 Widget 希望包含在遍历中,可以使用FocusableActionDetector
Widget 创建自己的控件。FocusableActionDetector
Widget 有助于在一个 Widget 中组合焦点、鼠标输入和快捷键。您可以创建一个定义操作和键绑定的检测器,并提供处理焦点和悬停高亮的回调。
class _BasicActionDetectorState extends State<BasicActionDetector> {
bool _hasFocus = false;
@override
Widget build(BuildContext context) {
return FocusableActionDetector(
onFocusChange: (value) => setState(() => _hasFocus = value),
actions: <Type, Action<Intent>>{
ActivateIntent: CallbackAction<Intent>(onInvoke: (intent) {
print('Enter or Space was pressed!');
return null;
}),
},
child: Stack(
clipBehavior: Clip.none,
children: [
const FlutterLogo(size: 100),
// Position focus in the negative margin for a cool effect
if (_hasFocus)
Positioned(
left: -4,
top: -4,
bottom: -4,
right: -4,
child: _roundedBorder(),
)
],
),
);
}
}
控制遍历顺序
#要更好地控制用户按 Tab 键遍历 Widget 时焦点的顺序,可以使用FocusTraversalGroup
定义应在按 Tab 键时被视为一组的树的部分。
例如,您可能希望在按 Tab 键到提交按钮之前,先遍历表单中的所有字段。
return Column(children: [
FocusTraversalGroup(
child: MyFormWithMultipleColumnsAndRows(),
),
SubmitButton(),
]);
Flutter 有几种内置方式来遍历 Widget 和组,默认为ReadingOrderTraversalPolicy
类。此类通常效果很好,但可以使用另一个预定义的TraversalPolicy
类或通过创建自定义策略来修改它。
键盘快捷键
#除了 Tab 遍历之外,桌面和 Web 用户习惯于将各种键盘快捷键绑定到特定操作。无论是用于快速删除的Delete
键,还是用于新建文档的Control+N
,请务必考虑用户期望的不同加速键。键盘是一个强大的输入工具,因此请尽可能地提高其效率。您的用户会感谢您的!
键盘快捷键可以通过 Flutter 中的几种方法实现,具体取决于您的目标。
如果您有一个像TextField
或Button
这样的单个 Widget 并且已经有一个焦点节点,您可以将其包装在KeyboardListener
或Focus
Widget 中,并监听键盘事件。
@override
Widget build(BuildContext context) {
return Focus(
onKeyEvent: (node, event) {
if (event is KeyDownEvent) {
print(event.logicalKey);
}
return KeyEventResult.ignored;
},
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: const TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
),
);
}
}
要将一组键盘快捷键应用于树的大部分区域,请使用Shortcuts
Widget。
// Define a class for each type of shortcut action you want
class CreateNewItemIntent extends Intent {
const CreateNewItemIntent();
}
Widget build(BuildContext context) {
return Shortcuts(
// Bind intents to key combinations
shortcuts: const <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.keyN, control: true):
CreateNewItemIntent(),
},
child: Actions(
// Bind intents to an actual method in your code
actions: <Type, Action<Intent>>{
CreateNewItemIntent: CallbackAction<CreateNewItemIntent>(
onInvoke: (intent) => _createNewItem(),
),
},
// Your sub-tree must be wrapped in a focusNode, so it can take focus.
child: Focus(
autofocus: true,
child: Container(),
),
),
);
}
Shortcuts
Widget 很有用,因为它只允许在该 Widget 树或其子级拥有焦点且可见时触发快捷键。
最后一个选项是全局监听器。此监听器可用于始终开启的、应用范围的快捷键,或用于无论何时可见(无论其焦点状态如何)都可以接受快捷键的面板。使用HardwareKeyboard
轻松添加全局监听器。
@override
void initState() {
super.initState();
HardwareKeyboard.instance.addHandler(_handleKey);
}
@override
void dispose() {
HardwareKeyboard.instance.removeHandler(_handleKey);
super.dispose();
}
要使用全局监听器检查键组合,可以使用HardwareKeyboard.instance.logicalKeysPressed
集合。例如,以下方法可以检查是否按下了任何提供的键。
static bool isKeyDown(Set<LogicalKeyboardKey> keys) {
return keys
.intersection(HardwareKeyboard.instance.logicalKeysPressed)
.isNotEmpty;
}
将这两件事放在一起,您可以在按下Shift+N
时触发一个操作。
bool _handleKey(KeyEvent event) {
bool isShiftDown = isKeyDown({
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
});
if (isShiftDown && event.logicalKey == LogicalKeyboardKey.keyN) {
_createNewItem();
return true;
}
return false;
}
使用静态监听器时需要注意的一点是,您通常需要在用户在字段中键入或与其关联的 Widget 隐藏在视图之外时禁用它。与Shortcuts
或KeyboardListener
不同,这需要您自行管理。当您为Delete
绑定 Delete/Backspace 加速键,但随后有用户可能在其中键入的子TextFields
时,这一点尤其重要。
自定义 Widget 的鼠标进入、离开和悬停
#在桌面端,通常会更改鼠标光标以指示鼠标悬停内容的功能。例如,当您将鼠标悬停在按钮上时,通常会看到一个手形光标,或者当您将鼠标悬停在文本上时,会看到一个I
形光标。
Flutter 的 Material 按钮处理标准按钮和文本光标的基本焦点状态。(一个值得注意的例外是,如果您更改 Material 按钮的默认样式以将overlayColor
设置为透明。)
为应用中的任何自定义按钮或手势检测器实现焦点状态。如果您更改了默认的 Material 按钮样式,请测试键盘焦点状态,并在需要时实现自己的焦点状态。
要从自定义 Widget 中更改光标,请使用MouseRegion
// Show hand cursor
return MouseRegion(
cursor: SystemMouseCursors.click,
// Request focus when clicked
child: GestureDetector(
onTap: () {
Focus.of(context).requestFocus();
_submit();
},
child: Logo(showBorder: hasFocus),
),
);
MouseRegion
也可用于创建自定义翻转和悬停效果。
return MouseRegion(
onEnter: (_) => setState(() => _isMouseOver = true),
onExit: (_) => setState(() => _isMouseOver = false),
onHover: (e) => print(e.localPosition),
child: Container(
height: 500,
color: _isMouseOver ? Colors.blue : Colors.black,
),
);
有关将按钮样式更改为在获得焦点时概述按钮的示例,请查看Wonderous 应用的按钮代码。该应用修改了FocusNode.hasFocus
属性以检查按钮是否获得焦点,如果获得焦点,则添加一个轮廓。
视觉密度
#您可能会考虑扩大 Widget 的“点击区域”以适应触摸屏,例如。
不同的输入设备提供不同级别的精度,这需要不同大小的点击区域。Flutter 的VisualDensity
类使您可以轻松地调整整个应用程序中视图的密度,例如,通过在触摸设备上使按钮更大(因此更容易点击)。
当您更改MaterialApp
的VisualDensity
时,支持它的MaterialComponents
会将其密度动画化以匹配。默认情况下,水平和垂直密度均设置为 0.0,但您可以将密度设置为所需的任何负值或正值。通过在不同密度之间切换,您可以轻松调整 UI。
要设置自定义视觉密度,请将密度注入到您的MaterialApp
主题中。
double densityAmt = touchMode ? 0.0 : -1.0;
VisualDensity density =
VisualDensity(horizontal: densityAmt, vertical: densityAmt);
return MaterialApp(
theme: ThemeData(visualDensity: density),
home: MainAppScaffold(),
debugShowCheckedModeBanner: false,
);
要在您自己的视图中使用VisualDensity
,您可以查找它。
VisualDensity density = Theme.of(context).visualDensity;
容器不仅会自动响应密度的变化,而且在变化时也会进行动画处理。这将您的自定义组件与内置组件结合在一起,从而在整个应用程序中实现平滑的过渡效果。
如所示,VisualDensity
是无单位的,因此对于不同的视图来说可能意味着不同的东西。在以下示例中,1 个密度单位等于 6 个像素,但这完全由您决定。它是无单位的事实使其非常通用,并且应该在大多数情况下都能正常工作。
值得注意的是,Material 通常为每个视觉密度单位使用大约 4 个逻辑像素的值。有关受支持组件的更多信息,请参阅VisualDensity
API。有关一般密度原理的更多信息,请参阅Material Design 指南。
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面最后更新于 2024-05-13。 查看源代码 或 报告问题。