Last active
April 7, 2025 20:19
-
-
Save justinmc/ef4ab9a6d16d8179d204144252d11d7d to your computer and use it in GitHub Desktop.
Reparenting FocusNode example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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