focus_scope.dart 22.2 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
import 'package:flutter/foundation.dart';
6 7 8 9

import 'basic.dart';
import 'focus_manager.dart';
import 'framework.dart';
10
import 'inherited_notifier.dart';
11

12 13
/// A widget that manages a [FocusNode] to allow keyboard focus to be given
/// to this widget and its descendants.
14
///
15
/// When the focus is gained or lost, [onFocusChanged] is called.
16
///
17 18 19
/// For keyboard events, [onKey] is called if [FocusNode.hasFocus] is true for
/// this widget's [focusNode], unless a focused descendant's [onKey] callback
/// returns false when called.
Adam Barth's avatar
Adam Barth committed
20
///
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
/// This widget does not provide any visual indication that the focus has
/// changed. Any desired visual changes should be made when [onFocusChanged] is
/// called.
///
/// To access the [FocusNode] of the nearest ancestor [Focus] widget and
/// establish a relationship that will rebuild the widget when the focus
/// changes, use the [Focus.of] and [FocusScope.of] static methods.
///
/// To access the focused state of the nearest [Focus] widget, use
/// [Focus.hasFocus] from a build method, which also establishes a relationship
/// between the calling widget and the [Focus] widget that will rebuild the
/// calling widget when the focus changes.
///
/// Managing a [FocusNode] means managing its lifecycle, listening for changes
/// in focus, and re-parenting it when needed to keep the focus hierarchy in
/// sync with the widget hierarchy. See [FocusNode] for more information about
/// the details of what node management entails if not using a [Focus] widget.
///
/// To collect a sub-tree of nodes into a group, use a [FocusScope].
///
41
/// {@tool dartpad --template=stateful_widget_scaffold}
42 43 44 45 46 47
/// This example shows how to manage focus using the [Focus] and [FocusScope]
/// widgets. See [FocusNode] for a similar example that doesn't use [Focus] or
/// [FocusScope].
///
/// ```dart imports
/// import 'package:flutter/services.dart';
48
/// ```
49
///
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
/// ```dart
/// Color _color = Colors.white;
///
/// bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
///   if (event is RawKeyDownEvent) {
///     print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
///     if (event.logicalKey == LogicalKeyboardKey.keyR) {
///       print('Changing color to red.');
///       setState(() {
///         _color = Colors.red;
///       });
///       return true;
///     } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
///       print('Changing color to green.');
///       setState(() {
///         _color = Colors.green;
///       });
///       return true;
///     } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
///       print('Changing color to blue.');
///       setState(() {
///         _color = Colors.blue;
///       });
///       return true;
///     }
///   }
///   return false;
/// }
///
/// @override
/// Widget build(BuildContext context) {
///   final TextTheme textTheme = Theme.of(context).textTheme;
///   return FocusScope(
///     debugLabel: 'Scope',
///     autofocus: true,
///     child: DefaultTextStyle(
86
///       style: textTheme.headline4,
87 88 89 90 91 92 93 94 95 96
///       child: Focus(
///         onKey: _handleKeyPress,
///         debugLabel: 'Button',
///         child: Builder(
///           builder: (BuildContext context) {
///             final FocusNode focusNode = Focus.of(context);
///             final bool hasFocus = focusNode.hasFocus;
///             return GestureDetector(
///               onTap: () {
///                 if (hasFocus) {
97
///                   focusNode.unfocus();
98
///                 } else {
99
///                   focusNode.requestFocus();
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
///                 }
///               },
///               child: Center(
///                 child: Container(
///                   width: 400,
///                   height: 100,
///                   alignment: Alignment.center,
///                   color: hasFocus ? _color : Colors.white,
///                   child: Text(hasFocus ? "I'm in color! Press R,G,B!" : 'Press to focus'),
///                 ),
///               ),
///             );
///           },
///         ),
///       ),
///     ),
///   );
/// }
/// ```
/// {@end-tool}
Adam Barth's avatar
Adam Barth committed
120
///
121
/// See also:
Adam Barth's avatar
Adam Barth committed
122
///
123 124 125 126 127 128 129 130 131 132 133 134 135
///  * [FocusNode], which represents a node in the focus hierarchy and
///    [FocusNode]'s API documentation includes a detailed explanation of its role
///    in the overall focus system.
///  * [FocusScope], a widget that manages a group of focusable widgets using a
///    [FocusScopeNode].
///  * [FocusScopeNode], a node that collects focus nodes into a group for
///    traversal.
///  * [FocusManager], a singleton that manages the primary focus and
///    distributes key events to focused nodes.
///  * [FocusTraversalPolicy], an object used to determine how to move the focus
///    to other nodes.
///  * [DefaultFocusTraversal], a widget used to configure the default focus
///    traversal policy for a widget subtree.
136 137
class Focus extends StatefulWidget {
  /// Creates a widget that manages a [FocusNode].
138
  ///
139 140
  /// The [child] argument is required and must not be null.
  ///
141
  /// The [autofocus] argument must not be null.
142
  const Focus({
143
    Key key,
144 145
    @required this.child,
    this.focusNode,
146
    this.autofocus = false,
147 148 149
    this.onFocusChange,
    this.onKey,
    this.debugLabel,
150
    this.canRequestFocus,
151
    this.skipTraversal,
152
    this.includeSemantics = true,
153 154
  })  : assert(child != null),
        assert(autofocus != null),
155
        assert(includeSemantics != null),
156
        super(key: key);
157

158 159 160 161 162 163 164 165 166 167 168
  /// A debug label for this widget.
  ///
  /// Not used for anything except to be printed in the diagnostic output from
  /// [toString] or [toStringDeep]. Also unused if a [focusNode] is provided,
  /// since that node can have its own [FocusNode.debugLabel].
  ///
  /// To get a string with the entire tree, call [debugDescribeFocusTree]. To
  /// print it to the console call [debugDumpFocusTree].
  ///
  /// Defaults to null.
  final String debugLabel;
169

170
  /// The child widget of this [Focus].
171
  ///
172 173
  /// {@macro flutter.widgets.child}
  final Widget child;
174

175 176
  /// Handler for keys pressed when this object or one of its children has
  /// focus.
177
  ///
178 179 180 181 182 183 184 185 186 187 188 189 190 191
  /// Key events are first given to the [FocusNode] that has primary focus, and
  /// if its [onKey] method return false, then they are given to each ancestor
  /// node up the focus hierarchy in turn. If an event reaches the root of the
  /// hierarchy, it is discarded.
  ///
  /// This is not the way to get text input in the manner of a text field: it
  /// leaves out support for input method editors, and doesn't support soft
  /// keyboards in general. For text input, consider [TextField],
  /// [EditableText], or [CupertinoTextField] instead, which do support these
  /// things.
  final FocusOnKeyCallback onKey;

