Created
January 8, 2024 10:51
-
-
Save Amir-P/2c4cc823007d1dc0a9727be611e62f2c to your computer and use it in GitHub Desktop.
Flutter two dimensional viewport buildOrObtainChildFor issue
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 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