FocusNode class

An object that can be used by a stateful widget to obtain the keyboard focus and to handle keyboard events.

Please see the Focus and FocusScope widgets, which are utility widgets that manage their own FocusNodes and FocusScopeNodes, respectively. If they aren't appropriate, FocusNodes can be managed directly.

FocusNodes are persistent objects that form a focus tree that is a representation of the widgets in the hierarchy that are interested in focus. A focus node might need to be created if it is passed in from an ancestor of a Focus widget to control the focus of the children from the ancestor, or a widget might need to host one if the widget subsystem is not being used, or if the Focus and FocusScope widgets provide insufficient control.

FocusNodes are organized into scopes (see FocusScopeNode), which form sub-trees of nodes that can be traversed as a group. Within a scope, the most recent nodes to have focus are remembered, and if a node is focused and then removed, the previous node receives focus again.

The focus node hierarchy can be traversed using the parent, children, ancestors and descendants accessors.

FocusNodes are ChangeNotifiers, so a listener can be registered to receive a notification when the focus changes. If the Focus and FocusScope widgets are being used to manage the nodes, consider establishing an InheritedWidget dependency on them by calling Focus.of or FocusScope.of instead. Focus.hasFocus can also be used to establish a similar dependency, especially if all that is needed is to determine whether or not the widget is focused at build time.

To see the focus tree in the debug console, call debugDumpFocusTree. To get the focus tree as a string, call debugDescribeFocusTree.

Lifecycle

There are several actors involved in the lifecycle of a FocusNode/FocusScopeNode. They are created and disposed by their owner, attached, detached, and reparented using a FocusAttachment by their host (which must be owned by the State of a StatefulWidget), and they are managed by the FocusManager. Different parts of the FocusNode API are intended for these different actors.

FocusNodes (and hence FocusScopeNodes) are persistent objects that form part of a focus tree that is a sparse representation of the widgets in the hierarchy that are interested in receiving keyboard events. They must be managed like other persistent state, which is typically done by a StatefulWidget that owns the node. A stateful widget that owns a focus scope node must call dispose from its State.dispose method.

Once created, a FocusNode must be attached to the widget tree via a FocusAttachment object. This attachment is created by calling attach, usually from the State.initState method. If the hosting widget is updated to have a different focus node, then the updated node needs to be attached in State.didUpdateWidget, after calling detach on the previous FocusAttachment.

Because FocusNodes form a sparse representation of the widget tree, they must be updated whenever the widget tree is rebuilt. This is done by calling FocusAttachment.reparent, usually from the State.build or State.didChangeDependencies methods of the widget that represents the focused region, so that the BuildContext assigned to the FocusScopeNode can be tracked (the context is used to obtain the RenderObject, from which the geometry of focused regions can be determined).

Creating a FocusNode each time State.build is invoked will cause the focus to be lost each time the widget is built, which is usually not desired behavior (call unfocus if losing focus is desired).

If, as is common, the hosting StatefulWidget is also the owner of the focus node, then it will also call dispose from its State.dispose (in which case the detach may be skipped, since dispose will automatically detach). If another object owns the focus node, then it must call dispose when the node is done being used.

Key Event Propagation

The FocusManager receives all key events and will pass them to the focused nodes. It starts with the node with the primary focus, and will call the onKey callback for that node. If the callback returns false, indicating that it did not handle the event, the FocusManager will move to the parent of that node and call its onKey. If that onKey returns true, then it will stop propagating the event. If it reaches the root FocusScopeNode, FocusManager.rootScope, the event is discarded.

Focus Traversal

The term traversal, sometimes called tab traversal, refers to moving the focus from one widget to the next in a particular order (also sometimes referred to as the tab order, since the TAB key is often bound to the action to move to the next widget).

To give focus to the logical next or previous widget in the UI, call the nextFocus or previousFocus methods. To give the focus to a widget in a particular direction, call the focusInDirection method.

The policy for what the next or previous widget is, or the widget in a particular direction, is determined by the FocusTraversalPolicy in force.

The ambient policy is determined by looking up the widget hierarchy for a DefaultFocusTraversal widget, and obtaining the focus traversal policy from it. Different focus nodes can inherit difference policies, so part of the app can go in widget order, and part can go in reading order, depending upon the use case.

Predefined policies include WidgetOrderFocusTraversalPolicy, ReadingOrderTraversalPolicy, and DirectionalFocusTraversalPolicyMixin, but custom policies can be built based upon these policies.

This example shows how a FocusNode should be managed if not using the Focus or FocusScope widgets. See the Focus widget for a similar example using Focus and FocusScope widgets.
import 'package:flutter/services.dart';

// ...

class ColorfulButton extends StatefulWidget {
  ColorfulButton({Key key}) : super(key: key);

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

class _ColorfulButtonState extends State<ColorfulButton> {
  FocusNode _node;
  bool _focused = false;
  FocusAttachment _nodeAttachment;
  Color _color = Colors.white;

  @override
  void initState() {
    super.initState();
    _node = FocusNode(debugLabel: 'Button');
    _node.addListener(_handleFocusChange);
    _nodeAttachment = _node.attach(context, onKey: _handleKeyPress);
  }

  void _handleFocusChange() {
    if (_node.hasFocus != _focused) {
      setState(() {
        _focused = _node.hasFocus;
      });
    }
  }

  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
  void dispose() {
    _node.removeListener(_handleFocusChange);
    // The attachment will automatically be detached in dispose().
    _node.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    _nodeAttachment.reparent();
    return GestureDetector(
      onTap: () {
        if (_focused) {
            _node.unfocus();
        } else {
           _node.requestFocus();
        }
      },
      child: Center(
        child: Container(
          width: 400,
          height: 100,
          color: _focused ? _color : Colors.white,
          alignment: Alignment.center,
          child: Text(
              _focused ? "I'm in color! Press R,G,B!" : 'Press to focus'),
        ),
      ),
    );
  }
}

// ...

Widget build(BuildContext context) {
  final TextTheme textTheme = Theme.of(context).textTheme;
  return DefaultTextStyle(
    style: textTheme.display1,
    child: ColorfulButton(),
  );
}

See also:

