携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
FocusWidget
顾名思义,它管理焦点节点。它是 Flutter 焦点系统中最重要和功能最强大的部分。它管理其拥有的焦点节点与焦点树的附加和分离,管理焦点节点的属性和回调,并具有静态函数以启用附加到小部件树的焦点节点的发现。
这意味着Focus小部件是包含您想要关注的树中所有子项的小部件。当在传递给它的FocusNode上调用 requestFocus方法时,tab 遍历完成,子树可以获得焦点。传递FocusNode是可选的,如果我们传递它,我们会获得额外的功能,如果我们不传递,那么Focus小部件会创建自己的。FocusNode的大部分功能最好通过更改Focus小部件的属性来访问。
以下示例显示了我们如何创建自定义的可聚焦小部件。容器中有一个对焦点事件做出反应的文本。
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Focus Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[MyCustomWidget(), MyCustomWidget()],
),
),
);
}
}
class MyCustomWidget extends StatefulWidget {
const MyCustomWidget({Key? key}) : super(key: key);@override
State<MyCustomWidget> createState() => _MyCustomWidgetState();
}
class _MyCustomWidgetState extends State<MyCustomWidget> {
Color _color = Colors.white;
String _label = 'Unfocused';
@override
Widget build(BuildContext context) {
return Focus(
onFocusChange: (bool focused) {
setState(() {
_color = focused ? Colors.black26 : Colors.white;
_label = focused ? 'Focused' : 'Unfocused';
});
},
child: Center(
child: Container(
width: 300,
height: 50,
alignment: Alignment.center,
color: _color,
child: Text(_label),
),
),
);
}
}
FocusWidget 键事件
可以使用Focus小部件的 onKey属性来监听和处理键事件。键事件从具有主要焦点的焦点节点开始。如果该节点未从该属性回调返回KeyEventResult.handled,则为其父焦点节点提供事件,依此类推,直到遇到焦点 树的根。 以下是Focus 小部件,它吸收其子树未处理的每个键,但不能成为主要焦点。
Widget build(BuildContext context) {
return Focus(
onKey: (FocusNode node, RawKeyEvent event) => KeyEventResult.handled,
canRequestFocus: false,
child: child,
);
}
Focus键事件在文本输入事件之前处理,因此当Focus小部件围绕文本字段时处理键事件会阻止该键被输入到文本字段中。
下面是一个不允许在文本字段中输入字母a的小部件示例。
Widget build(BuildContext context) {
return Focus(
onKey: (FocusNode node, RawKeyEvent event) {
return (event.logicalKey == LogicalKeyboardKey.keyA) ? KeyEventResult.handled : KeyEventResult.ignored;
},
child: TextField(),
);
}
如果我们要做的是输入验证,那么可以使用TextInputFormatter可能会更好地实现此示例的功能。当然该方法仍然很有用,例如,Shortcuts小部件使用此方法在快捷方式成为文本输入之前对其进行处理。
控制 FocusWidget 的焦点
以下方法用于控制此节点及其后代如何参与Focus 小部件的焦点过程。
- 如果该
skipTraversal属性为真,则该焦点节点不参与焦点遍历。如果在其焦点节点上调用它仍然是可requestFocus聚焦的,但是当焦点遍历系统正在寻找下一个要关注的事物时会被跳过。 - 不出所料,该
canRequestFocus属性控制此小部件管理的焦点节点是否Focus可用于请求焦点。如果此属性为 false,则requestFocus在节点上调用无效。这也意味着该节点被跳过以进行焦点遍历,因为它无法请求焦点。 - 该
descendantsAreFocusable属性控制该节点的后代是否可以接收焦点,但仍允许该节点接收焦点。此属性可用于关闭整个小部件子树的可聚焦性。这就是ExcludeFocus小部件的工作方式:它只是Focus具有此属性集的小部件。
FocusWidget 中的自动对焦
顾名思义,当我们想要在焦点树中自动聚焦 UI 元素时,此属性很有用。建议我们只给一个孩子在一个焦点树中自动对焦。
更改通知焦点小部件
onFocusChanged回调用于监听焦点更改,即使更改是用于添加或删除小部件以及焦点更改。
获取焦点节点
您可以通过不同的方法获取FocusNode 。
- 您可以将其作为属性传递给Focus小部件,同时从祖先那里获取它。
- 您可以使用Focus.of(context) 访问它,同时从后代获得它。
- 当需要从当前上下文中获取它时,我们可以使用Builder方法。 以下示例显示了此方法。
Widget build(BuildContext context) {
return Focus(
child: Builder(
builder: (BuildContext context) {
final bool hasPrimary = Focus.of(context).hasPrimaryFocus;
print('Building with primary focus: $hasPrimary');
return const SizedBox(width: 100, height: 100);
},),
);
}
定时
请求焦点时,仅在当前构建阶段完成后才会生效。这意味着焦点更改总是延迟一帧,因为更改焦点会导致小部件树的任意部分重建,包括当前请求焦点的小部件的祖先。因为后代不能弄脏他们的祖先,所以它必须在帧之间发生,以便任何需要的更改都可以在下一帧发生。