  /// Handler called when the focus changes.
  ///
192
  /// Called with true if this widget's node gains focus, and false if it loses
193 194 195
  /// focus.
  final ValueChanged<bool> onFocusChange;

196
  /// {@template flutter.widgets.Focus.autofocus}
197 198 199
  /// True if this widget will be selected as the initial focus when no other
  /// node in its scope is currently focused.
  ///
200 201 202 203 204 205
  /// Ideally, there is only one widget with autofocus set in each [FocusScope].
  /// If there is more than one widget with autofocus set, then the first one
  /// added to the tree will get focus.
  ///
  /// Must not be null. Defaults to false.
  /// {@endtemplate}
206 207
  final bool autofocus;

208 209
  /// {@template flutter.widgets.Focus.focusNode}
  /// An optional focus node to use as the focus node for this widget.
210
  ///
211 212 213 214 215
  /// If one is not supplied, then one will be automatically allocated, owned,
  /// and managed by this widget. The widget will be focusable even if a
  /// [focusNode] is not supplied. If supplied, the given `focusNode` will be
  /// _hosted_ by this widget, but not owned. See [FocusNode] for more
  /// information on what being hosted and/or owned implies.
216 217 218 219
  ///
  /// Supplying a focus node is sometimes useful if an ancestor to this widget
  /// wants to control when this widget has the focus. The owner will be
  /// responsible for calling [FocusNode.dispose] on the focus node when it is
220 221 222
  /// done with it, but this widget will attach/detach and reparent the node
  /// when needed.
  /// {@endtemplate}
223
  final FocusNode focusNode;
224

225 226 227 228 229
  /// Sets the [FocusNode.skipTraversal] flag on the focus node so that it won't
  /// be visited by the [FocusTraversalPolicy].
  ///
  /// This is sometimes useful if a Focus widget should receive key events as
  /// part of the focus chain, but shouldn't be accessible via focus traversal.
230 231 232 233
  ///
  /// This is different from [canRequestFocus] because it only implies that the
  /// widget can't be reached via traversal, not that it can't be focused. It may
  /// still be focused explicitly.
234 235
  final bool skipTraversal;

236 237 238 239 240 241 242 243 244 245 246
  /// Include semantics information in this [Focus] widget.
  ///
  /// If true, this [Focus] widget will include a [Semantics] node that
  /// indicates the [Semantics.focusable] and [Semantics.focused] properties.
  ///
  /// Is is not typical to set this to false, as that can affect the semantics
  /// information available to accessibility systems.
  ///
  /// Must not be null, defaults to true.
  final bool includeSemantics;

247
  /// {@template flutter.widgets.Focus.canRequestFocus}
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
  /// If true, this widget may request the primary focus.
  ///
  /// Defaults to true.  Set to false if you want the [FocusNode] this widget
  /// manages to do nothing when [requestFocus] is called on it. Does not affect
  /// the children of this node, and [FocusNode.hasFocus] can still return true
  /// if this node is the ancestor of the primary focus.
  ///
  /// This is different than [skipTraversal] because [skipTraversal] still
  /// allows the widget to be focused, just not traversed to.
  ///
  /// Setting [canRequestFocus] to false implies that the widget will also be
  /// skipped for traversal purposes.
  ///
  /// See also:
  ///
263 264 265 266
  ///  * [DefaultFocusTraversal], a widget that sets the traversal policy for
  ///    its descendants.
  ///  * [FocusTraversalPolicy], a class that can be extended to describe a
  ///    traversal policy.
267
  /// {@endtemplate}
268 269
  final bool canRequestFocus;

270 271
  /// Returns the [focusNode] of the [Focus] that most tightly encloses the
  /// given [BuildContext].
272
  ///
273 274 275 276
  /// If no [Focus] node is found before reaching the nearest [FocusScope]
  /// widget, or there is no [Focus] widget in scope, then this method will
  /// throw an exception. To return null instead of throwing, pass true for
  /// [nullOk].
277
  ///
278
  /// The [context] and [nullOk] arguments must not be null.
279
  static FocusNode of(BuildContext context, { bool nullOk = false, bool scopeOk = false }) {
280
    assert(context != null);
281
    assert(nullOk != null);
282
    assert(scopeOk != null);
283
    final _FocusMarker marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
284
    final FocusNode node = marker?.notifier;
285
    if (node == null) {
286 287
      if (!nullOk) {
        throw FlutterError(
288 289 290 291 292 293
            'Focus.of() was called with a context that does not contain a Focus widget.\n'
                'No Focus widget ancestor could be found starting from the context that was passed to '
                'Focus.of(). This can happen because you are using a widget that looks for a Focus '
                'ancestor, and do not have a Focus widget descendant in the nearest FocusScope.\n'
                'The context used was:\n'
                '  $context'
294 295 296 297
        );
      }
      return null;
    }
298
    if (!scopeOk && node is FocusScopeNode) {
299 300
      if (!nullOk) {
        throw FlutterError(
301 302 303 304 305 306
            'Focus.of() was called with a context that does not contain a Focus between the given '
            'context and the nearest FocusScope widget.\n'
            'No Focus ancestor could be found starting from the context that was passed to '
            'Focus.of() to the point where it found the nearest FocusScope widget. This can happen '
            'because you are using a widget that looks for a Focus ancestor, and do not have a '
            'Focus widget ancestor in the current FocusScope.\n'
307 308 309 310 311 312 313
            'The context used was:\n'
            '  $context'
        );
      }
      return null;
    }
    return node;
314 315
  }

316 317 318
  /// Returns true if the nearest enclosing [Focus] widget's node is focused.
  ///
  /// A convenience method to allow build methods to write:
319 320 321 322 323 324 325
  /// `Focus.isAt(context)` to get whether or not the nearest [Focus] above them
  /// in the widget hierarchy currently has the input focus.
  ///
  /// Returns false if no [Focus] widget is found before reaching the nearest
  /// [FocusScope], or if the root of the focus tree is reached without finding
  /// a [Focus] widget.
  static bool isAt(BuildContext context) => Focus.of(context, nullOk: true)?.hasFocus ?? false;
326

327
  @override
328 329 330 331
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(StringProperty('debugLabel', debugLabel, defaultValue: null));
    properties.add(FlagProperty('autofocus', value: autofocus, ifTrue: 'AUTOFOCUS', defaultValue: false));
332
    properties.add(DiagnosticsProperty<FocusNode>('node', focusNode, defaultValue: null));
333 334 335 336
  }

  @override
  _FocusState createState() => _FocusState();
