paint method

  1. @override
String paint()
override

Implementation

@override
String paint() {
  final maxWidth = size.width.round();
  final viewportHeight = size.height.round();
  final separatorBreaks = _separatorBreaks(separator);
  final itemCount = children.length;
  final hasSelection =
      controller is WidgetScrollController &&
      (controller as WidgetScrollController).hasSelection;
  if (itemCount == 0 || viewportHeight <= 0) {
    _invalidateVisiblePaintCache();
    _resetVisibleHitCache();
    _resetRepaintFlag();
    return '';
  }

  final offset = controller.offset.clamp(0, controller.maxOffset);
  if (!hasSelection &&
      _canUseVisiblePaintCache(
        offset: offset,
        viewportHeight: viewportHeight,
        maxWidth: maxWidth,
        itemCount: itemCount,
        separatorBreaks: separatorBreaks,
        isVariableHeight: variableHeight,
      )) {
    final cached = _cachedVisibleContent!;
    _resetRepaintFlag();
    return cached;
  }

  if (variableHeight) {
    final baseEstimate = math
        .max(1, estimatedItemExtent ?? itemExtent)
        .toInt();
    final estimate = _resolveAdaptiveEstimate(baseEstimate);
    _syncCache(itemCount, separatorBreaks, estimate);
    ({
      String visible,
      bool heightsChanged,
      int anchorIndex,
      int anchorOffsetInItem,
    })
    buildVisibleForOffset(int target) {
      _resetVisibleHitCache();
      final prefix = _prefixHeights(itemCount, separatorBreaks, estimate);
      var startIndex = _findStartIndex(prefix, target).clamp(0, itemCount);
      var offsetInItem = target - prefix[startIndex];
      if (startIndex >= itemCount) {
        startIndex = itemCount - 1;
        offsetInItem = 0;
      }
      final requiredLines = offsetInItem + viewportHeight;

      final buffer = StringBuffer();
      var lineCount = 0;
      var measuredHeightsChanged = false;
      _lastPaintOffset = target;
      _lastPaintViewportHeight = viewportHeight;
      _lastOffsetInItem = offsetInItem;

      for (
        var i = startIndex;
        i < itemCount && lineCount < requiredLines;
        i++
      ) {
        final itemStart = lineCount;
        final resolved = _resolveChildPaint(index: i, maxWidth: maxWidth);
        final text = resolved.text;
        final measured = resolved.measured;
        if (_measuredHeights[i] != measured) {
          _measuredHeights[i] = measured;
          measuredHeightsChanged = true;
          _invalidateMeasurements();
        }
        buffer.write(text);
        lineCount += measured;
        final itemEnd = lineCount;
        if (itemEnd > offsetInItem && itemStart < requiredLines) {
          _lastVisibleHits.add(
            _VisibleItemHit(
              index: i,
              bufferStart: itemStart,
              bufferEnd: itemEnd,
            ),
          );
        }
        if (i < itemCount - 1 && separator.isNotEmpty) {
          buffer.write(separator);
          lineCount += separatorBreaks;
        }
      }

      final content = buffer.toString();
      return (
        visible: _sliceLines(content, offsetInItem, viewportHeight),
        heightsChanged: measuredHeightsChanged,
        anchorIndex: startIndex,
        anchorOffsetInItem: offsetInItem,
      );
    }

    var workingOffset = offset;
    var rendered = buildVisibleForOffset(workingOffset);

    if (rendered.heightsChanged) {
      _traceScroll(
        'virtual_list.measurements_changed '
        'zone=$zoneId items=$itemCount estimate=$estimate '
        'offset=$workingOffset max=${controller.maxOffset}',
      );
      final contentHeight = _estimatedContentHeight(
        itemCount,
        separatorBreaks,
        estimate,
      );
      _setContentExtent(contentHeight);

      // Stabilize viewport anchor when measured heights change: keep the
      // same first visible item and intra-item line, and move absolute
      // offset to match the new prefix sum. This prevents apparent jumps
      // backward/forward while estimates converge during scrolling.
      final anchorOffset = _offsetForAnchor(
        itemCount: itemCount,
        separatorBreaks: separatorBreaks,
        estimate: estimate,
        anchorIndex: rendered.anchorIndex,
        anchorOffsetInItem: rendered.anchorOffsetInItem,
      ).clamp(0, controller.maxOffset);
      final currentOffset = controller.offset.clamp(0, controller.maxOffset);
      final suppressAnchorAdjust =
          controller is WidgetScrollController &&
          (controller as WidgetScrollController).thumbDragActive;
      if (anchorOffset != currentOffset && !suppressAnchorAdjust) {
        _traceScroll(
          'virtual_list.anchor_adjust '
          'zone=$zoneId from=$currentOffset to=$anchorOffset '
          'anchorIndex=${rendered.anchorIndex} '
          'anchorInItem=${rendered.anchorOffsetInItem} '
          'contentHeight=$contentHeight max=${controller.maxOffset}',
        );
        controller.jumpTo(anchorOffset);
      } else if (anchorOffset != currentOffset && suppressAnchorAdjust) {
        _traceScroll(
          'virtual_list.anchor_skip '
          'zone=$zoneId from=$currentOffset target=$anchorOffset '
          'anchorIndex=${rendered.anchorIndex} '
          'anchorInItem=${rendered.anchorOffsetInItem} '
          'contentHeight=$contentHeight max=${controller.maxOffset}',
        );
      }

      final nextOffset = controller.offset.clamp(0, controller.maxOffset);
      if (nextOffset != workingOffset) {
        _traceScroll(
          'virtual_list.offset_resolved '
          'zone=$zoneId $workingOffset->$nextOffset max=${controller.maxOffset}',
        );
        _resetRepaintFlag();
        workingOffset = nextOffset;
        rendered = buildVisibleForOffset(workingOffset);
      }
    }

    _storeVisiblePaintCache(
      visible: _applySelectionIfNeeded(rendered.visible, workingOffset),
      offset: workingOffset,
      viewportHeight: viewportHeight,
      maxWidth: maxWidth,
      itemCount: itemCount,
      separatorBreaks: separatorBreaks,
      isVariableHeight: true,
    );

    _resetRepaintFlag();
    return _cachedVisibleContent!;
  }

  _resetVisibleHitCache();

  final itemHeight = math.max(1, itemExtent).toInt();
  final stride = itemHeight + separatorBreaks;
  final startIndex = stride > 0 ? offset ~/ stride : 0;
  final offsetInStride = stride > 0 ? offset % stride : 0;
  final requiredLines = offsetInStride + viewportHeight;

  final buffer = StringBuffer();
  var lineCount = 0;
  for (var i = startIndex; i < itemCount && lineCount < requiredLines; i++) {
    final resolved = _resolveChildPaint(index: i, maxWidth: maxWidth);
    final text = resolved.text;
    buffer.write(text);
    lineCount += resolved.measured;
    if (i < itemCount - 1 && separator.isNotEmpty) {
      buffer.write(separator);
      lineCount += separatorBreaks;
    }
  }

  final visible = _applySelectionIfNeeded(
    _sliceLines(buffer.toString(), offsetInStride, viewportHeight),
    offset,
  );
  _storeVisiblePaintCache(
    visible: visible,
    offset: offset,
    viewportHeight: viewportHeight,
    maxWidth: maxWidth,
    itemCount: itemCount,
    separatorBreaks: separatorBreaks,
    isVariableHeight: false,
  );
  _resetRepaintFlag();
  return visible;
}