'Flutter Focus is focussing multiple nodes instead of a single node
I am building an app that is going to be controlled by a D-pad keyboard events. So I dug into the flutter Focus system. I want to give it special directions because in my app I want to work with multiple "control zones" in my example I added 2 zones, A and B.
At this moment, when I push the left arrow it focuses in the left (A) area. When I push the right arrow it focuses the right (B) area. But is focuses all the buttons in the "zone"??
Actual behavior:

The expected behavior is that it switches the zone and focuses one button with the left/right keys. With the up/down keys it should switch buttons inside the focus inside the active zone.
here is my code:
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;
}
or see:
https://gist.github.com/dixi83/2b2d0a63fe465baa09210be9d34fd194
I tried also the unfocus() method but because is did not make any difference I left it away in my example.
Update 1:
Someone on discord attended me on using the same FocusNode for 3 buttons. He is right but the strange thing is before I posted this question, I had a version with 2 lists of focus nodes where I tried handling the focus with a pointer. This version showed me the exact the same behavior... here is the source code:
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;
}
}
or see:
https://gist.github.com/dixi83/f653022ad1bdb1d41dcfcf0f1d278b8b
Solution 1:[1]
With my first attempt (See Update 1) it seems that I was more close to the solution than I thought... My mistake is that I used List.filled() in stead of List.generate()
The difference? This is perfectly explained by danypata.
final List<FocusNode> _focusNodeList = List.generate(_nrOfNodes, (_) => FocusNode());
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