337 338
}

339 340
class _FocusState extends State<Focus> {
  FocusNode _internalNode;
341
  FocusNode get focusNode => widget.focusNode ?? _internalNode;
342 343
  bool _hasPrimaryFocus;
  bool _canRequestFocus;
344
  bool _didAutofocus = false;
345 346 347 348 349 350 351 352 353 354 355
  FocusAttachment _focusAttachment;

  @override
  void initState() {
    super.initState();
    _initNode();
  }

  void _initNode() {
    if (widget.focusNode == null) {
      // Only create a new node if the widget doesn't have one.
356 357
      // This calls a function instead of just allocating in place because
      // _createNode is overridden in _FocusScopeState.
358 359
      _internalNode ??= _createNode();
    }
360 361 362 363 364 365
    if (widget.skipTraversal != null) {
      focusNode.skipTraversal = widget.skipTraversal;
    }
    if (widget.canRequestFocus != null) {
      focusNode.canRequestFocus = widget.canRequestFocus;
    }
366 367
    _canRequestFocus = focusNode.canRequestFocus;
    _hasPrimaryFocus = focusNode.hasPrimaryFocus;
368
    _focusAttachment = focusNode.attach(context, onKey: widget.onKey);
369

370
    // Add listener even if the _internalNode existed before, since it should
371
    // not be listening now if we're re-using a previous one because it should
372
    // have already removed its listener.
373
    focusNode.addListener(_handleFocusChanged);
374 375
  }

376 377 378 379 380 381 382
  FocusNode _createNode() {
    return FocusNode(
      debugLabel: widget.debugLabel,
      canRequestFocus: widget.canRequestFocus ?? true,
      skipTraversal: widget.skipTraversal ?? false,
    );
  }
383 384 385 386 387

