Repository: flutter
Issue:  Improper Rendering of Items with Large Data When Vertical Scrolling.
Labels: framework,f: scrolling,c: rendering,has reproducible steps,P3,team-framework,triaged-framework,found in release: 3.22,found in release: 3.23

### Steps to reproduce

1. Run the attached sample.
2.  Scroll the list view vertically.
3. Once the list reaches approximately 500,000 records, items become improperly aligned.

### Expected results

**Items should render properly** even with a large record count. The heights of the items should be maintained correctly.

### Actual results

* After reaching a certain number of records, the rendering of items becomes improper. The heights of the items are not set correctly, and **gaps appear between the items in the UI**.
* This issue can be reproduced by using two **SingleChildScrollView** widgets, setting one with **scrollDirection: Axis.horizontal**, or by using the **isRepaintBoundary => true** override property in the container rendering part of **RenderBox**.

### Code sample

<details open><summary>Code sample</summary>

```dart
// ignore_for_file: must_be_immutable

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:collection/collection.dart';

void main() {
  runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: Text('Custom List Sample'),
      ),
      body: CustomList(),
    ),
  ));
}

class CustomList extends StatelessWidget {
  CustomList({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      return ListContainerWidget(constraints);
    });
  }
}

class ListContainerWidget extends StatefulWidget {
  const ListContainerWidget(this.constraints, {super.key});

  final BoxConstraints constraints;

  @override
  State<ListContainerWidget> createState() => _ListContainerWidgetState();
}

class _ListContainerWidgetState extends State<ListContainerWidget> {
  double _width = 0.0;
  double _height = 0.0;
  ScrollController? _verticalController;
  ItemsContainer items = ItemsContainer.instance;
  late ItemsBuilder data;

  @override
  void initState() {
    super.initState();
    _verticalController = ScrollController();
    _height = widget.constraints.maxHeight + 50;
    _width = widget.constraints.maxWidth;
    _verticalController!.addListener(_verticalListener);
    data = items.fetchItemsDetails();
  }

  @override
  void dispose() {
    _verticalController!.dispose();
    super.dispose();
  }

  void _verticalListener() {
    if (mounted) {
      setState(() {
        ItemsContainer.instance.verticalOffset = _verticalController!.offset;
        ItemsContainer.instance.fetchItemsDetails();
        _height = _height + 1000000;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      controller: _verticalController,
      scrollDirection: Axis.vertical,
      child: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: DisplayArea(
            Size(_width, _height),
            data: data,
          )),
    );
  }
}

class DisplayArea extends StatefulWidget {
  const DisplayArea(this.containerSize, {required this.data, super.key});

  final Size containerSize;
  final ItemsBuilder data;

  @override
  State<DisplayArea> createState() => _DisplayAreaState();
}

class _DisplayAreaState extends State<DisplayArea> {
  @override
  Widget build(BuildContext context) {
    final List<Widget> children = <Widget>[];

    children.addAll(widget.data.items
        .map<Widget>((ItemsDetails item) => item.element)
        .toList());

    return ContainerWidget(
      key: widget.key,
      containerSize: widget.containerSize,
      children: children,
    );
  }
}

class ContainerWidget extends StatefulWidget {
  const ContainerWidget(
      {required this.containerSize, required this.children, super.key});

  final Size containerSize;

  final List<Widget> children;

  @override
  State<ContainerWidget> createState() => _ContainerWidgetState();
}

class _ContainerWidgetState extends State<ContainerWidget> {
  @override
  Widget build(BuildContext context) {
    return ContainerRenderObjectWidget(widget.containerSize, widget.children);
  }
}

class ContainerRenderObjectWidget extends MultiChildRenderObjectWidget {
  ContainerRenderObjectWidget(this._containerSize, this.children)
      : super(children: RepaintBoundary.wrapAll(List<Widget>.from(children)));

  @override
  final List<Widget> children;

  Size get containerSize {
    return _containerSize;
  }

  Size _containerSize = Size.zero;

  @override
  RenderContainerBox createRenderObject(BuildContext context) {
    return RenderContainerBox(containerSize);
  }

  @override
  void updateRenderObject(
      BuildContext context, RenderContainerBox renderObject) {
    super.updateRenderObject(context, renderObject);
    renderObject..containerSize = containerSize;
  }
}

class RenderContainerBox extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, ContainerParentData>,
        RenderBoxContainerDefaultsMixin<RenderBox, ContainerParentData> {
  RenderContainerBox(this._containerSize);

  @override
  void setupParentData(RenderObject child) {
    super.setupParentData(child);
    if (child.parentData is! ContainerParentData) {
      child.parentData = ContainerParentData();
    }
  }

  Size get containerSize => _containerSize;
  Size _containerSize = Size.zero;

  set containerSize(Size newContainerSize) {
    if (_containerSize == newContainerSize) {
      return;
    }
    _containerSize = newContainerSize;
    markNeedsLayout();
    markNeedsPaint();
  }

  @override
  bool get isRepaintBoundary => false;

  @override
  void performLayout() {
    size =
        constraints.constrain(Size(containerSize.width, containerSize.height));

    void layout(
        {required RenderBox child,
        required double width,
        required double height}) {
      child.layout(BoxConstraints.tightFor(width: width, height: height),
          parentUsesSize: true);
    }

    RenderBox? child = firstChild;
    while (child != null) {
      final ContainerParentData parentData =
          child.parentData! as ContainerParentData;
      final RenderBox itemsDetails = child;
      if (itemsDetails is RenderRecord) {
        final Rect itemRect = Rect.fromLTWH(0, 0, containerSize.width, 50);

        parentData
          ..width = itemRect.width
          ..height = itemRect.height
          ..offset = Offset(0, itemsDetails.itemIndex.toDouble());
        layout(
            child: child, width: parentData.width, height: parentData.height);
      }

      child = parentData.nextSibling;
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    RenderBox? child = firstChild;
    while (child != null) {
      final ContainerParentData childParentData =
          child.parentData! as ContainerParentData;
      final RenderBox itemsDetails = child;
      if (itemsDetails is RenderRecord) {
        if (childParentData.width != 0.0 && childParentData.height != 0.0) {
          context.paintChild(
              child, Offset(0, itemsDetails.itemIndex * 50) + offset);
        }
      }
      child = childParentData.nextSibling;
    }
  }
}

class ContainerParentData extends ContainerBoxParentData<RenderBox> {
  double width = 0.0;
  double height = 0.0;
}

class RecordRenderWidget extends SingleChildRenderObjectWidget {
  RecordRenderWidget({required this.itemIndex, required this.child})
      : super(child: RepaintBoundary.wrap(child, 0));

  int itemIndex;

  @override
  final Widget child;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderRecord(itemIndex: itemIndex);
  }

  @override
  void updateRenderObject(BuildContext context, RenderRecord renderObject) {
    super.updateRenderObject(context, renderObject);
    renderObject.itemIndex = itemIndex;
  }
}

class RenderRecord extends RenderBox
    with RenderObjectWithChildMixin<RenderBox> {
  RenderRecord({required this.itemIndex});
  int itemIndex;

  @override
  bool get isRepaintBoundary => true;

  @override
  void performLayout() {
    size = constraints
        .constrain(Size(constraints.maxWidth, constraints.maxHeight));

    if (child != null) {
      child!.layout(
          BoxConstraints.tightFor(
              width: constraints.maxWidth, height: constraints.maxHeight),
          parentUsesSize: true);
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      context.paintChild(child!, offset);
    }

    super.paint(context, offset);
  }
}

class ItemsBuilder {
  List<ItemsDetails> items = [];

  ItemsBuilder() {
    items = [];
  }

  void fetchItems(int startIndex, int endIndex) {
    items.forEach((item) => item.isEnsured = false);

    for (int index = startIndex; index <= endIndex; index++) {
      ItemsDetails? item =
          items.firstWhereOrNull((item) => item.itemIndex == index);

      if (item != null) {
        item.isEnsured = true;
        item.element = DataElementWidget(itemIndex: index);
      } else {
        item = items.firstWhereOrNull((item) =>
            item.itemIndex == -1 ||
            !(item.itemIndex >= startIndex && item.itemIndex <= endIndex));

        if (item != null) {
          item.updateItemDetails(index);
        }

        if (item == null) {
          item = createNewItem(index);
          items.add(item);
        }
      }
    }

    items.forEach((item) {
      if (!item.isEnsured) {
        item.itemIndex = -1;
      }
    });
  }

  ItemsDetails createNewItem(int itemIndex) {
    var item = ItemsDetails();
    item.isEnsured = true;
    item.itemIndex = itemIndex;
    item.element = DataElementWidget(itemIndex: itemIndex);
    return item;
  }
}

class ItemsDetails {
  bool isEnsured = false;
  int itemIndex = -1;
  late Widget element;

  void updateItemDetails(int itemIndex) {
    this.itemIndex = itemIndex;
    this.isEnsured = true;
    this.element = DataElementWidget(itemIndex: itemIndex);
  }
}

class DataElementWidget extends StatefulWidget {
  const DataElementWidget({
    Key? key,
    required this.itemIndex,
  }) : super(key: key);

  final int itemIndex;

  @override
  State<DataElementWidget> createState() => _DataElementWidget();
}

class _DataElementWidget extends State<DataElementWidget> {
  @override
  Widget build(BuildContext context) {
    return RecordRenderWidget(
      itemIndex: widget.itemIndex,
      child: Container(
        color: Colors.blue,
        child: Text(
          'Items Count: ${widget.itemIndex}\n',
          style: TextStyle(color: Color.fromRGBO(0, 0, 0, 0.87)),
        ),
      ),
    );
  }
}

class ItemsContainer {
  static final ItemsContainer instance = ItemsContainer._();

  factory ItemsContainer() => instance;

  ItemsContainer._() {
    data = ItemsBuilder();
  }

  double verticalOffset = 0;
  Size? renderSize;
  late ItemsBuilder data;

  List<int> getItemsStartEndIndex() {
    int startIndex = (verticalOffset ~/ 50).floor();
    int endIndex = ((verticalOffset + 600) / 50).ceil();
    return [startIndex, endIndex];
  }

  ItemsBuilder fetchItemsDetails() {
    var indexes = getItemsStartEndIndex();
    int startRowIndex = indexes[0], endRowIndex = indexes[1];

    data.fetchItems(startRowIndex, endRowIndex);

    return data;
  }
}
```

</details>


### Screenshots or Video

<details open>
<summary>Screenshots / Video demonstration</summary>

https://github.com/flutter/flutter/assets/120440951/d392a6e2-930d-42ff-923d-be958fea691b

</details>


### Logs

<details open><summary>Logs</summary>

```console
[Paste your logs here]
```

</details>


### Flutter Doctor output

<details open><summary>Doctor output</summary>

```console
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.22.2, on Microsoft Windows [Version 10.0.19045.4529], locale en-US)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[√] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Professional 2022 17.9.6)
[√] Android Studio (version 2021.3)
[√] VS Code (version 1.90.2)
[√] Connected device (3 available)
[√] Network resources

• No issues found!   
```

</details>