focus.dart 6.24 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Ian Hickson's avatar
Ian Hickson committed
4

5 6 7
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

8
void main() {
9 10 11 12 13 14
  runApp(const MaterialApp(
    title: 'Focus Demo',
    home: FocusDemo(),
  ));
}

15
class DemoButton extends StatefulWidget {
16
  const DemoButton({super.key, required this.name, this.canRequestFocus = true, this.autofocus = false});
17 18

  final String name;
19 20 21 22
  final bool canRequestFocus;
  final bool autofocus;

  @override
23
  State<DemoButton> createState() => _DemoButtonState();
24 25 26
}

class _DemoButtonState extends State<DemoButton> {
27
  late final FocusNode focusNode = FocusNode(
28 29
      debugLabel: widget.name,
      canRequestFocus: widget.canRequestFocus,
30
  );
31 32 33

  @override
  void dispose() {
34
    focusNode.dispose();
35 36 37 38 39 40 41 42
    super.dispose();
  }

  @override
  void didUpdateWidget(DemoButton oldWidget) {
    super.didUpdateWidget(oldWidget);
    focusNode.canRequestFocus = widget.canRequestFocus;
  }
43 44

  void _handleOnPressed() {
45 46 47
    focusNode.requestFocus();
    print('Button ${widget.name} pressed.');
    debugDumpFocusTree();
48 49 50 51
  }

  @override
  Widget build(BuildContext context) {
52
    return TextButton(
53 54
      focusNode: focusNode,
      autofocus: widget.autofocus,
55 56
      style: ButtonStyle(
        overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
57
          if (states.contains(MaterialState.focused)) {
58
            return Colors.red.withOpacity(0.25);
59 60
          }
          if (states.contains(MaterialState.hovered)) {
61
            return Colors.blue.withOpacity(0.25);
62
          }
63
          return Colors.transparent;
64 65
        }),
      ),
66
      onPressed: () => _handleOnPressed(),
67
      child: Text(widget.name),
68 69 70 71 72
    );
  }
}

class FocusDemo extends StatefulWidget {
73
  const FocusDemo({super.key});
74 75

  @override
76
  State<FocusDemo> createState() => _FocusDemoState();
77 78 79
}

class _FocusDemoState extends State<FocusDemo> {
80
  FocusNode? outlineFocus;
81 82 83 84 85 86 87 88 89

  @override
  void initState() {
    super.initState();
    outlineFocus = FocusNode(debugLabel: 'Demo Focus Node');
  }

  @override
  void dispose() {
90
    outlineFocus?.dispose();
91 92 93
    super.dispose();
  }

94
  KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
95 96 97 98 99 100 101 102
    if (event is RawKeyDownEvent) {
      print('Scope got key event: ${event.logicalKey}, $node');
      print('Keys down: ${RawKeyboard.instance.keysPressed}');
      if (event.logicalKey == LogicalKeyboardKey.tab) {
        debugDumpFocusTree();
        if (event.isShiftPressed) {
          print('Moving to previous.');
          node.previousFocus();
103
          return KeyEventResult.handled;
104 105 106
        } else {
          print('Moving to next.');
          node.nextFocus();
107
          return KeyEventResult.handled;
108 109 110 111
        }
      }
      if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
        node.focusInDirection(TraversalDirection.left);
112
        return KeyEventResult.handled;
113 114 115
      }
      if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
        node.focusInDirection(TraversalDirection.right);
116
        return KeyEventResult.handled;
117 118 119
      }
      if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
        node.focusInDirection(TraversalDirection.up);
120
        return KeyEventResult.handled;
121 122 123
      }
      if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
        node.focusInDirection(TraversalDirection.down);
124
        return KeyEventResult.handled;
125 126
      }
    }
127
    return KeyEventResult.ignored;
128 129 130 131 132 133
  }

  @override
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;

134
    return FocusTraversalGroup(
135 136 137 138 139 140
      policy: ReadingOrderTraversalPolicy(),
      child: FocusScope(
        debugLabel: 'Scope',
        onKey: _handleKeyPress,
        autofocus: true,
        child: DefaultTextStyle(
141
          style: textTheme.headlineMedium!,
142 143 144 145 146 147 148 149 150 151 152 153 154
          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>[
155
                    const Row(
156
                      mainAxisAlignment: MainAxisAlignment.center,
157
                      children: <Widget>[
158 159 160 161
                        DemoButton(
                          name: 'One',
                          autofocus: true,
                        ),
162 163
                      ],
                    ),
164
                    const Row(
165
                      mainAxisAlignment: MainAxisAlignment.center,
166
                      children: <Widget>[
167
                        DemoButton(name: 'Two'),
168 169 170 171
                        DemoButton(
                          name: 'Three',
                          canRequestFocus: false,
                        ),
172 173
                      ],
                    ),
174
                    const Row(
175
                      mainAxisAlignment: MainAxisAlignment.center,
176
                      children: <Widget>[
177 178 179 180 181
                        DemoButton(name: 'Four'),
                        DemoButton(name: 'Five'),
                        DemoButton(name: 'Six'),
                      ],
                    ),
182
                    OutlinedButton(onPressed: () => print('pressed'), child: const Text('PRESS ME')),
183 184
                    const Padding(
                      padding: EdgeInsets.all(8.0),
185 186 187 188
                      child: TextField(
                        decoration: InputDecoration(labelText: 'Enter Text', filled: true),
                      ),
                    ),
189 190
                    const Padding(
                      padding: EdgeInsets.all(8.0),
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
                      child: TextField(
                        decoration: InputDecoration(
                          border: OutlineInputBorder(),
                          labelText: 'Enter Text',
                          filled: false,
                        ),
                      ),
                    ),
                  ],
                );
              }),
            ),
          ),
        ),
      ),
    );
  }
}