1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'package:flutter/material.dart'; |
6 | |
7 | /// Flutter code sample for [FocusTraversalGroup]. |
8 | |
9 | void main() => runApp(const FocusTraversalGroupExampleApp()); |
10 | |
11 | class FocusTraversalGroupExampleApp extends StatelessWidget { |
12 | const FocusTraversalGroupExampleApp({super.key}); |
13 | |
14 | @override |
15 | Widget build(BuildContext context) { |
16 | return const MaterialApp( |
17 | home: FocusTraversalGroupExample(), |
18 | ); |
19 | } |
20 | } |
21 | |
22 | /// A button wrapper that adds either a numerical or lexical order, depending on |
23 | /// the type of T. |
24 | class OrderedButton<T> extends StatefulWidget { |
25 | const OrderedButton({ |
26 | super.key, |
27 | required this.name, |
28 | this.canRequestFocus = true, |
29 | this.autofocus = false, |
30 | required this.order, |
31 | }); |
32 | |
33 | final String name; |
34 | final bool canRequestFocus; |
35 | final bool autofocus; |
36 | final T order; |
37 | |
38 | @override |
39 | State<OrderedButton<T>> createState() => _OrderedButtonState<T>(); |
40 | } |
41 | |
42 | class _OrderedButtonState<T> extends State<OrderedButton<T>> { |
43 | late FocusNode focusNode; |
44 | |
45 | @override |
46 | void initState() { |
47 | super.initState(); |
48 | focusNode = FocusNode( |
49 | debugLabel: widget.name, |
50 | canRequestFocus: widget.canRequestFocus, |
51 | ); |
52 | } |
53 | |
54 | @override |
55 | void dispose() { |
56 | focusNode.dispose(); |
57 | super.dispose(); |
58 | } |
59 | |
60 | @override |
61 | void didUpdateWidget(OrderedButton<T> oldWidget) { |
62 | super.didUpdateWidget(oldWidget); |
63 | focusNode.canRequestFocus = widget.canRequestFocus; |
64 | } |
65 | |
66 | void _handleOnPressed() { |
67 | focusNode.requestFocus(); |
68 | debugPrint('Button ${widget.name} pressed.' ); |
69 | debugDumpFocusTree(); |
70 | } |
71 | |
72 | @override |
73 | Widget build(BuildContext context) { |
74 | final FocusOrder order = switch (widget.order) { |
75 | final num number => NumericFocusOrder(number.toDouble()), |
76 | final Object? object => LexicalFocusOrder(object.toString()), |
77 | }; |
78 | |
79 | return FocusTraversalOrder( |
80 | order: order, |
81 | child: Padding( |
82 | padding: const EdgeInsets.all(8.0), |
83 | child: OutlinedButton( |
84 | focusNode: focusNode, |
85 | autofocus: widget.autofocus, |
86 | style: const ButtonStyle( |
87 | overlayColor: WidgetStateProperty<Color?>.fromMap( |
88 | // If neither of these states is active, the property will |
89 | // resolve to null, deferring to the default overlay color. |
90 | <WidgetState, Color>{ |
91 | WidgetState.focused: Colors.red, |
92 | WidgetState.hovered: Colors.blue, |
93 | }, |
94 | ), |
95 | foregroundColor: WidgetStateProperty<Color?>.fromMap( |
96 | // "WidgetState.focused | WidgetState.hovered" could be used |
97 | // instead of separate map keys, but this setup allows setting |
98 | // the button style to a constant value for improved efficiency. |
99 | <WidgetState, Color>{ |
100 | WidgetState.focused: Colors.white, |
101 | WidgetState.hovered: Colors.white, |
102 | }, |
103 | ), |
104 | ), |
105 | onPressed: () => _handleOnPressed(), |
106 | child: Text(widget.name), |
107 | ), |
108 | ), |
109 | ); |
110 | } |
111 | } |
112 | |
113 | class FocusTraversalGroupExample extends StatelessWidget { |
114 | const FocusTraversalGroupExample({super.key}); |
115 | |
116 | @override |
117 | Widget build(BuildContext context) { |
118 | return ColoredBox( |
119 | color: Colors.white, |
120 | child: FocusTraversalGroup( |
121 | policy: OrderedTraversalPolicy(), |
122 | child: Column( |
123 | mainAxisAlignment: MainAxisAlignment.center, |
124 | children: <Widget>[ |
125 | // A group that is ordered with a numerical order, from left to right. |
126 | FocusTraversalGroup( |
127 | policy: OrderedTraversalPolicy(), |
128 | child: Row( |
129 | mainAxisAlignment: MainAxisAlignment.center, |
130 | children: List<Widget>.generate(3, (int index) { |
131 | return OrderedButton<num>( |
132 | name: 'num: $index' , |
133 | // TRY THIS: change this to "3 - index" and see how the order changes. |
134 | order: index, |
135 | ); |
136 | }), |
137 | ), |
138 | ), |
139 | // A group that is ordered with a lexical order, from right to left. |
140 | FocusTraversalGroup( |
141 | policy: OrderedTraversalPolicy(), |
142 | child: Row( |
143 | mainAxisAlignment: MainAxisAlignment.center, |
144 | children: List<Widget>.generate(3, (int index) { |
145 | // Order as "C" "B", "A". |
146 | final String order = String.fromCharCode('A' .codeUnitAt(0) + (2 - index)); |
147 | return OrderedButton<String>( |
148 | name: 'String: $order' , |
149 | order: order, |
150 | ); |
151 | }), |
152 | ), |
153 | ), |
154 | // A group that orders in widget order, regardless of what the order is set to. |
155 | FocusTraversalGroup( |
156 | // Because this is NOT an OrderedTraversalPolicy, the |
157 | // assigned order of these OrderedButtons is ignored, and they |
158 | // are traversed in widget order. TRY THIS: change this to |
159 | // "OrderedTraversalPolicy()" and see that it now follows the |
160 | // numeric order set on them instead of the widget order. |
161 | policy: WidgetOrderTraversalPolicy(), |
162 | child: Row( |
163 | mainAxisAlignment: MainAxisAlignment.center, |
164 | children: List<Widget>.generate(3, (int index) { |
165 | return OrderedButton<num>( |
166 | name: 'ignored num: ${3 - index}' , |
167 | order: 3 - index, |
168 | ); |
169 | }), |
170 | ), |
171 | ), |
172 | ], |
173 | ), |
174 | ), |
175 | ); |
176 | } |
177 | } |
178 | |