Last active
January 18, 2024 21:24
-
-
Save Piinks/4424936c57ed13093eb389123383e894 to your computer and use it in GitHub Desktop.
Two Dimensional Grid in Flutter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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