Skip to content

Instantly share code, notes, and snippets.

@Piinks
Last active January 18, 2024 21:24
Show Gist options
  • Save Piinks/4424936c57ed13093eb389123383e894 to your computer and use it in GitHub Desktop.
Save Piinks/4424936c57ed13093eb389123383e894 to your computer and use it in GitHub Desktop.
Two Dimensional Grid in Flutter
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
scrollBehavior: const MaterialScrollBehavior().copyWith(
// Mouse dragging enabled for this demo
dragDevices: PointerDeviceKind.values.toSet(),
),
debugShowCheckedModeBanner: false,
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: TwoDimensionalGridView(
diagonalDragBehavior: DiagonalDragBehavior.free,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 9,
maxYIndex: 9,
builder: (BuildContext context, ChildVicinity vicinity) {
return Container(
color: vicinity.xIndex.isEven && vicinity.yIndex.isEven
? Colors.amber[50]
: (vicinity.xIndex.isOdd && vicinity.yIndex.isOdd
? Colors.purple[50]
: null),
height: 200,
width: 200,
child: Center(
child: Text(
'Row ${vicinity.yIndex}: Column ${vicinity.xIndex}')),
);
}),
),
);
}
}
class TwoDimensionalGridView extends TwoDimensionalScrollView {
const TwoDimensionalGridView({
super.key,
super.primary,
super.mainAxis = Axis.vertical,
super.verticalDetails = const ScrollableDetails.vertical(),
super.horizontalDetails = const ScrollableDetails.horizontal(),
required TwoDimensionalChildBuilderDelegate delegate,
super.cacheExtent,
super.diagonalDragBehavior = DiagonalDragBehavior.none,
super.dragStartBehavior = DragStartBehavior.start,
super.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
super.clipBehavior = Clip.hardEdge,
}) : super(delegate: delegate);
@override
Widget buildViewport(
BuildContext context,
ViewportOffset verticalOffset,
ViewportOffset horizontalOffset,
) {
return TwoDimensionalGridViewport(
horizontalOffset: horizontalOffset,
horizontalAxisDirection: horizontalDetails.direction,
verticalOffset: verticalOffset,
verticalAxisDirection: verticalDetails.direction,
mainAxis: mainAxis,
delegate: delegate as TwoDimensionalChildBuilderDelegate,
cacheExtent: cacheExtent,
clipBehavior: clipBehavior,
);
}
}
class TwoDimensionalGridViewport extends TwoDimensionalViewport {
const TwoDimensionalGridViewport({
super.key,
required super.verticalOffset,
required super.verticalAxisDirection,
required super.horizontalOffset,
required super.horizontalAxisDirection,
required TwoDimensionalChildBuilderDelegate super.delegate,
required super.mainAxis,
super.cacheExtent,
super.clipBehavior = Clip.hardEdge,
});
@override
RenderTwoDimensionalViewport createRenderObject(BuildContext context) {
return RenderTwoDimensionalGridViewport(
horizontalOffset: horizontalOffset,
horizontalAxisDirection: horizontalAxisDirection,
verticalOffset: verticalOffset,
verticalAxisDirection: verticalAxisDirection,
mainAxis: mainAxis,
delegate: delegate as TwoDimensionalChildBuilderDelegate,
childManager: context as TwoDimensionalChildManager,
cacheExtent: cacheExtent,
clipBehavior: clipBehavior,
);
}
@override
void updateRenderObject(
BuildContext context,
RenderTwoDimensionalGridViewport renderObject,
) {
renderObject
..horizontalOffset = horizontalOffset
..horizontalAxisDirection = horizontalAxisDirection
..verticalOffset = verticalOffset
..verticalAxisDirection = verticalAxisDirection
..mainAxis = mainAxis
..delegate = delegate
..cacheExtent = cacheExtent
..clipBehavior = clipBehavior;
}
}
class RenderTwoDimensionalGridViewport extends RenderTwoDimensionalViewport {
RenderTwoDimensionalGridViewport({
required super.horizontalOffset,
required super.horizontalAxisDirection,
required super.verticalOffset,
required super.verticalAxisDirection,
required TwoDimensionalChildBuilderDelegate delegate,
required super.mainAxis,
required super.childManager,
super.cacheExtent,
super.clipBehavior = Clip.hardEdge,
}) : super(delegate: delegate);
@override
void layoutChildSequence() {
final double horizontalPixels = horizontalOffset.pixels;
final double verticalPixels = verticalOffset.pixels;
final double viewportWidth = viewportDimension.width + cacheExtent;
final double viewportHeight = viewportDimension.height + cacheExtent;
final TwoDimensionalChildBuilderDelegate builderDelegate =
delegate as TwoDimensionalChildBuilderDelegate;
final int maxRowIndex = builderDelegate.maxYIndex!;
final int maxColumnIndex = builderDelegate.maxXIndex!;
final int leadingColumn = math.max((horizontalPixels / 200).floor(), 0);
final int leadingRow = math.max((verticalPixels / 200).floor(), 0);
final int trailingColumn = math.min(
((horizontalPixels + viewportWidth) / 200).ceil(),
maxColumnIndex,
);
final int trailingRow = math.min(
((verticalPixels + viewportHeight) / 200).ceil(),
maxRowIndex,
);
double xLayoutOffset = (leadingColumn * 200) - horizontalOffset.pixels;
for (int column = leadingColumn; column <= trailingColumn; column++) {
double yLayoutOffset = (leadingRow * 200) - verticalOffset.pixels;
for (int row = leadingRow; row <= trailingRow; row++) {
final ChildVicinity vicinity =
ChildVicinity(xIndex: column, yIndex: row);
final RenderBox child = buildOrObtainChildFor(vicinity)!;
child.layout(constraints.loosen());
// Subclasses only need to set the normalized layout offset. The super
// class adjusts for reversed axes.
parentDataOf(child).layoutOffset = Offset(xLayoutOffset, yLayoutOffset);
yLayoutOffset += 200;
}
xLayoutOffset += 200;
}
// Set the min and max scroll extents for each axis.
final double verticalExtent = 200 * (maxRowIndex + 1);
verticalOffset.applyContentDimensions(
0.0,
clampDouble(
verticalExtent - viewportDimension.height, 0.0, double.infinity),
);
final double horizontalExtent = 200 * (maxColumnIndex + 1);
horizontalOffset.applyContentDimensions(
0.0,
clampDouble(
horizontalExtent - viewportDimension.width, 0.0, double.infinity),
);
// Super class handles garbage collection too!
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment