inDirection method

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

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 policy data 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:
        requestFocusCallback(
          firstFocus,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
        );
      case TraversalDirection.right:
      case TraversalDirection.down:
        requestFocusCallback(
          firstFocus,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
        );
    }
    return true;
  }
  if (_popPolicyDataIfNeeded(direction, nearestScope, focusedChild)) {
    return true;
  }
  FocusNode? found;
  final ScrollableState? focusedScrollable = Scrollable.maybeOf(focusedChild.context!);
  switch (direction) {
    case TraversalDirection.down:
    case TraversalDirection.up:
      Iterable<FocusNode> eligibleNodes = _sortAndFilterVertically(direction, focusedChild.rect, nearestScope.traversalDescendants);
      if (eligibleNodes.isEmpty) {
        break;
      }
      if (focusedScrollable != null && !focusedScrollable.position.atEdge) {
        final Iterable<FocusNode> filteredEligibleNodes = eligibleNodes.where((FocusNode node) => Scrollable.maybeOf(node.context!) == focusedScrollable);
        if (filteredEligibleNodes.isNotEmpty) {
          eligibleNodes = filteredEligibleNodes;
        }
      }
      if (direction == TraversalDirection.up) {
        eligibleNodes = eligibleNodes.toList().reversed;
      }
      // 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 = eligibleNodes.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
      if (inBand.isNotEmpty) {
        found = _sortByDistancePreferVertical(focusedChild.rect.center, inBand).first;
        break;
      }
      // Only out-of-band targets are eligible, so pick the one that is
      // closest to the center line horizontally, and if any are the same
      // distance horizontally, pick the closest one of those vertically.
      found = _sortClosestEdgesByDistancePreferHorizontal(focusedChild.rect.center, eligibleNodes).first;
    case TraversalDirection.right:
    case TraversalDirection.left:
      Iterable<FocusNode> eligibleNodes = _sortAndFilterHorizontally(direction, focusedChild.rect, nearestScope.traversalDescendants);
      if (eligibleNodes.isEmpty) {
        break;
      }
      if (focusedScrollable != null && !focusedScrollable.position.atEdge) {
        final Iterable<FocusNode> filteredEligibleNodes = eligibleNodes.where((FocusNode node) => Scrollable.maybeOf(node.context!) == focusedScrollable);
        if (filteredEligibleNodes.isNotEmpty) {
          eligibleNodes = filteredEligibleNodes;
        }
      }
      if (direction == TraversalDirection.left) {
        eligibleNodes = eligibleNodes.toList().reversed;
      }
      // 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 = eligibleNodes.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
      if (inBand.isNotEmpty) {
        found = _sortByDistancePreferHorizontal(focusedChild.rect.center, inBand).first;
        break;
      }
      // Only out-of-band targets are eligible, so pick the one that is
      // closest to the center line vertically, and if any are the same
      // distance vertically, pick the closest one of those horizontally.
      found = _sortClosestEdgesByDistancePreferVertical(focusedChild.rect.center, eligibleNodes).first;
  }
  if (found != null) {
    _pushPolicyData(direction, nearestScope, focusedChild);
    switch (direction) {
      case TraversalDirection.up:
      case TraversalDirection.left:
        requestFocusCallback(
          found,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
        );
      case TraversalDirection.down:
      case TraversalDirection.right:
        requestFocusCallback(
          found,
          alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
        );
    }
    return true;
  }
  return false;
}