  @override
  void dispose() {
    // Regardless of the node owner, we need to remove it from the tree and stop
    // listening to it.
388
    focusNode.removeListener(_handleFocusChanged);
389
    _focusAttachment.detach();
390

391 392 393 394 395
    // Don't manage the lifetime of external nodes given to the widget, just the
    // internal node.
    _internalNode?.dispose();
    super.dispose();
  }
396 397 398 399

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
400
    _focusAttachment?.reparent();
401 402 403 404
    _handleAutofocus();
  }

  void _handleAutofocus() {
405
    if (!_didAutofocus && widget.autofocus) {
406
      FocusScope.of(context).autofocus(focusNode);
407 408 409 410 411
      _didAutofocus = true;
    }
  }

  @override
412 413
  void deactivate() {
    super.deactivate();
414 415 416 417 418 419 420
    // The focus node's location in the tree is no longer valid here. But
    // we can't unfocus or remove the node from the tree because if the widget
    // is moved to a different part of the tree (via global key) it should
    // retain its focus state. That's why we temporarily park it on the root
    // focus node (via reparent) until it either gets moved to a different part
    // of the tree (via didChangeDependencies) or until it is disposed.
    _focusAttachment?.reparent();
421 422 423 424 425 426
    _didAutofocus = false;
  }

  @override
  void didUpdateWidget(Focus oldWidget) {
    super.didUpdateWidget(oldWidget);
427 428 429 430 431 432 433 434 435 436
    assert(() {
      // Only update the debug label in debug builds, and only if we own the
      // node.
      if (oldWidget.debugLabel != widget.debugLabel && _internalNode != null) {
        _internalNode.debugLabel = widget.debugLabel;
      }
      return true;
    }());

    if (oldWidget.focusNode == widget.focusNode) {
437 438 439 440 441 442
      if (widget.skipTraversal != null) {
        focusNode.skipTraversal = widget.skipTraversal;
      }
      if (widget.canRequestFocus != null) {
        focusNode.canRequestFocus = widget.canRequestFocus;
      }
443 444 445 446
    } else {
      _focusAttachment.detach();
      focusNode.removeListener(_handleFocusChanged);
      _initNode();
447
    }
448

449 450 451
    if (oldWidget.autofocus != widget.autofocus) {
      _handleAutofocus();
    }
452 453 454
  }

  void _handleFocusChanged() {
455 456 457 458
    final bool hasPrimaryFocus = focusNode.hasPrimaryFocus;
    final bool canRequestFocus = focusNode.canRequestFocus;
    if (widget.onFocusChange != null) {
      widget.onFocusChange(focusNode.hasFocus);
459
    }
460
    if (_hasPrimaryFocus != hasPrimaryFocus) {
461
      setState(() {
462
        _hasPrimaryFocus = hasPrimaryFocus;
463 464
      });
    }
465
    if (_canRequestFocus != canRequestFocus) {
466
      setState(() {
467
        _canRequestFocus = canRequestFocus;
468 469
      });
    }
470 471 472 473
  }

  @override
  Widget build(BuildContext context) {
474
    _focusAttachment.reparent();
475 476 477
    Widget child = widget.child;
    if (widget.includeSemantics) {
      child = Semantics(
478 479
        focusable: _canRequestFocus,
        focused: _hasPrimaryFocus,
480
        child: widget.child,
481 482 483 484 485
      );
    }
    return _FocusMarker(
      node: focusNode,
      child: child,
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
    );
  }
}

