Вы можете создать свой собственный CustomGridView с виджетом CustomPainter и нарисовать все элементы + добавить один детектор жестов и рассчитать место касания, если вам нужно добавить поведение onTap к блокам
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;
}
}
}