TwoDimensionalScrollView lag for small-many grid
- Dominant language
- Dart
- Stars
- 179k
- Forks
- 31.1k
- PR merge metrics
- PR metrics pending
Description
### Steps to reproduce
You can checkout [dartpad](https://dartpad.dev/?id=01997ebded25bb83b9c297d8c25791f3)
### Code sample
Code sample
```dart
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
///*I was playing with 2D gridView and my use-case is to render thousands of grids,
/// it is good 100px).
/// but it keeps lagging once I have small grids(<20px)
/// idk if it is for heavy computation on layoutChildSequence,
/// I am wondering is there anyway of optimizing RenderTwoDimensionalViewport for small many grid.
/// !https://github.com/yeasin50/game_of_life/blob/master/lib/src/presentation/widgets/two_dimensional_gridview.dart
///
/// Create a grid view on TwoDimensionalScrollView
///
/// ```dart
/// TwoDimensionalGridView(
/// gridDimension: 20,
/// diagonalDragBehavior: DiagonalDragBehavior.free,
/// cacheExtent: 500,
/// delegate: TwoDimensionalChildBuilderDelegate(
/// maxXIndex: 130,
/// maxYIndex: 130,
/// builder: (context, vicinity) {
/// return Container(
/// height: 20,
/// width: 20,
/// color: Colors.primaries[(vicinity.xIndex + vicinity.yIndex) % Colors.primaries.length],
/// alignment: Alignment.center,
/// child: Text(
/// vicinity.toString(),
/// ),
/// );
/// },
/// ),
/// ),
/// ```
///
@Deprecated("Not usable for large items, use [TwoDimensionalCustomPaintGridView] instead")
class TwoDimensionalGridView extends TwoDimensionalScrollView {
const TwoDimensionalGridView({
super.key,
required super.delegate,
required this.gridDimension,
super.primary,
super.mainAxis = Axis.vertical,
super.verticalDetails = const ScrollableDetails.vertical(),
super.horizontalDetails = const ScrollableDetails.horizontal(),
super.cacheExtent,
super.diagonalDragBehavior = DiagonalDragBehavior.none,
super.dragStartBehavior = DragStartBehavior.start,
super.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
super.clipBehavior = Clip.hardEdge,
});
/// size of the grid
final double gridDimension;
@override
Widget buildViewport(
BuildContext context,
ViewportOffset verticalOffset,
ViewportOffset horizontalOffset,
) {
return TwoDimensionalGridViewPort(
gridDimension: gridDimension,
cacheExtent: cacheExtent,
clipBehavior: clipBehavior,
verticalOffset: verticalOffset,
verticalAxisDirection: AxisDirection.down,
horizontalAxisDirection: AxisDirection.right,
horizontalOffset: horizontalOffset,
delegate: delegate,
mainAxis: mainAxis,
);
}
}
class TwoDimensionalGridViewPort extends TwoDimensionalViewport {
const TwoDimensionalGridViewPort({
super.key,
required super.verticalOffset,
required super.verticalAxisDirection,
required super.horizontalOffset,
required super.horizontalAxisDirection,
required super.delegate,
required super.mainAxis,
super.cacheExtent,
super.clipBehavior = Clip.hardEdge,
required this.gridDimension,
});
final double gridDimension;
@override
RenderTwoDimensionalViewport createRenderObject(BuildContext context) {
return RenderTreeViewPostT(
gridDimension: gridDimension,
horizontalOffset: horizontalOffset,
horizontalAxisDirection: horizontalAxisDirection,
verticalOffset: verticalOffset,
verticalAxisDirection: verticalAxisDirection,
delegate: delegate,
mainAxis: mainAxis,
childManager: context as TwoDimensionalChildManager,
);
}
@override
void updateRenderObject(BuildContext context, covariant RenderTwoDimensionalViewport renderObject) {
super.updateRenderObject(context, renderObject);
}
}
class RenderTreeViewPostT extends RenderTwoDimensionalViewport {
RenderTreeViewPostT({
required super.horizontalOffset,
required super.horizontalAxisDirection,
required super.verticalOffset,
required super.verticalAxisDirection,
required super.delegate,
required super.mainAxis,
required super.childManager,
required this.gridDimension,
});
///ig there should be way to get from child instead, while I will have same size grid, so ignoring
final double gridDimension;
@override
void layoutChildSequence() {
// FIXME: The laggy ui
final double horizontalPixels = horizontalOffset.pixels;
final double verticalPixels = verticalOffset.pixels;
final viewPortWidth = viewportDimension.width + cacheExtent;
final viewPortHeight = viewportDimension.height + cacheExtent;
final TwoDimensionalChildBuilderDelegate builderDelegate = delegate as TwoDimensionalChildBuilderDelegate;
final int maxRowIndex = builderDelegate.maxYIndex!;
final int maxColIndex = builderDelegate.maxXIndex!;
final int leadingColumn = math.max((horizontalPixels / gridDimension).floor(), 0);
final int leadingRow = math.max((verticalPixels / gridDimension).floor(), 0);
final int trailingColumn = math.min(((horizontalPixels + viewPortWidth) / gridDimension).ceil(), maxColIndex);
final int trailingRow = math.min(((verticalPixels + viewPortHeight) / gridDimension).ceil(), maxRowIndex);
double xLayoutOffset = (leadingColumn * gridDimension) - horizontalPixels;
for (int x = leadingColumn; x < trailingColumn; x++) {
double yLayoutOffset = (leadingRow * gridDimension) - verticalPixels;
for (int y = leadingRow; y < trailingRow; y++) {
final ChildVicinity childVicinity = ChildVicinity(xIndex: x, yIndex: y);
final RenderBox child = buildOrObtainChildFor(childVicinity)!;
child.layout(constraints.loosen());
parentDataOf(child).layoutOffset = Offset(xLayoutOffset, yLayoutOffset);
yLayoutOffset += gridDimension;
}
xLayoutOffset += gridDimension;
}
final double verticalExtent = gridDimension * (maxRowIndex + 1.0);
verticalOffset.applyContentDimensions(0, (verticalExtent - viewportDimension.height));
final double horizontalExtent = gridDimension * (maxColIndex + 1);
horizontalOffset.applyContentDimensions(0, (horizontalExtent - viewportDimension.width));
}
}
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
scrollBehavior: MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown
},
),
));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: TwoDimensionalGridView(
gridDimension: 20,
diagonalDragBehavior: DiagonalDragBehavior.free,
cacheExtent: 500,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 200,
maxYIndex: 100,
builder: (context, vicinity) {
return SizedBox.expand(
child: ColoredBox(
key: ValueKey(vicinity),
color: Colors.primaries[(vicinity.xIndex + vicinity.yIndex) % Colors.primaries.length ~/ 2],
),
);
},
),
),
);
}
}
```
### What target platforms are you seeing this bug on?
Android, Web, Windows
### OS/Browser name and version | Device information
Any, Windows10
### Does the problem occur on emulator/simulator as well as on physical devices?
Yes
### Logs
_No response_
### Flutter Doctor output
Doctor output
```console
[√] Flutter (Channel stable, 3.24.1, on Microsoft Windows [Version 10.0.19045.4780], locale en-US)
• Flutter version 3.24.1 on channel stable at P:\symlinks_portal\fvm_cache\versions\3.24.1
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 5874a72aa4 (2 weeks ago), 2024-08-20 16:46:00 -0500
• Engine revision c9b9d5780d
• Dart version 3.5.1
• DevTools version 2.37.2
[√] Windows Version (Installed version of Windows is version 10 or higher)
```
Contributor guide
Assessment
This issue has not been assessed yet.