  • Focus, a widget that manages a FocusNode and provides access to focus information and actions to its descendant widgets.
  • FocusScope, a widget that manages a FocusScopeNode and provides access to scope information and actions to its descendant widgets.
  • FocusAttachment, a widget that connects a FocusScopeNode to the widget tree.
  • FocusManager, a singleton that manages the focus and distributes key events to focused nodes.
  • FocusTraversalPolicy, a class 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.
Mixed in types
Implementers

Constructors

FocusNode({String debugLabel, FocusOnKeyCallback onKey, bool skipTraversal: false, bool canRequestFocus: true })
Creates a focus node. [...]

Properties

ancestors Iterable<FocusNode>
An Iterable over the ancestors of this node. [...]
read-only
canRequestFocus bool
If true, this focus node may request the primary focus. [...]
read / write
children Iterable<FocusNode>
An iterator over the children of this node.
read-only
context BuildContext
The context that was supplied to attach. [...]
read-only
debugLabel String
A debug label that is used for diagnostic output. [...]
read / write
descendants Iterable<FocusNode>
An Iterable over the hierarchy of children below this one, in depth-first order.
read-only
enclosingScope FocusScopeNode
Returns the nearest enclosing scope node above this node, or null if the node has not yet be added to the focus tree. [...]
read-only
hasFocus bool
Whether this node has input focus. [...]
read-only
hasPrimaryFocus bool
Returns true if this node currently has the application-wide input focus. [...]
read-only
highlightMode FocusHighlightMode
Returns the FocusHighlightMode that is currently in effect for this node.
read-only
nearestScope FocusScopeNode
Returns the nearest enclosing scope node above this node, including this node, if it's a scope. [...]
read-only
offset Offset
Returns the global offset to the upper left corner of the attached widget's RenderObject, in logical units.
read-only
onKey FocusOnKeyCallback
Called if this focus node receives a key event while focused (i.e. when hasFocus returns true). [...]
read-only
parent FocusNode
Returns the parent node for this object. [...]
read-only
rect Rect
Returns the global rectangle of the attached widget's RenderObject, in logical units.
read-only
size Size
Returns the size of the attached widget's RenderObject, in logical units.
read-only
skipTraversal bool
If true, tells the focus traversal policy to skip over this node for purposes of the traversal algorithm. [...]
read / write
traversalChildren Iterable<FocusNode>
An iterator over the children that are allowed to be traversed by the FocusTraversalPolicy.
read-only
traversalDescendants Iterable<FocusNode>
Returns all descendants which do not have the skipTraversal flag set.
read-only
hashCode int
The hash code for this object. [...]
read-only, inherited
hasListeners bool
Whether any listeners are currently registered. [...]
@protected, read-only, inherited
runtimeType Type
A representation of the runtime type of the object.
read-only, inherited

Methods

attach(BuildContext context, { FocusOnKeyCallback onKey }) FocusAttachment
Called by the host StatefulWidget to attach a FocusNode to the widget tree. [...]
@mustCallSuper
consumeKeyboardToken() bool
Removes the keyboard token from this focus node if it has one. [...]
debugDescribeChildren() List<DiagnosticsNode>
Returns a list of DiagnosticsNode objects describing this node's children. [...]
override
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node. [...]
override
dispose() → void
Discards any resources used by the object. After this is called, the object is not in a usable state and should be discarded (calls to addListener and removeListener will throw after the object is disposed). [...]
override
focusInDirection(TraversalDirection direction) bool
Request to move the focus to the nearest focus node in the given direction, by calling the FocusTraversalPolicy.inDirection method. [...]
nextFocus() bool
Request to move the focus to the next focus node, by calling the FocusTraversalPolicy.next method. [...]
previousFocus() bool
Request to move the focus to the previous focus node, by calling the FocusTraversalPolicy.previous method. [...]
requestFocus([FocusNode node ]) → void
Requests the primary focus for this node, or for a supplied node, which will also give focus to its ancestors. [...]
unfocus() → void
Removes focus from a node that has the primary focus, and cancels any outstanding requests to focus it. [...]
addListener(VoidCallback listener) → void
Register a closure to be called when the object changes. [...]
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed. [...]
inherited
notifyListeners() → void
Call all the registered listeners. [...]
@protected, @visibleForTesting, inherited
removeListener(VoidCallback listener) → void
Remove a previously registered closure from the list of closures that are notified when the object changes. [...]
inherited
toDiagnosticsNode({String name, DiagnosticsTreeStyle style }) DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep. [...]
inherited
toString({DiagnosticLevel minLevel: DiagnosticLevel.debug }) String
Returns a string representation of this object.
inherited
toStringDeep({String prefixLineOne: '', String prefixOtherLines, DiagnosticLevel minLevel: DiagnosticLevel.debug }) String
Returns a string representation of this node and its descendants. [...]
inherited
toStringShallow({String joiner: ', ', DiagnosticLevel minLevel: DiagnosticLevel.debug }) String
Returns a one-line detailed description of the object. [...]
inherited
toStringShort() String
A brief description of this object, usually just the runtimeType and the hashCode. [...]
inherited

Operators

operator ==(dynamic other) bool
The equality operator. [...]
inherited