/// A [FocusScope] is similar to a [Focus], but also serves as a scope for other
/// [Focus]s and [FocusScope]s, grouping them together.
///
/// Like [Focus], [FocusScope] provides an [onFocusChange] as a way to be
/// notified when the focus is given to or removed from this widget.
///
/// The [onKey] argument allows specification of a key event handler that is
/// invoked when this node or one of its children has focus. Keys are handed to
/// the primary focused widget first, and then they propagate through the
/// ancestors of that node, stopping if one of them returns true from [onKey],
/// indicating that it has handled the event.
///
/// A [FocusScope] manages a [FocusScopeNode]. Managing a [FocusScopeNode] means
/// managing its lifecycle, listening for changes in focus, and re-parenting it
/// when the widget hierarchy changes. See [FocusNode] and [FocusScopeNode] for
/// more information about the details of what node management entails if not
/// using a [FocusScope] widget.
///
508 509 510 511 512 513 514 515 516 517 518 519 520 521
/// A [DefaultTraversalPolicy] widget provides the [FocusTraversalPolicy] for
/// the [FocusScopeNode]s owned by its descendant widgets. Each [FocusScopeNode]
/// has [FocusNode] descendants. The traversal policy defines what "previous
/// focus", "next focus", and "move focus in this direction" means for them.
///
/// [FocusScopeNode]s remember the last [FocusNode] that was focused within
/// their descendants, and can move that focus to the next/previous node, or a
/// node in a particular direction when the [FocusNode.nextFocus],
/// [FocusNode.previousFocus], or [FocusNode.focusInDirection] are called on a
/// [FocusNode] or [FocusScopeNode].
///
/// To move the focus, use methods on [FocusScopeNode]. For instance, to move
/// the focus to the next node, call `Focus.of(context).nextFocus()`.
///
522 523
/// See also:
///
524 525 526 527 528 529 530 531 532 533 534
///  * [FocusScopeNode], which represents a scope node in the focus hierarchy.
///  * [FocusNode], which represents a node in the focus hierarchy and has an
///    explanation of the focus system.
///  * [Focus], a widget that manages a [FocusNode] and allows easy access to
///    managing focus without having to manage the node.
///  * [FocusManager], a singleton that manages the focus and distributes key
///    events to focused nodes.
///  * [FocusTraversalPolicy], an object used to determine how to move the focus
///    to other nodes.
///  * [DefaultFocusTraversal], a widget used to configure the default focus
///    traversal policy for a widget subtree.
535 536 537 538 539 540 541 542
class FocusScope extends Focus {
  /// Creates a widget that manages a [FocusScopeNode].
  ///
  /// The [child] argument is required and must not be null.
  ///
  /// The [autofocus], and [showDecorations] arguments must not be null.
  const FocusScope({
    Key key,
543
    FocusScopeNode node,
544 545 546
    @required Widget child,
    bool autofocus = false,
    ValueChanged<bool> onFocusChange,
547 548
    bool canRequestFocus,
    bool skipTraversal,
549 550 551 552 553 554 555 556 557 558
    FocusOnKeyCallback onKey,
    String debugLabel,
  })  : assert(child != null),
        assert(autofocus != null),
        super(
          key: key,
          child: child,
          focusNode: node,
          autofocus: autofocus,
          onFocusChange: onFocusChange,
559 560
          canRequestFocus: canRequestFocus,
          skipTraversal: skipTraversal,
561 562 563 564 565 566 567 568 569 570 571 572 573
          onKey: onKey,
          debugLabel: debugLabel,
        );

