inDirection method

  1. @mustCallSuper
  2. @override
bool inDirection (
  1. FocusNode currentNode,
  2. TraversalDirection direction
)
@mustCallSuper, inherited

Focuses the next widget in the given direction in the FocusScope that contains the currentNode.

This determines what the next node to receive focus in the given direction will be by inspecting the node tree, and then calling FocusNode.requestFocus on it.

Returns true if it successfully found a node and requested focus.

Maintains a stack of previous locations that have been visited on the policyData for the affected FocusScopeNode. If the previous direction was the opposite of the current direction, then the this policy will request focus on the previously focused node. Change to another direction other than the current one or its opposite will clear the stack.

If this function returns true when called by a subclass, then the subclass should return true and not request focus from any node.

Implementation

@mustCallSuper
@override
bool inDirection(FocusNode currentNode, TraversalDirection direction) {
  final FocusScopeNode nearestScope = currentNode.nearestScope;
  final FocusNode focusedChild = nearestScope.focusedChild;
  if (focusedChild == null) {
    final FocusNode firstFocus = findFirstFocusInDirection(currentNode, direction) ?? currentNode;
    switch (direction) {
      case TraversalDirection.up:
      case TraversalDirection.left:
        _focusAndEnsureVisible(
          firstFocus,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
        );
        break;
      case TraversalDirection.right:
      case TraversalDirection.down:
        _focusAndEnsureVisible(
          firstFocus,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
        );
        break;
    }
    return true;
  }
  if (_popPolicyDataIfNeeded(direction, nearestScope, focusedChild)) {
    return true;
  }
  FocusNode found;
  final ScrollableState focusedScrollable = Scrollable.of(focusedChild.context);
  switch (direction) {
    case TraversalDirection.down:
    case TraversalDirection.up:
      Iterable<FocusNode> eligibleNodes = _sortAndFilterVertically(
        direction,
        focusedChild.rect,
        nearestScope.traversalDescendants,
      );
      if (focusedScrollable != null && !focusedScrollable.position.atEdge) {
        final Iterable<FocusNode> filteredEligibleNodes = eligibleNodes.where((FocusNode node) => Scrollable.of(node.context) == focusedScrollable);
        if (filteredEligibleNodes.isNotEmpty) {
          eligibleNodes = filteredEligibleNodes;
        }
      }
      if (eligibleNodes.isEmpty) {
        break;
      }
      List<FocusNode> sorted = eligibleNodes.toList();
      if (direction == TraversalDirection.up) {
        sorted = sorted.reversed.toList();
      }
      // Find any nodes that intersect the band of the focused child.
      final Rect band = Rect.fromLTRB(focusedChild.rect.left, -double.infinity, focusedChild.rect.right, double.infinity);
      final Iterable<FocusNode> inBand = sorted.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
      if (inBand.isNotEmpty) {
        // The inBand list is already sorted by horizontal distance, so pick
        // the closest one.
        found = inBand.first;
        break;
      }
      // Only out-of-band targets remain, so pick the one that is closest the
      // to the center line horizontally.
      mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) {
        return (a.rect.center.dx - focusedChild.rect.center.dx).abs().compareTo((b.rect.center.dx - focusedChild.rect.center.dx).abs());
      });
      found = sorted.first;
      break;
    case TraversalDirection.right:
    case TraversalDirection.left:
      Iterable<FocusNode> eligibleNodes = _sortAndFilterHorizontally(direction, focusedChild.rect, nearestScope);
      if (focusedScrollable != null && !focusedScrollable.position.atEdge) {
        final Iterable<FocusNode> filteredEligibleNodes = eligibleNodes.where((FocusNode node) => Scrollable.of(node.context) == focusedScrollable);
        if (filteredEligibleNodes.isNotEmpty) {
          eligibleNodes = filteredEligibleNodes;
        }
      }
      if (eligibleNodes.isEmpty) {
        break;
      }
      List<FocusNode> sorted = eligibleNodes.toList();
      if (direction == TraversalDirection.left) {
        sorted = sorted.reversed.toList();
      }
      // Find any nodes that intersect the band of the focused child.
      final Rect band = Rect.fromLTRB(-double.infinity, focusedChild.rect.top, double.infinity, focusedChild.rect.bottom);
      final Iterable<FocusNode> inBand = sorted.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
      if (inBand.isNotEmpty) {
        // The inBand list is already sorted by vertical distance, so pick the
        // closest one.
        found = inBand.first;
        break;
      }
      // Only out-of-band targets remain, so pick the one that is closest the
      // to the center line vertically.
      mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) {
        return (a.rect.center.dy - focusedChild.rect.center.dy).abs().compareTo((b.rect.center.dy - focusedChild.rect.center.dy).abs());
      });
      found = sorted.first;
      break;
  }
  if (found != null) {
    _pushPolicyData(direction, nearestScope, focusedChild);
    switch (direction) {
      case TraversalDirection.up:
      case TraversalDirection.left:
        _focusAndEnsureVisible(
          found,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
        );
        break;
      case TraversalDirection.down:
      case TraversalDirection.right:
        _focusAndEnsureVisible(
          found,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
        );
        break;
    }
    return true;
  }
  return false;
}