Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/two_dimensional_scrollables/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.2

* Fixes an issue where trailing pinned spans were included in the regular layout pass, leading to unnecessary iterations.

## 0.5.1

* Fixes an infinite loop of onExit/onEnter events when setState is called within onEnter in a TableSpan.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
!span.isPinned && span.trailingOffset >= _targetTrailingColumnPixel,
);
if (_firstNonPinnedColumn != null) {
_lastNonPinnedColumn ??= _columnMetrics.length - 1;
_lastNonPinnedColumn ??= _lastRegularColumnIndex;
}

if (_rowMetrics.isNotEmpty) {
Expand Down Expand Up @@ -1056,7 +1056,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
!span.isPinned && span.trailingOffset >= _targetTrailingRowPixel,
);
if (_firstNonPinnedRow != null) {
_lastNonPinnedRow ??= _rowMetrics.length - 1;
_lastNonPinnedRow ??= _lastRegularRowIndex;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/two_dimensional_scrollables/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: two_dimensional_scrollables
description: Widgets that scroll using the two dimensional scrolling foundation.
version: 0.5.1
version: 0.5.2
repository: https://github.com/flutter/packages/tree/main/packages/two_dimensional_scrollables
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+two_dimensional_scrollables%22+

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4631,6 +4631,84 @@ void main() {
expect(mergedRect.top, 200);
expect(mergedRect.bottom, 400);
});

testWidgets('Trailing pinned spans are not included in regular layout pass', (
WidgetTester tester,
) async {
// This test ensures that trailing pinned spans are not built or painted
// as part of the regular (non-pinned) range.
const spanExtent = 100.0;
final paintCounts = <TableVicinity, int>{};

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SizedBox(
height: 400,
width: 400,
child: TableView.builder(
cacheExtent: 0.0,
columnCount: 6, // 2 regular (0,1), 4 trailing pinned (2,3,4,5)
rowCount: 6, // 2 regular, 4 trailing pinned
trailingPinnedColumnCount: 4,
trailingPinnedRowCount: 4,
columnBuilder: (int index) =>
const TableSpan(extent: FixedTableSpanExtent(spanExtent)),
rowBuilder: (int index) =>
const TableSpan(extent: FixedTableSpanExtent(spanExtent)),
cellBuilder: (context, vicinity) {
return TableViewCell(
child: _PaintCounter(
onPaint: () {
paintCounts[vicinity] = (paintCounts[vicinity] ?? 0) + 1;
},
child: Text('x: ${vicinity.column} y: ${vicinity.row}'),
),
);
},
),
),
),
),
);

await tester.pump();

// Pinned cells should be painted exactly once.
// Before the fix, they would be painted once in the regular pass
// (if incorrectly included) and once in the pinned pass.
// Since they are visually clipped in the regular pass by pushClipRect,
// they would only be painted once anyway, BUT the fix ensures
// we don't even iterate over them in the regular pass.
expect(paintCounts[const TableVicinity(column: 5, row: 5)], 1);
expect(paintCounts[TableVicinity.zero], 1);
expect(paintCounts.length, 36);
});
}

class _PaintCounter extends SingleChildRenderObjectWidget {
const _PaintCounter({required this.onPaint, required super.child});
final VoidCallback onPaint;
@override
_RenderPaintCounter createRenderObject(BuildContext context) =>
_RenderPaintCounter(onPaint);
@override
void updateRenderObject(
BuildContext context,
_RenderPaintCounter renderObject,
) {
renderObject.onPaint = onPaint;
}
}

class _RenderPaintCounter extends RenderProxyBox {
_RenderPaintCounter(this.onPaint);
VoidCallback onPaint;
@override
void paint(PaintingContext context, Offset offset) {
onPaint();
super.paint(context, offset);
}
}

class _NullBuildContext implements BuildContext, TwoDimensionalChildManager {
Expand Down
Loading