performLayout method

  1. @override
void performLayout()
override

Do the work of computing the layout for this render object.

Do not call this function directly: call layout instead. This function is called by layout when there is actually work to be done by this render object during layout. The layout constraints provided by your parent are available via the constraints getter.

If sizedByParent is true, then this function should not actually change the dimensions of this render object. Instead, that work should be done by performResize. If sizedByParent is false, then this function should both change the dimensions of this render object and instruct its children to layout.

In implementing this function, you must call layout on each of your children, passing true for parentUsesSize if your layout information is dependent on your child's layout information. Passing true for parentUsesSize ensures that this render object will undergo layout if the child undergoes layout. Otherwise, the child can change its layout information without informing this render object.

Some special RenderObject subclasses (such as the one used by OverlayPortal.overlayChildLayoutBuilder) call applyPaintTransform in their performLayout implementation. To ensure such RenderObjects get the up-to-date paint transform, RenderObject subclasses should typically update the paint transform (as reported by applyPaintTransform) in this method instead of paint.

Implementation

@override
void performLayout() {
  size = constraints.biggest;

  // Content extents come from the row count and the resolved column widths,
  // so the scroll positions can be settled before any child does work.
  //
  // The horizontal extent is the *scrolling* band's, not the whole content's:
  // pinned columns occupy screen width that the scroll never recovers, so
  // measuring the scrollable range against the full viewport would let the
  // user scroll past the last unpinned column by exactly the pinned width.
  final maxVertical = math.max(0.0, contentHeight - size.height);
  final maxHorizontal = maxHorizontalOffset;

  _vertical
    ..applyViewportDimension(size.height)
    ..applyContentDimensions(0.0, maxVertical);
  _horizontal
    ..applyViewportDimension(_scrollableExtent)
    ..applyContentDimensions(0.0, maxHorizontal);

  _verticalOffset = _vertical.hasPixels
      ? _vertical.pixels.clamp(0.0, maxVertical)
      : 0.0;
  _horizontalOffset = _horizontal.hasPixels
      ? _horizontal.pixels.clamp(0.0, maxHorizontal)
      : 0.0;

  // Windowing goes through the metrics rather than dividing by a height, so
  // the uniform case stays arithmetic and the measured case becomes a binary
  // search without anything here changing.
  final anchor = _rowMetrics.clampedRowAt(_verticalOffset);
  final first = math.max(0, anchor - _overscanRows);
  final span =
      _rowMetrics.rowsSpanning(anchor, size.height) + _overscanRows * 2;
  if (first != _firstVisibleRow || span != _visibleRowCount) {
    _firstVisibleRow = first;
    _visibleRowCount = span;
    _byCell.clear();
    markNeedsSemanticsUpdate();
  }

  final window = (_firstVisibleRow, _lastVisibleRow);
  final build = cellBuilder;
  if (build != null && (_cellsNeedBuild || window != _builtWindow)) {
    _cellsNeedBuild = false;
    _builtWindow = window;
    invokeLayoutCallback<BoxConstraints>((_) => build(window.$1, window.$2));
  }

  // Overlay children are scoped to the window — widget cells by the builder
  // above, the editor by the widget layer — so all that remains is to give
  // each one its cell box.
  var child = firstChild;
  while (child != null) {
    final data = child.parentData! as FitGridCellParentData;
    final columnIndex = data.columnIndex;
    final rowIndex = data.rowIndex;

    if (columnIndex == FitGridCellParentData.fullRow) {
      final inRange = rowIndex >= 0 && rowIndex < rowCount;
      child.layout(
        BoxConstraints.tightFor(
          width: size.width,
          height: inRange ? _rowMetrics.heightOf(rowIndex) : 0.0,
        ),
        parentUsesSize: false,
      );
      data.offset = Offset(
        0,
        (inRange ? _rowMetrics.offsetOf(rowIndex) : 0.0) - _verticalOffset,
      );
    } else if (columnIndex < 0 || columnIndex >= _columnLayout.length) {
      // Stale tag — lay out degenerately rather than throwing. A child that
      // cannot be placed must still be laid out, or the semantics and paint
      // phases will trip over an unlaid-out render object.
      child.layout(BoxConstraints.tight(Size.zero), parentUsesSize: false);
      data.offset = Offset.zero;
    } else {
      final width = _columnLayout.widths[columnIndex];
      final inRange = rowIndex >= 0 && rowIndex < rowCount;
      child.layout(
        BoxConstraints.tightFor(
          width: width,
          height: inRange ? _rowMetrics.heightOf(rowIndex) : 0.0,
        ),
        parentUsesSize: false,
      );
      data.offset = Offset(
        _screenLeft(columnIndex),
        (inRange ? _rowMetrics.offsetOf(rowIndex) : 0.0) - _verticalOffset,
      );
    }
    child = data.nextSibling;
  }
  _layoutSticky();
}