Skip to content

Instantly share code, notes, and snippets.

@justinmc
Last active April 7, 2025 20:19
Show Gist options
  • Save justinmc/ef4ab9a6d16d8179d204144252d11d7d to your computer and use it in GitHub Desktop.
Save justinmc/ef4ab9a6d16d8179d204144252d11d7d to your computer and use it in GitHub Desktop.
Reparenting FocusNode example.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// Modified from the FocusNode example in the docs:
// https://main-api.flutter.dev/flutter/widgets/FocusNode-class.html
void main() => runApp(const FocusNodeExampleApp());
class FocusNodeExampleApp extends StatelessWidget {
const FocusNodeExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('FocusNode Sample')),
body: const FocusNodeExample(),
),
);
}
}
class ColorfulButton extends StatefulWidget {
const ColorfulButton({super.key});
@override
State<ColorfulButton> createState() {
print('justin createState.');
return _ColorfulButtonState();
}
}
class _ColorfulButtonState extends State<ColorfulButton> {
late FocusNode _node;
bool _focused = false;
FocusAttachment? _nodeAttachment;
Color _color = Colors.white;
@override
void initState() {
print('justin ColorfulButtonState initState.');
super.initState();
_node = FocusNode(debugLabel: 'Button');
_node.addListener(_handleFocusChange);
if (_nodeAttachment != null) {
_nodeAttachment!.detach();
}
_nodeAttachment = _node.attach(context, onKeyEvent: _handleKeyPress);
}
@override
void didUpdateWidget(ColorfulButton oldWidget) {
super.didUpdateWidget(oldWidget);
}
void _handleFocusChange() {
if (_node.hasFocus != _focused) {
setState(() {
_focused = _node.hasFocus;
});
}
}
KeyEventResult _handleKeyPress(FocusNode node, KeyEvent event) {
if (event is KeyDownEvent) {
debugPrint(
'Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
switch (event.logicalKey) {
case LogicalKeyboardKey.keyR:
debugPrint('Changing color to red.');
setState(() {
_color = Colors.red;
});
return KeyEventResult.handled;
case LogicalKeyboardKey.keyG:
debugPrint('Changing color to green.');
setState(() {
_color = Colors.green;
});
return KeyEventResult.handled;
case LogicalKeyboardKey.keyB:
debugPrint('Changing color to blue.');
setState(() {
_color = Colors.blue;
});
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
@override
void dispose() {
print('justin ColorfulButtonState initState.');
_node.removeListener(_handleFocusChange);
// The attachment will automatically be detached in dispose().
_node.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
print('justin buildColorsfulbutton');
_nodeAttachment!.reparent();
return GestureDetector(
onTap: () {
if (_focused) {
_node.unfocus();
} else {
_node.requestFocus();
}
},
child: Center(
child: Container(
width: 400,
height: 100,
color: _focused ? _color : Colors.white,
alignment: Alignment.center,
child:
Text(_focused ? "I'm in color! Press R,G,B!" : 'Press to focus'),
),
),
);
}
}
class FocusNodeExample extends StatefulWidget {
const FocusNodeExample({super.key});
@override
State<FocusNodeExample> createState() => _FocusNodeExampleState();
}
class _FocusNodeExampleState extends State<FocusNodeExample> {
final GlobalKey _buttonKey = GlobalKey();
late final ColorfulButton _button;
bool _inAlternativeParent = false;
@override
void initState() {
super.initState();
_button = ColorfulButton(
key:
_buttonKey); // Without this key, _button's state will be recreated.
print('justin _FocusNodeExample initstate.');
}
@override
void dispose() {
print('justin _FocusNodeExample dispose.');
super.dispose();
}
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return Column(
children: <Widget>[
ColoredBox(
color: Colors.cyan.withValues(alpha: 0.2),
child: DefaultTextStyle(
style: textTheme.headlineMedium!,
child: _inAlternativeParent ? const Text('empty') : _button,
),
),
ColoredBox(
color: Colors.yellow.withValues(alpha: 0.2),
child: DefaultTextStyle(
style: textTheme.headlineMedium!,
child: _inAlternativeParent ? _button : const Text('empty'),
),
),
TextButton(
onPressed: () {
setState(() {
_inAlternativeParent = !_inAlternativeParent;
});
},
child: const Text('reparent'),
),
],
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment