|  | // Copyright 2014 The Flutter Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | import 'package:flutter/material.dart'; | 
|  | import 'package:flutter/services.dart'; | 
|  |  | 
|  | void main() { | 
|  | runApp(const MaterialApp( | 
|  | title: 'Focus Demo', | 
|  | home: FocusDemo(), | 
|  | )); | 
|  | } | 
|  |  | 
|  | class DemoButton extends StatefulWidget { | 
|  | const DemoButton({super.key, required this.name, this.canRequestFocus = true, this.autofocus = false}); | 
|  |  | 
|  | final String name; | 
|  | final bool canRequestFocus; | 
|  | final bool autofocus; | 
|  |  | 
|  | @override | 
|  | State<DemoButton> createState() => _DemoButtonState(); | 
|  | } | 
|  |  | 
|  | class _DemoButtonState extends State<DemoButton> { | 
|  | late final FocusNode focusNode = FocusNode( | 
|  | debugLabel: widget.name, | 
|  | canRequestFocus: widget.canRequestFocus, | 
|  | ); | 
|  |  | 
|  | @override | 
|  | void dispose() { | 
|  | focusNode.dispose(); | 
|  | super.dispose(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void didUpdateWidget(DemoButton oldWidget) { | 
|  | super.didUpdateWidget(oldWidget); | 
|  | focusNode.canRequestFocus = widget.canRequestFocus; | 
|  | } | 
|  |  | 
|  | void _handleOnPressed() { | 
|  | focusNode.requestFocus(); | 
|  | print('Button ${widget.name} pressed.'); | 
|  | debugDumpFocusTree(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Widget build(BuildContext context) { | 
|  | return TextButton( | 
|  | focusNode: focusNode, | 
|  | autofocus: widget.autofocus, | 
|  | style: ButtonStyle( | 
|  | overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { | 
|  | if (states.contains(MaterialState.focused)) { | 
|  | return Colors.red.withOpacity(0.25); | 
|  | } | 
|  | if (states.contains(MaterialState.hovered)) { | 
|  | return Colors.blue.withOpacity(0.25); | 
|  | } | 
|  | return Colors.transparent; | 
|  | }), | 
|  | ), | 
|  | onPressed: () => _handleOnPressed(), | 
|  | child: Text(widget.name), | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | class FocusDemo extends StatefulWidget { | 
|  | const FocusDemo({super.key}); | 
|  |  | 
|  | @override | 
|  | State<FocusDemo> createState() => _FocusDemoState(); | 
|  | } | 
|  |  | 
|  | class _FocusDemoState extends State<FocusDemo> { | 
|  | FocusNode? outlineFocus; | 
|  |  | 
|  | @override | 
|  | void initState() { | 
|  | super.initState(); | 
|  | outlineFocus = FocusNode(debugLabel: 'Demo Focus Node'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void dispose() { | 
|  | outlineFocus?.dispose(); | 
|  | super.dispose(); | 
|  | } | 
|  |  | 
|  | KeyEventResult _handleKeyPress(FocusNode node, KeyEvent event) { | 
|  | if (event is KeyDownEvent) { | 
|  | print('Scope got key event: ${event.logicalKey}, $node'); | 
|  | print('Keys down: ${HardwareKeyboard.instance.logicalKeysPressed}'); | 
|  | if (event.logicalKey == LogicalKeyboardKey.tab) { | 
|  | debugDumpFocusTree(); | 
|  | if (HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.shiftLeft) | 
|  | || HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.shiftRight)) { | 
|  | print('Moving to previous.'); | 
|  | node.previousFocus(); | 
|  | return KeyEventResult.handled; | 
|  | } else { | 
|  | print('Moving to next.'); | 
|  | node.nextFocus(); | 
|  | return KeyEventResult.handled; | 
|  | } | 
|  | } | 
|  | if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { | 
|  | node.focusInDirection(TraversalDirection.left); | 
|  | return KeyEventResult.handled; | 
|  | } | 
|  | if (event.logicalKey == LogicalKeyboardKey.arrowRight) { | 
|  | node.focusInDirection(TraversalDirection.right); | 
|  | return KeyEventResult.handled; | 
|  | } | 
|  | if (event.logicalKey == LogicalKeyboardKey.arrowUp) { | 
|  | node.focusInDirection(TraversalDirection.up); | 
|  | return KeyEventResult.handled; | 
|  | } | 
|  | if (event.logicalKey == LogicalKeyboardKey.arrowDown) { | 
|  | node.focusInDirection(TraversalDirection.down); | 
|  | return KeyEventResult.handled; | 
|  | } | 
|  | } | 
|  | return KeyEventResult.ignored; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Widget build(BuildContext context) { | 
|  | final TextTheme textTheme = Theme.of(context).textTheme; | 
|  |  | 
|  | return FocusTraversalGroup( | 
|  | policy: ReadingOrderTraversalPolicy(), | 
|  | child: FocusScope( | 
|  | debugLabel: 'Scope', | 
|  | onKeyEvent: _handleKeyPress, | 
|  | autofocus: true, | 
|  | child: DefaultTextStyle( | 
|  | style: textTheme.headlineMedium!, | 
|  | child: Scaffold( | 
|  | appBar: AppBar( | 
|  | title: const Text('Focus Demo'), | 
|  | ), | 
|  | floatingActionButton: FloatingActionButton( | 
|  | child: const Text('+'), | 
|  | onPressed: () {}, | 
|  | ), | 
|  | body: Center( | 
|  | child: Builder(builder: (BuildContext context) { | 
|  | return Column( | 
|  | mainAxisAlignment: MainAxisAlignment.center, | 
|  | children: <Widget>[ | 
|  | const Row( | 
|  | mainAxisAlignment: MainAxisAlignment.center, | 
|  | children: <Widget>[ | 
|  | DemoButton( | 
|  | name: 'One', | 
|  | autofocus: true, | 
|  | ), | 
|  | ], | 
|  | ), | 
|  | const Row( | 
|  | mainAxisAlignment: MainAxisAlignment.center, | 
|  | children: <Widget>[ | 
|  | DemoButton(name: 'Two'), | 
|  | DemoButton( | 
|  | name: 'Three', | 
|  | canRequestFocus: false, | 
|  | ), | 
|  | ], | 
|  | ), | 
|  | const Row( | 
|  | mainAxisAlignment: MainAxisAlignment.center, | 
|  | children: <Widget>[ | 
|  | DemoButton(name: 'Four'), | 
|  | DemoButton(name: 'Five'), | 
|  | DemoButton(name: 'Six'), | 
|  | ], | 
|  | ), | 
|  | OutlinedButton(onPressed: () => print('pressed'), child: const Text('PRESS ME')), | 
|  | const Padding( | 
|  | padding: EdgeInsets.all(8.0), | 
|  | child: TextField( | 
|  | decoration: InputDecoration(labelText: 'Enter Text', filled: true), | 
|  | ), | 
|  | ), | 
|  | const Padding( | 
|  | padding: EdgeInsets.all(8.0), | 
|  | child: TextField( | 
|  | decoration: InputDecoration( | 
|  | border: OutlineInputBorder(), | 
|  | labelText: 'Enter Text', | 
|  | filled: false, | 
|  | ), | 
|  | ), | 
|  | ), | 
|  | ], | 
|  | ); | 
|  | }), | 
|  | ), | 
|  | ), | 
|  | ), | 
|  | ), | 
|  | ); | 
|  | } | 
|  | } |