  /// Returns the [FocusScopeNode] of the [FocusScope] that most tightly
  /// encloses the given [context].
  ///
  /// If this node doesn't have a [Focus] widget ancestor, then the
  /// [FocusManager.rootScope] is returned.
  ///
  /// The [context] argument must not be null.
  static FocusScopeNode of(BuildContext context) {
    assert(context != null);
574
    final _FocusMarker marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
575 576 577 578 579 580 581 582 583 584 585 586
    return marker?.notifier?.nearestScope ?? context.owner.focusManager.rootScope;
  }

  @override
  _FocusScopeState createState() => _FocusScopeState();
}

class _FocusScopeState extends _FocusState {
  @override
  FocusScopeNode _createNode() {
    return FocusScopeNode(
      debugLabel: widget.debugLabel,
587 588
      canRequestFocus: widget.canRequestFocus ?? true,
      skipTraversal: widget.skipTraversal ?? false,
589 590 591 592 593 594
    );
  }

  @override
  Widget build(BuildContext context) {
    _focusAttachment.reparent();
595
    return Semantics(
596
      explicitChildNodes: true,
597
      child: _FocusMarker(
598
        node: focusNode,
599
        child: widget.child,
600 601 602 603
      ),
    );
  }
}
604 605 606 607 608 609 610 611 612 613 614

// The InheritedWidget marker for Focus and FocusScope.
class _FocusMarker extends InheritedNotifier<FocusNode> {
  const _FocusMarker({
    Key key,
    @required FocusNode node,
    @required Widget child,
  })  : assert(node != null),
        assert(child != null),
        super(key: key, notifier: node, child: child);
}