Skip to content

Instantly share code, notes, and snippets.

@Amir-P
Created January 8, 2024 10:51
Show Gist options
  • Select an option

  • Save Amir-P/2c4cc823007d1dc0a9727be611e62f2c to your computer and use it in GitHub Desktop.

Select an option

Save Amir-P/2c4cc823007d1dc0a9727be611e62f2c to your computer and use it in GitHub Desktop.
Flutter two dimensional viewport buildOrObtainChildFor issue
// 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 StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
final vicinityToKey1 = <ChildVicinity, String>{
ChildVicinity(xIndex: 1, yIndex: 1): '1',
ChildVicinity(xIndex: 1, yIndex: 2): '2',
};
final vicinityToKey2 = <ChildVicinity, String>{
ChildVicinity(xIndex: 0, yIndex: 0): '1',
ChildVicinity(xIndex: 1, yIndex: 1): '2',
};
class _MyHomePageState extends State<MyHomePage> {
late Map<ChildVicinity, String> vicinityToKey = vicinityToKey1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() {
if (vicinityToKey == vicinityToKey1) {
vicinityToKey = vicinityToKey2;
} else {
vicinityToKey = vicinityToKey1;
}
}),
),
body: TwoDimensionalGridView(
diagonalDragBehavior: DiagonalDragBehavior.free,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 30,
maxYIndex: 30,
addAutomaticKeepAlives: false,
addRepaintBoundaries: false,
builder: (BuildContext context, ChildVicinity vicinity) {
final key = vicinityToKey[vicinity];
if (key != null) {
return AutomaticKeepAlive(
key: ValueKey(key),
child: RepaintBoundary(
child: 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(key)),
),
),
);
}
return null;
}),
),
);
}
}
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 = 10,
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);
if (child != null) {
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