Flutter Focus 关注多个节点而不是单个节点
Flutter Focus is focussing multiple nodes instead of a single node
我正在构建一个将由方向键键盘事件控制的应用程序。所以我研究了 flutter Focus 系统。我想给它特别的指示,因为在我的应用程序中我想使用多个“控制区”,在我的示例中我添加了 2 个区域,A 和 B。
此时,当我按下左箭头时,它会聚焦在左侧 (A) 区域。当我按下右箭头时,它会聚焦右 (B) 区域。但是是集中在“区域”中的所有按钮吗?
实际行为:
预期的行为是切换区域并使用 left/right 键聚焦一个按钮。使用 up/down 键,它应该在活动区域内的焦点内切换按钮。
这是我的代码:
class FocusTest extends StatefulWidget {
const FocusTest({Key? key}) : super(key: key);
@override
_FocusTestState createState() => _FocusTestState();
}
class _FocusTestState extends State<FocusTest> {
static bool _aIsSelected = true;
final FocusScopeNode _focusScopeNodeA = FocusScopeNode();
static bool _bIsSelected = false;
final FocusScopeNode _focusScopeNodeB = FocusScopeNode();
@override
void initState() {
super.initState();
RawKeyboard.instance.addListener(_handleDpad);
}
@override
void dispose() {
_focusScopeNodeA.dispose();
_focusScopeNodeB.dispose();
RawKeyboard.instance.removeListener(_handleDpad);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Focus(
child: Column(
children: [
TextButton(
onPressed: () { print("a1");},
focusNode: _focusScopeNodeA,
autofocus: true,
child: const Text("a 1"),
),
TextButton(
onPressed: () { print("a2");},
focusNode: _focusScopeNodeA,
autofocus: false,
child: const Text("a 2"),
),
TextButton(
onPressed: () { print("a3");},
focusNode: _focusScopeNodeA,
autofocus: false,
child: const Text("a 3"),
),
],
),
),
),
),
Expanded(
flex: 10,
child: Container()),
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Column(
children: [
TextButton(
onPressed: () { print("b1");},
focusNode: _focusScopeNodeB,
autofocus: false,
child: const Text("b 1"),
),
TextButton(
onPressed: () { print("b2");},
focusNode: _focusScopeNodeB,
autofocus: false,
child: const Text("b 2"),
),
TextButton(
onPressed: () { print("b3"); },
focusNode: _focusScopeNodeB,
autofocus: false,
child: const Text("b 3"),
),
],
),
),
),
],
),
),
);
}
KeyEventResult _handleDpad(RawKeyEvent event) {
//debugDumpFocusTree();
if (event.runtimeType == RawKeyUpEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
if (kDebugMode) {
print("left button");
}
_aIsSelected = true;
_bIsSelected = false;
_focusScopeNodeA.requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
if (kDebugMode) {
print("right button");
}
_aIsSelected = false;
_bIsSelected = true;
_focusScopeNodeB.requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (kDebugMode) {
print("down button");
}
if (_aIsSelected) {
_focusScopeNodeA.nextFocus();
}
if (_bIsSelected) {
_focusScopeNodeB.nextFocus();
}
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
if (kDebugMode) {
print("up button");
}
if (_aIsSelected) {
_focusScopeNodeA.previousFocus();
}
if (_bIsSelected) {
_focusScopeNodeB.previousFocus();
}
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
或查看:
https://gist.github.com/dixi83/2b2d0a63fe465baa09210be9d34fd194
我也尝试了 unfocus() 方法,但因为它没有任何区别,所以我在示例中将其保留。
更新 1:
discord 上的某个人参加了我对 3 个按钮使用相同的 FocusNode 的讨论。他是对的,但奇怪的是在我发布这个问题之前,我有一个包含 2 个焦点节点列表的版本,我尝试用指针处理焦点。这个版本向我展示了完全相同的行为...这是源代码:
class FocusDemo extends StatefulWidget {
const FocusDemo({Key? key}) : super(key: key);
@override
_FocusDemoState createState() => _FocusDemoState();
}
class _FocusDemoState extends State<FocusDemo> {
static const int _nrOfNodesA = 3;
static int _focusPointerA = 0;
static bool _aIsSelected = true;
final List<FocusNode> _focusNodesA = List.filled(_nrOfNodesA, FocusNode());
static const int _nrOfNodesB = 3;
static int _focusPointerB = 0;
static bool _bIsSelected = false;
final List<FocusNode> _focusNodesB = List.filled(_nrOfNodesB, FocusNode());
@override
void initState() {
super.initState();
_focusNodesA[0].hasPrimaryFocus;
_focusNodesA[0].requestFocus();
RawKeyboard.instance.addListener(_handleDpad);
}
@override
void dispose() {
for(int i=0; i < _nrOfNodesA; i++){
_focusNodesA[i].dispose();
}
for(int i=0; i < _nrOfNodesB; i++){
_focusNodesB[i].dispose();
}
RawKeyboard.instance.removeListener(_handleDpad);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Focus(
child: Column(
children: [
TextButton(
onPressed: () { print("a1");},
focusNode: _focusNodesA[0],
autofocus: true,
child: const Text("a 1"),
),
TextButton(
onPressed: () { print("a2");},
focusNode: _focusNodesA[1],
autofocus: false,
child: const Text("a 2"),
),
TextButton(
onPressed: () { print("a3");},
focusNode: _focusNodesA[2],
autofocus: false,
child: const Text("a 3"),
),
],
),
),
),
),
Expanded(
flex: 10,
child: Container()),
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Column(
children: [
TextButton(
onPressed: () { print("b1");},
focusNode: _focusNodesB[0],
autofocus: false,
child: const Text("b 1"),
),
TextButton(
onPressed: () { print("b2");},
focusNode: _focusNodesB[1],
autofocus: false,
child: const Text("b 2"),
),
TextButton(
onPressed: () { print("b3"); },
focusNode: _focusNodesB[2],
autofocus: false,
child: const Text("b 3"),
),
],
),
),
),
],
),
),
);
}
KeyEventResult _handleDpad(RawKeyEvent event) {
//debugDumpFocusTree();
if (event.runtimeType == RawKeyUpEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
if (kDebugMode) {
print("left button");
}
_aIsSelected = true;
_bIsSelected = false;
_focusPointerA = 0;
_focusPointerB = 0;
_focusNodesA[0].requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
if (kDebugMode) {
print("right button");
}
_aIsSelected = false;
_bIsSelected = true;
_focusPointerA = 0;
_focusPointerB = 0;
_focusNodesB[0].requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (kDebugMode) {
print("down button");
}
_focusPointerA--;
_focusPointerB--;
if (_focusPointerA < 0) {
_focusPointerA = 0;
}
if (_focusPointerB < 0) {
_focusPointerB = 0;
}
if (_aIsSelected) {
_focusNodesA[_focusPointerA].requestFocus();
}
if (_bIsSelected) {
_focusNodesB[_focusPointerB].requestFocus();
}
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
if (kDebugMode) {
print("up button");
}
_focusPointerA++;
_focusPointerB++;
if (_focusPointerA >= _nrOfNodesA) {
_focusPointerA = _nrOfNodesA-1;
}
if (_focusPointerB >= _nrOfNodesB) {
_focusPointerB = _nrOfNodesA-1;
}
if (_aIsSelected) {
_focusNodesA[_focusPointerA].requestFocus();
}
if (_bIsSelected) {
_focusNodesB[_focusPointerB].requestFocus();
}
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
}
或查看:
https://gist.github.com/dixi83/f653022ad1bdb1d41dcfcf0f1d278b8b
我的第一次尝试(见更新 1)似乎比我想象的更接近解决方案......我的错误是我使用 List.filled()
而不是 List.generate()
区别? .
完美地解释了这一点
final List<FocusNode> _focusNodeList = List.generate(_nrOfNodes, (_) => FocusNode());
我正在构建一个将由方向键键盘事件控制的应用程序。所以我研究了 flutter Focus 系统。我想给它特别的指示,因为在我的应用程序中我想使用多个“控制区”,在我的示例中我添加了 2 个区域,A 和 B。
此时,当我按下左箭头时,它会聚焦在左侧 (A) 区域。当我按下右箭头时,它会聚焦右 (B) 区域。但是是集中在“区域”中的所有按钮吗?
实际行为:
预期的行为是切换区域并使用 left/right 键聚焦一个按钮。使用 up/down 键,它应该在活动区域内的焦点内切换按钮。
这是我的代码:
class FocusTest extends StatefulWidget {
const FocusTest({Key? key}) : super(key: key);
@override
_FocusTestState createState() => _FocusTestState();
}
class _FocusTestState extends State<FocusTest> {
static bool _aIsSelected = true;
final FocusScopeNode _focusScopeNodeA = FocusScopeNode();
static bool _bIsSelected = false;
final FocusScopeNode _focusScopeNodeB = FocusScopeNode();
@override
void initState() {
super.initState();
RawKeyboard.instance.addListener(_handleDpad);
}
@override
void dispose() {
_focusScopeNodeA.dispose();
_focusScopeNodeB.dispose();
RawKeyboard.instance.removeListener(_handleDpad);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Focus(
child: Column(
children: [
TextButton(
onPressed: () { print("a1");},
focusNode: _focusScopeNodeA,
autofocus: true,
child: const Text("a 1"),
),
TextButton(
onPressed: () { print("a2");},
focusNode: _focusScopeNodeA,
autofocus: false,
child: const Text("a 2"),
),
TextButton(
onPressed: () { print("a3");},
focusNode: _focusScopeNodeA,
autofocus: false,
child: const Text("a 3"),
),
],
),
),
),
),
Expanded(
flex: 10,
child: Container()),
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Column(
children: [
TextButton(
onPressed: () { print("b1");},
focusNode: _focusScopeNodeB,
autofocus: false,
child: const Text("b 1"),
),
TextButton(
onPressed: () { print("b2");},
focusNode: _focusScopeNodeB,
autofocus: false,
child: const Text("b 2"),
),
TextButton(
onPressed: () { print("b3"); },
focusNode: _focusScopeNodeB,
autofocus: false,
child: const Text("b 3"),
),
],
),
),
),
],
),
),
);
}
KeyEventResult _handleDpad(RawKeyEvent event) {
//debugDumpFocusTree();
if (event.runtimeType == RawKeyUpEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
if (kDebugMode) {
print("left button");
}
_aIsSelected = true;
_bIsSelected = false;
_focusScopeNodeA.requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
if (kDebugMode) {
print("right button");
}
_aIsSelected = false;
_bIsSelected = true;
_focusScopeNodeB.requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (kDebugMode) {
print("down button");
}
if (_aIsSelected) {
_focusScopeNodeA.nextFocus();
}
if (_bIsSelected) {
_focusScopeNodeB.nextFocus();
}
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
if (kDebugMode) {
print("up button");
}
if (_aIsSelected) {
_focusScopeNodeA.previousFocus();
}
if (_bIsSelected) {
_focusScopeNodeB.previousFocus();
}
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
或查看:
https://gist.github.com/dixi83/2b2d0a63fe465baa09210be9d34fd194
我也尝试了 unfocus() 方法,但因为它没有任何区别,所以我在示例中将其保留。
更新 1:
discord 上的某个人参加了我对 3 个按钮使用相同的 FocusNode 的讨论。他是对的,但奇怪的是在我发布这个问题之前,我有一个包含 2 个焦点节点列表的版本,我尝试用指针处理焦点。这个版本向我展示了完全相同的行为...这是源代码:
class FocusDemo extends StatefulWidget {
const FocusDemo({Key? key}) : super(key: key);
@override
_FocusDemoState createState() => _FocusDemoState();
}
class _FocusDemoState extends State<FocusDemo> {
static const int _nrOfNodesA = 3;
static int _focusPointerA = 0;
static bool _aIsSelected = true;
final List<FocusNode> _focusNodesA = List.filled(_nrOfNodesA, FocusNode());
static const int _nrOfNodesB = 3;
static int _focusPointerB = 0;
static bool _bIsSelected = false;
final List<FocusNode> _focusNodesB = List.filled(_nrOfNodesB, FocusNode());
@override
void initState() {
super.initState();
_focusNodesA[0].hasPrimaryFocus;
_focusNodesA[0].requestFocus();
RawKeyboard.instance.addListener(_handleDpad);
}
@override
void dispose() {
for(int i=0; i < _nrOfNodesA; i++){
_focusNodesA[i].dispose();
}
for(int i=0; i < _nrOfNodesB; i++){
_focusNodesB[i].dispose();
}
RawKeyboard.instance.removeListener(_handleDpad);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Focus(
child: Column(
children: [
TextButton(
onPressed: () { print("a1");},
focusNode: _focusNodesA[0],
autofocus: true,
child: const Text("a 1"),
),
TextButton(
onPressed: () { print("a2");},
focusNode: _focusNodesA[1],
autofocus: false,
child: const Text("a 2"),
),
TextButton(
onPressed: () { print("a3");},
focusNode: _focusNodesA[2],
autofocus: false,
child: const Text("a 3"),
),
],
),
),
),
),
Expanded(
flex: 10,
child: Container()),
Expanded(
flex: 45,
child: Container(
height: 155,
color: Colors.grey[350],
child: Column(
children: [
TextButton(
onPressed: () { print("b1");},
focusNode: _focusNodesB[0],
autofocus: false,
child: const Text("b 1"),
),
TextButton(
onPressed: () { print("b2");},
focusNode: _focusNodesB[1],
autofocus: false,
child: const Text("b 2"),
),
TextButton(
onPressed: () { print("b3"); },
focusNode: _focusNodesB[2],
autofocus: false,
child: const Text("b 3"),
),
],
),
),
),
],
),
),
);
}
KeyEventResult _handleDpad(RawKeyEvent event) {
//debugDumpFocusTree();
if (event.runtimeType == RawKeyUpEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
if (kDebugMode) {
print("left button");
}
_aIsSelected = true;
_bIsSelected = false;
_focusPointerA = 0;
_focusPointerB = 0;
_focusNodesA[0].requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
if (kDebugMode) {
print("right button");
}
_aIsSelected = false;
_bIsSelected = true;
_focusPointerA = 0;
_focusPointerB = 0;
_focusNodesB[0].requestFocus();
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (kDebugMode) {
print("down button");
}
_focusPointerA--;
_focusPointerB--;
if (_focusPointerA < 0) {
_focusPointerA = 0;
}
if (_focusPointerB < 0) {
_focusPointerB = 0;
}
if (_aIsSelected) {
_focusNodesA[_focusPointerA].requestFocus();
}
if (_bIsSelected) {
_focusNodesB[_focusPointerB].requestFocus();
}
return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
if (kDebugMode) {
print("up button");
}
_focusPointerA++;
_focusPointerB++;
if (_focusPointerA >= _nrOfNodesA) {
_focusPointerA = _nrOfNodesA-1;
}
if (_focusPointerB >= _nrOfNodesB) {
_focusPointerB = _nrOfNodesA-1;
}
if (_aIsSelected) {
_focusNodesA[_focusPointerA].requestFocus();
}
if (_bIsSelected) {
_focusNodesB[_focusPointerB].requestFocus();
}
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
}
或查看:
https://gist.github.com/dixi83/f653022ad1bdb1d41dcfcf0f1d278b8b
我的第一次尝试(见更新 1)似乎比我想象的更接近解决方案......我的错误是我使用 List.filled()
而不是 List.generate()
区别?
final List<FocusNode> _focusNodeList = List.generate(_nrOfNodes, (_) => FocusNode());