layout method

void layout(
  1. Constraints constraints,
  2. {bool parentUsesSize = false}
)

Compute the layout for this render object.

This method is the main entry point for parents to ask their children to update their layout information. The parent passes a constraints object, which informs the child as to which layouts are permissible. The child is required to obey the given constraints.

If the parent reads information computed during the child's layout, the parent must pass true for parentUsesSize. In that case, the parent will be marked as needing layout whenever the child is marked as needing layout because the parent's layout information depends on the child's layout information. If the parent uses the default value (false) for parentUsesSize, the child can change its layout information (subject to the given constraints) without informing the parent.

Subclasses should not override layout directly. Instead, they should override performResize and/or performLayout. The layout method delegates the actual work to performResize and performLayout.

The parent's performLayout method should call the layout of all its children unconditionally. It is the layout method's responsibility (as implemented here) to return early if the child does not need to do any work to update its layout information.

Implementation

@pragma('vm:notify-debugger-on-exception')
void layout(Constraints constraints, { bool parentUsesSize = false }) {
  assert(!_debugDisposed);
  if (!kReleaseMode && debugProfileLayoutsEnabled) {
    Map<String, String>? debugTimelineArguments;
    assert(() {
      if (debugEnhanceLayoutTimelineArguments) {
        debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
      }
      return true;
    }());
    FlutterTimeline.startSync(
      '$runtimeType',
      arguments: debugTimelineArguments,
    );
  }
  assert(constraints.debugAssertIsValid(
    isAppliedConstraint: true,
    informationCollector: () {
      final List<String> stack = StackTrace.current.toString().split('\n');
      int? targetFrame;
      final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +Render(?:Object|Box).layout \(');
      for (int i = 0; i < stack.length; i += 1) {
        if (layoutFramePattern.matchAsPrefix(stack[i]) != null) {
          targetFrame = i + 1;
        } else if (targetFrame != null) {
          break;
        }
      }
      if (targetFrame != null && targetFrame < stack.length) {
        final Pattern targetFramePattern = RegExp(r'^#[0-9]+ +(.+)$');
        final Match? targetFrameMatch = targetFramePattern.matchAsPrefix(stack[targetFrame]);
        final String? problemFunction = (targetFrameMatch != null && targetFrameMatch.groupCount > 0) ? targetFrameMatch.group(1) : stack[targetFrame].trim();
        return <DiagnosticsNode>[
          ErrorDescription(
            "These invalid constraints were provided to $runtimeType's layout() "
            'function by the following function, which probably computed the '
            'invalid constraints in question:\n'
            '  $problemFunction',
          ),
        ];
      }
      return <DiagnosticsNode>[];
    },
  ));
  assert(!_debugDoingThisResize);
  assert(!_debugDoingThisLayout);
  final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject;
  final RenderObject relayoutBoundary = isRelayoutBoundary ? this : parent!._relayoutBoundary!;
  assert(() {
    _debugCanParentUseSize = parentUsesSize;
    return true;
  }());

  if (!_needsLayout && constraints == _constraints) {
    assert(() {
      // in case parentUsesSize changed since the last invocation, set size
      // to itself, so it has the right internal debug values.
      _debugDoingThisResize = sizedByParent;
      _debugDoingThisLayout = !sizedByParent;
      final RenderObject? debugPreviousActiveLayout = _debugActiveLayout;
      _debugActiveLayout = this;
      debugResetSize();
      _debugActiveLayout = debugPreviousActiveLayout;
      _debugDoingThisLayout = false;
      _debugDoingThisResize = false;
      return true;
    }());

    if (relayoutBoundary != _relayoutBoundary) {
      _relayoutBoundary = relayoutBoundary;
      visitChildren(_propagateRelayoutBoundaryToChild);
    }

    if (!kReleaseMode && debugProfileLayoutsEnabled) {
      FlutterTimeline.finishSync();
    }
    return;
  }
  _constraints = constraints;
  if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
    // The local relayout boundary has changed, must notify children in case
    // they also need updating. Otherwise, they will be confused about what
    // their actual relayout boundary is later.
    visitChildren(_cleanChildRelayoutBoundary);
  }
  _relayoutBoundary = relayoutBoundary;
  assert(!_debugMutationsLocked);
  assert(!_doingThisLayoutWithCallback);
  assert(() {
    _debugMutationsLocked = true;
    if (debugPrintLayouts) {
      debugPrint('Laying out (${sizedByParent ? "with separate resize" : "with resize allowed"}) $this');
    }
    return true;
  }());
  if (sizedByParent) {
    assert(() {
      _debugDoingThisResize = true;
      return true;
    }());
    try {
      performResize();
      assert(() {
        debugAssertDoesMeetConstraints();
        return true;
      }());
    } catch (e, stack) {
      _reportException('performResize', e, stack);
    }
    assert(() {
      _debugDoingThisResize = false;
      return true;
    }());
  }
  RenderObject? debugPreviousActiveLayout;
  assert(() {
    _debugDoingThisLayout = true;
    debugPreviousActiveLayout = _debugActiveLayout;
    _debugActiveLayout = this;
    return true;
  }());
  try {
    performLayout();
    markNeedsSemanticsUpdate();
    assert(() {
      debugAssertDoesMeetConstraints();
      return true;
    }());
  } catch (e, stack) {
    _reportException('performLayout', e, stack);
  }
  assert(() {
    _debugActiveLayout = debugPreviousActiveLayout;
    _debugDoingThisLayout = false;
    _debugMutationsLocked = false;
    return true;
  }());
  _needsLayout = false;
  markNeedsPaint();

  if (!kReleaseMode && debugProfileLayoutsEnabled) {
    FlutterTimeline.finishSync();
  }
}