Slow Flutter GridView
You can create your own CustomGridView with CustomPainter widget, and draw all items + add one gesture detector and calculate the place of touch, if you need to add onTap behavior to blocs
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
final int redCount = 728;
final int greyCount = 3021;
final int allCount = 4160;
final int crossAxisCount = 52;
enum BlockTypes {
red,
gray,
green,
yellow,
}
class MyHomePage extends StatefulWidget {
MyHomePage()
: blocks = List<BlockTypes>.generate(allCount, (index) {
if (index < redCount) {
return BlockTypes.red;
} else if (index < redCount + greyCount) {
return BlockTypes.gray;
}
return BlockTypes.green;
});
final List<BlockTypes> blocks;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int columnsCount;
double blocSize;
int clickedIndex;
Offset clickOffset;
bool hasSizes = false;
List<BlockTypes> blocks;
final ScrollController scrollController = ScrollController();
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
blocks = widget.blocks;
super.initState();
}
void _afterLayout(_) {
blocSize = context.size.width / crossAxisCount;
columnsCount = (allCount / crossAxisCount).ceil();
setState(() {
hasSizes = true;
});
}
void onTapDown(TapDownDetails details) {
final RenderBox box = context.findRenderObject();
clickOffset = box.globalToLocal(details.globalPosition);
}
void onTap() {
final dx = clickOffset.dx;
final dy = clickOffset.dy + scrollController.offset;
final tapedRow = (dx / blocSize).floor();
final tapedColumn = (dy / blocSize).floor();
clickedIndex = tapedColumn * crossAxisCount + tapedRow;
setState(() {
blocks[clickedIndex] = BlockTypes.yellow;
});
}
@override
Widget build(BuildContext context) {
print(blocSize);
return hasSizes
? SingleChildScrollView(
controller: scrollController,
child: GestureDetector(
onTapDown: onTapDown,
onTap: onTap,
child: CustomPaint(
size: Size(
MediaQuery.of(context).size.width,
columnsCount * blocSize,
),
painter: CustomGridView(
blocs: widget.blocks,
columnsCount: columnsCount,
blocSize: blocSize,
),
),
),
)
: Container();
}
}
class CustomGridView extends CustomPainter {
final double gap = 1;
final Paint painter = Paint()
..strokeWidth = 1
..style = PaintingStyle.fill;
final int columnsCount;
final double blocSize;
final List<BlockTypes> blocs;
CustomGridView({this.columnsCount, this.blocSize, this.blocs});
@override
void paint(Canvas canvas, Size size) {
blocs.asMap().forEach((index, bloc) {
setColor(bloc);
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(
getLeft(index),
getTop(index),
blocSize - gap,
blocSize - gap,
),
Radius.circular(1.0)),
painter);
});
}
double getTop(int index) {
return (index / crossAxisCount).floor().toDouble() * blocSize;
}
double getLeft(int index) {
return (index % crossAxisCount).floor().toDouble() * blocSize;
}
@override
bool shouldRepaint(CustomGridView oldDelegate) => true;
@override
bool shouldRebuildSemantics(CustomGridView oldDelegate) => true;
void setColor(BlockTypes bloc) {
switch (bloc) {
case BlockTypes.red:
painter.color = Colors.red;
break;
case BlockTypes.gray:
painter.color = Colors.grey;
break;
case BlockTypes.green:
painter.color = Colors.green;
break;
case BlockTypes.yellow:
painter.color = Colors.yellow;
break;
}
}
}
eth0
Updated on December 20, 2022Comments
-
eth0 over 1 year
I have a need to create a 52x80 grid of square blocks. It looks like this:
But performance is particularly slow whilst developing in an emulator (over 1s 'lag'). I understand that Flutter code runs faster in release mode on a physical device, which is also true in my case if the device is new-ish. But if the device is a few years old (i.e. Samsung Galaxy S8 or iPhone 8) then there is a frustratingly noticeable time in loading the view and whilst scrolling. And I can't release my app like that. I'm building my GridView like this:
GridView.count( shrinkWrap: true, primary: false, padding: const EdgeInsets.all(5.0), crossAxisCount: 52, crossAxisSpacing: 1.0, mainAxisSpacing: 1.0, addAutomaticKeepAlives: true, children: blocks.map((block) => // blocks is just a list of 4160 objects FlatButton( child: null, color: block.backgroundColor, onPressed: () { // open a new route }, splashColor: Colors.transparent, highlightColor: Colors.transparent ) ).toList() )
I've tried switching out
FlatButton
for anImage
or aSizedBox
which helps a little. Any suggestions on how I could make this faster? -
eth0 about 4 yearsAmazing. This is 100x better. Thanks for pointing me in the right direction!
-
Rémi Rousselet about 4 yearsSingleChildScrollView is not a very good idea (as it will paint the items that are not visible too). A custom RenderSliver would probably be better
-
Kherel about 4 years@Rémi Rousselet. In the question eth0 was talking about the grid 52x80, it can be showen in one screen.