-
-
Save NonymousMorlock/ee2e5f0ddb0c661abc00ebfb9602687d to your computer and use it in GitHub Desktop.
Two Dimensional Grid in Flutter
This file contains hidden or 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.xIndex}: Column ${vicinity.yIndex}')), | |
); | |
}), | |
), | |
); | |
} | |
} | |
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 SimpleBuilderTableViewport( | |
horizontalOffset: horizontalOffset, | |
horizontalAxisDirection: horizontalDetails.direction, | |
verticalOffset: verticalOffset, | |
verticalAxisDirection: verticalDetails.direction, | |
mainAxis: mainAxis, | |
delegate: delegate as TwoDimensionalChildBuilderDelegate, | |
cacheExtent: cacheExtent, | |
clipBehavior: clipBehavior, | |
); | |
} | |
} | |
class SimpleBuilderTableViewport extends TwoDimensionalViewport { | |
const SimpleBuilderTableViewport({ | |
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 RenderSimpleBuilderTableViewport( | |
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, RenderSimpleBuilderTableViewport renderObject) { | |
renderObject | |
..horizontalOffset = horizontalOffset | |
..horizontalAxisDirection = horizontalAxisDirection | |
..verticalOffset = verticalOffset | |
..verticalAxisDirection = verticalAxisDirection | |
..mainAxis = mainAxis | |
..delegate = delegate | |
..cacheExtent = cacheExtent | |
..clipBehavior = clipBehavior; | |
} | |
} | |
class RenderSimpleBuilderTableViewport extends RenderTwoDimensionalViewport { | |
RenderSimpleBuilderTableViewport({ | |
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; | |
final int maxColumnIndex; | |
maxRowIndex = builderDelegate.maxYIndex!; | |
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.tighten(width: 200.0, height: 200.0)); | |
// 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; | |
} | |
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