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.

Implementation

@override
void performLayout() {
  double rowHeaderWidth = 0;
  if (rowHeader != null) {
    rowHeaderWidth = rowHeader!.getMaxIntrinsicWidth(double.infinity);
  }

  double columnHeaderHeight = 0;
  if (columnHeader != null) {
    columnHeaderHeight = columnHeader!.getMaxIntrinsicHeight(double.infinity);
  }

  double viewWidth = 0;
  double viewHeight = 0;
  double viewportWidth = 0;
  double viewportHeight = 0;
  late double previousVerticalScrollBarWidth;
  late double previousHorizontalScrollBarHeight;
  double verticalScrollBarWidth = _cachedVerticalScrollBarWidth;
  double horizontalScrollBarHeight = _cachedHorizontalScrollBarHeight;
  int i = 0;

  bool scrollBarSizesChanged() {
    return horizontalScrollBarHeight != previousHorizontalScrollBarHeight ||
        verticalScrollBarWidth != previousVerticalScrollBarWidth;
  }

  do {
    previousHorizontalScrollBarHeight = horizontalScrollBarHeight;
    previousVerticalScrollBarWidth = verticalScrollBarWidth;

    final _ScrollPaneViewportResolver viewportResolver = _ScrollPaneViewportResolver(
      constraints: constraints,
      offset: scrollController.scrollOffset,
      sizeAdjustment: Offset(
        rowHeaderWidth + verticalScrollBarWidth,
        columnHeaderHeight + horizontalScrollBarHeight,
      ),
    );

    if (view != null) {
      double minWidth = 0;
      double maxWidth = double.infinity;
      double minHeight = 0;
      double maxHeight = double.infinity;

      switch (horizontalScrollBarPolicy) {
        case ScrollBarPolicy.stretch:
          if (constraints.hasBoundedWidth) {
            minWidth = max(constraints.minWidth - rowHeaderWidth - verticalScrollBarWidth, 0);
            maxWidth = max(constraints.maxWidth - rowHeaderWidth - verticalScrollBarWidth, 0);
          }
          break;
        case ScrollBarPolicy.expand:
          if (constraints.hasBoundedWidth) {
            minWidth = max(constraints.minWidth - rowHeaderWidth - verticalScrollBarWidth, 0);
          }
          break;
        case ScrollBarPolicy.always:
        case ScrollBarPolicy.auto:
        case ScrollBarPolicy.never:
          // Unbounded width constraints
          break;
      }

      switch (verticalScrollBarPolicy) {
        case ScrollBarPolicy.stretch:
          if (constraints.hasBoundedHeight) {
            minHeight =
                max(constraints.minHeight - columnHeaderHeight - horizontalScrollBarHeight, 0);
            maxHeight =
                max(constraints.maxHeight - columnHeaderHeight - horizontalScrollBarHeight, 0);
          }
          break;
        case ScrollBarPolicy.expand:
          if (constraints.hasBoundedHeight) {
            minHeight =
                max(constraints.minHeight - columnHeaderHeight - horizontalScrollBarHeight, 0);
          }
          break;
        case ScrollBarPolicy.always:
        case ScrollBarPolicy.auto:
        case ScrollBarPolicy.never:
          // Unbounded height constraints
          break;
      }

      final SegmentConstraints viewConstraints = SegmentConstraints(
        minWidth: minWidth,
        maxWidth: maxWidth,
        minHeight: minHeight,
        maxHeight: maxHeight,
        viewportResolver: viewportResolver,
      );
      view!.layout(viewConstraints, parentUsesSize: true);
      viewWidth = view!.size.width;
      viewHeight = view!.size.height;
    }

    final Rect viewportRect = viewportResolver.resolve(Size(viewWidth, viewHeight));
    viewportWidth = viewportRect.width;
    viewportHeight = viewportRect.height;

    if (horizontalScrollBarPolicy == ScrollBarPolicy.always ||
        (horizontalScrollBarPolicy == ScrollBarPolicy.auto && viewWidth > viewportWidth) ||
        (horizontalScrollBarPolicy == ScrollBarPolicy.expand && viewWidth > viewportWidth)) {
      horizontalScrollBarHeight = horizontalScrollBar!.getMinIntrinsicHeight(double.infinity);
    } else {
      horizontalScrollBarHeight = 0;
    }

    if (verticalScrollBarPolicy == ScrollBarPolicy.always ||
        (verticalScrollBarPolicy == ScrollBarPolicy.auto && viewHeight > viewportHeight) ||
        (verticalScrollBarPolicy == ScrollBarPolicy.expand && viewHeight > viewportHeight)) {
      verticalScrollBarWidth = verticalScrollBar!.getMinIntrinsicWidth(double.infinity);
    } else {
      verticalScrollBarWidth = 0;
    }

    size = constraints.constrainDimensions(
      viewportWidth + rowHeaderWidth + verticalScrollBarWidth,
      viewportHeight + columnHeaderHeight + horizontalScrollBarHeight,
    );
  } while (++i <= RenderScrollPane._maxLayoutPasses && scrollBarSizesChanged());

  final double width = size.width;
  final double height = size.height;

  _cachedHorizontalScrollBarHeight = horizontalScrollBarHeight;
  _cachedVerticalScrollBarWidth = verticalScrollBarWidth;

  if (i > RenderScrollPane._maxLayoutPasses) {
    assert(() {
      throw FlutterError('A RenderScrollPane exceeded its maximum number of layout cycles.\n'
          'RenderScrollPane render objects, during layout, can retry if the introduction '
          'of scrollbars changes the constraints for one of its children.\n'
          'In the case of this RenderScrollPane object, however, this happened $RenderScrollPane._maxLayoutPasses '
          'times and still there was no consensus on the constraints. This usually '
          'indicates a bug.');
    }());
  }

  if (columnHeader != null) {
    final SegmentConstraints columnHeaderConstraints = SegmentConstraints.tightFor(
      width: viewWidth,
      height: columnHeaderHeight,
      viewportResolver: StaticViewportResolver.fromParts(
        offset: Offset(scrollController.scrollOffset.dx, 0),
        size: Size(viewportWidth, columnHeaderHeight),
      ),
    );
    columnHeader!.layout(columnHeaderConstraints, parentUsesSize: true);
  }

  if (rowHeader != null) {
    final SegmentConstraints rowHeaderConstraints = SegmentConstraints.tightFor(
      width: rowHeaderWidth,
      height: viewHeight,
      viewportResolver: StaticViewportResolver.fromParts(
        offset: Offset(0, scrollController.scrollOffset.dy),
        size: Size(rowHeaderWidth, viewportHeight),
      ),
    );
    rowHeader!.layout(rowHeaderConstraints, parentUsesSize: true);
  }

  if (columnHeaderHeight > 0 && rowHeaderWidth > 0) {
    _ScrollPaneParentData parentData = parentDataFor(topLeftCorner!);
    parentData.offset = Offset.zero;
    parentData.visible = true;
    topLeftCorner!.layout(BoxConstraints.tightFor(
      width: rowHeaderWidth,
      height: columnHeaderHeight,
    ));
  } else {
    topLeftCorner!.layout(BoxConstraints.tight(Size.zero));
    parentDataFor(topLeftCorner!).visible = false;
  }

  if (rowHeaderWidth > 0 && horizontalScrollBarHeight > 0) {
    _ScrollPaneParentData parentData = parentDataFor(bottomLeftCorner!);
    parentData.offset = Offset(0, height - horizontalScrollBarHeight);
    parentData.visible = true;
    bottomLeftCorner!.layout(BoxConstraints.tightFor(
      width: rowHeaderWidth,
      height: horizontalScrollBarHeight,
    ));
  } else {
    bottomLeftCorner!.layout(BoxConstraints.tight(Size.zero));
    parentDataFor(bottomLeftCorner!).visible = false;
  }

  if (verticalScrollBarWidth > 0 && horizontalScrollBarHeight > 0) {
    _ScrollPaneParentData parentData = parentDataFor(bottomRightCorner!);
    parentData.offset = Offset(
      width - verticalScrollBarWidth,
      height - horizontalScrollBarHeight,
    );
    parentData.visible = true;
    bottomRightCorner!.layout(BoxConstraints.tightFor(
      width: verticalScrollBarWidth,
      height: horizontalScrollBarHeight,
    ));
  } else {
    bottomRightCorner!.layout(BoxConstraints.tight(Size.zero));
    parentDataFor(bottomRightCorner!).visible = false;
  }

  if (columnHeaderHeight > 0 && verticalScrollBarWidth > 0) {
    _ScrollPaneParentData parentData = parentDataFor(topRightCorner!);
    parentData.offset = Offset(width - verticalScrollBarWidth, 0);
    parentData.visible = true;
    topRightCorner!.layout(BoxConstraints.tightFor(
      width: verticalScrollBarWidth,
      height: columnHeaderHeight,
    ));
  } else {
    topRightCorner!.layout(BoxConstraints.tight(Size.zero));
    parentDataFor(topRightCorner!).visible = false;
  }

  _invokeUpdateScrollOffsetCallback(() {
    // This will bounds-check the scroll offset. We ignore scroll controller
    // notifications so as to keep from adjusting the scroll bar values
    // (we do so below when we lay the scroll bars out)
    scrollController._setRenderValues(
      scrollOffset: scrollController.scrollOffset,
      viewSize: view!.size,
      viewportSize: Size(viewportWidth, viewportHeight),
    );
  });

  // Position the view, row header, and column header only after giving the
  // scroll controller a chance to bounds-check its scroll offset value.

  if (view != null) {
    _ScrollPaneParentData parentData = parentDataFor(view!);
    parentData.offset = Offset(
      rowHeaderWidth - scrollController.scrollOffset.dx,
      columnHeaderHeight - scrollController.scrollOffset.dy,
    );
    parentData.visible = true;
  }

  if (columnHeader != null) {
    _ScrollPaneParentData parentData = parentDataFor(columnHeader!);
    parentData.offset = Offset(rowHeaderWidth - scrollController.scrollOffset.dx, 0);
    parentData.visible = true;
  }

  if (rowHeader != null) {
    _ScrollPaneParentData parentData = parentDataFor(rowHeader!);
    parentData.offset = Offset(0, columnHeaderHeight - scrollController.scrollOffset.dy);
    parentData.visible = true;
  }

  // Adjust the structure of our scroll bars. Make sure to do this after we
  // bounds-check the scroll offset; otherwise we might try to set structure
  // values that are out of bounds.

  _ScrollPaneParentData horizontalScrollBarParentData = parentDataFor(horizontalScrollBar!);
  if (viewWidth > 0 && horizontalScrollBarHeight > 0) {
    horizontalScrollBarParentData.visible = true;
    horizontalScrollBarParentData.offset = Offset(
      rowHeaderWidth,
      height - horizontalScrollBarHeight,
    );
    final double extent = min(viewWidth, viewportWidth);
    horizontalScrollBar!.blockIncrement = max(1, viewportWidth - _horizontalReveal);
    horizontalScrollBar!.layout(
      ScrollBarConstraints.fromBoxConstraints(
        boxConstraints: BoxConstraints.tightFor(
          width: viewportWidth,
          height: horizontalScrollBarHeight,
        ),
        enabled: !(scrollController.scrollOffset.dx == 0 && extent == viewWidth),
        start: 0,
        end: viewWidth,
        value: scrollController.scrollOffset.dx,
        extent: extent,
      ),
      parentUsesSize: true,
    );
  } else {
    horizontalScrollBarParentData.visible = false;
    horizontalScrollBar!.layout(
      ScrollBarConstraints.fromBoxConstraints(
        boxConstraints: BoxConstraints.tight(Size.zero),
        enabled: horizontalScrollBar!.enabled,
        start: horizontalScrollBar!.start,
        end: horizontalScrollBar!.end,
        value: horizontalScrollBar!.value,
        extent: horizontalScrollBar!.extent,
      ),
      parentUsesSize: true,
    );
  }

  _ScrollPaneParentData verticalScrollBarParentData = parentDataFor(verticalScrollBar!);
  if (viewHeight > 0 && verticalScrollBarWidth > 0) {
    verticalScrollBarParentData.visible = true;
    verticalScrollBarParentData.offset = Offset(
      width - verticalScrollBarWidth,
      columnHeaderHeight,
    );
    final double extent = min(viewHeight, viewportHeight);
    verticalScrollBar!.blockIncrement = max(1, viewportHeight - _verticalReveal);
    verticalScrollBar!.layout(
      ScrollBarConstraints.fromBoxConstraints(
        boxConstraints: BoxConstraints.tightFor(
          width: verticalScrollBarWidth,
          height: viewportHeight,
        ),
        enabled: !(scrollController.scrollOffset.dy == 0 && extent == viewHeight),
        start: 0,
        end: viewHeight,
        value: scrollController.scrollOffset.dy,
        extent: extent,
      ),
      parentUsesSize: true,
    );
  } else {
    verticalScrollBarParentData.visible = false;
    verticalScrollBar!.layout(
      ScrollBarConstraints.fromBoxConstraints(
        boxConstraints: BoxConstraints.tight(Size.zero),
        enabled: verticalScrollBar!.enabled,
        start: verticalScrollBar!.start,
        end: verticalScrollBar!.end,
        value: verticalScrollBar!.value,
        extent: verticalScrollBar!.extent,
      ),
      parentUsesSize: true,
    );
  }
}