| // 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'; | |
| /// Flutter code sample for [FocusScope]. | |
| void main() => runApp(const FocusScopeExampleApp()); | |
| class FocusScopeExampleApp extends StatelessWidget { | |
| const FocusScopeExampleApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return const MaterialApp(home: FocusScopeExample()); | |
| } | |
| } | |
| /// A demonstration pane. | |
| /// | |
| /// This is just a separate widget to simplify the example. | |
| class Pane extends StatelessWidget { | |
| const Pane({ | |
| super.key, | |
| required this.focusNode, | |
| this.onPressed, | |
| required this.backgroundColor, | |
| required this.icon, | |
| this.child, | |
| }); | |
| final FocusNode focusNode; | |
| final VoidCallback? onPressed; | |
| final Color backgroundColor; | |
| final Widget icon; | |
| final Widget? child; | |
| @override | |
| Widget build(BuildContext context) { | |
| return Material( | |
| color: backgroundColor, | |
| child: Stack( | |
| fit: .expand, | |
| children: <Widget>[ | |
| Center(child: child), | |
| Align( | |
| alignment: .topLeft, | |
| child: IconButton( | |
| autofocus: true, | |
| focusNode: focusNode, | |
| onPressed: onPressed, | |
| icon: icon, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class FocusScopeExample extends StatefulWidget { | |
| const FocusScopeExample({super.key}); | |
| @override | |
| State<FocusScopeExample> createState() => _FocusScopeExampleState(); | |
| } | |
| class _FocusScopeExampleState extends State<FocusScopeExample> { | |
| bool backdropIsVisible = false; | |
| FocusNode backdropNode = FocusNode(debugLabel: 'Close Backdrop Button'); | |
| FocusNode foregroundNode = FocusNode(debugLabel: 'Option Button'); | |
| @override | |
| void dispose() { | |
| backdropNode.dispose(); | |
| foregroundNode.dispose(); | |
| super.dispose(); | |
| } | |
| Widget _buildStack(BuildContext context, BoxConstraints constraints) { | |
| final Size stackSize = constraints.biggest; | |
| return Stack( | |
| fit: .expand, | |
| // The backdrop is behind the front widget in the Stack, but the widgets | |
| // would still be active and traversable without the FocusScope. | |
| children: <Widget>[ | |
| // TRY THIS: Try removing this FocusScope entirely to see how it affects | |
| // the behavior. Without this FocusScope, the "ANOTHER BUTTON TO FOCUS" | |
| // button, and the IconButton in the backdrop Pane would be focusable | |
| // even when the backdrop wasn't visible. | |
| FocusScope( | |
| // TRY THIS: Try commenting out this line. Notice that the focus | |
| // starts on the backdrop and is stuck there? It seems like the app is | |
| // non-responsive, but it actually isn't. This line makes sure that | |
| // this focus scope and its children can't be focused when they're not | |
| // visible. It might help to make the background color of the | |
| // foreground pane semi-transparent to see it clearly. | |
| canRequestFocus: backdropIsVisible, | |
| child: Pane( | |
| icon: const Icon(Icons.close), | |
| focusNode: backdropNode, | |
| backgroundColor: Colors.lightBlue, | |
| onPressed: () => setState(() => backdropIsVisible = false), | |
| child: Column( | |
| mainAxisAlignment: .center, | |
| children: <Widget>[ | |
| // This button would be not visible, but still focusable from | |
| // the foreground pane without the FocusScope. | |
| ElevatedButton( | |
| onPressed: () => debugPrint('You pressed the other button!'), | |
| child: const Text('ANOTHER BUTTON TO FOCUS'), | |
| ), | |
| DefaultTextStyle( | |
| style: Theme.of(context).textTheme.displayMedium!, | |
| child: const Text('BACKDROP'), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| AnimatedPositioned( | |
| curve: Curves.easeInOut, | |
| duration: const Duration(milliseconds: 300), | |
| top: backdropIsVisible ? stackSize.height * 0.9 : 0.0, | |
| width: stackSize.width, | |
| height: stackSize.height, | |
| onEnd: () { | |
| if (backdropIsVisible) { | |
| backdropNode.requestFocus(); | |
| } else { | |
| foregroundNode.requestFocus(); | |
| } | |
| }, | |
| child: Pane( | |
| icon: const Icon(Icons.menu), | |
| focusNode: foregroundNode, | |
| // TRY THIS: Try changing this to Colors.green.withValues(alpha: 0.8) to see for | |
| // yourself that the hidden components do/don't get focus. | |
| backgroundColor: Colors.green, | |
| onPressed: backdropIsVisible | |
| ? null | |
| : () => setState(() => backdropIsVisible = true), | |
| child: DefaultTextStyle( | |
| style: Theme.of(context).textTheme.displayMedium!, | |
| child: const Text('FOREGROUND'), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| // Use a LayoutBuilder so that we can base the size of the stack on the size | |
| // of its parent. | |
| return LayoutBuilder(builder: _buildStack); | |
| } | |
| } |