Revert "InteractiveViewer.builder (#77414)" (#78867)
This reverts commit 2c2c8a7561fabbd8a841cc64fff2f98a8373bea4.
diff --git a/packages/flutter/lib/src/widgets/interactive_viewer.dart b/packages/flutter/lib/src/widgets/interactive_viewer.dart
index 835d08c..bbe53de 100644
--- a/packages/flutter/lib/src/widgets/interactive_viewer.dart
+++ b/packages/flutter/lib/src/widgets/interactive_viewer.dart
@@ -11,17 +11,8 @@
import 'basic.dart';
import 'framework.dart';
import 'gesture_detector.dart';
-import 'layout_builder.dart';
import 'ticker_provider.dart';
-/// A type for widget builders that take a [Quad] of the current viewport.
-///
-/// See also:
-///
-/// * [InteractiveViewer.builder], whose builder is of this type.
-/// * [WidgetBuilder], which is similar, but takes no viewport.
-typedef InteractiveViewerWidgetBuilder = Widget Function(BuildContext context, Quad viewport);
-
/// A widget that enables pan and zoom interactions with its child.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=zrn7V3bMJvg}
@@ -41,6 +32,8 @@
/// robust positioning of an InteractiveViewer child that works for all screen
/// sizes and child sizes.
///
+/// The [child] must not be null.
+///
/// See also:
/// * The [Flutter Gallery's transformations demo](https://github.com/flutter/gallery/blob/master/lib/demos/reference/transformations_demo.dart),
/// which includes the use of InteractiveViewer.
@@ -74,7 +67,7 @@
class InteractiveViewer extends StatefulWidget {
/// Create an InteractiveViewer.
///
- /// The `child` parameter must not be null.
+ /// The [child] parameter must not be null.
InteractiveViewer({
Key? key,
this.clipBehavior = Clip.hardEdge,
@@ -91,7 +84,7 @@
this.panEnabled = true,
this.scaleEnabled = true,
this.transformationController,
- required Widget child,
+ required this.child,
}) : assert(alignPanAxis != null),
assert(child != null),
assert(constrained != null),
@@ -110,51 +103,6 @@
&& boundaryMargin.vertical.isInfinite) || (boundaryMargin.top.isFinite
&& boundaryMargin.right.isFinite && boundaryMargin.bottom.isFinite
&& boundaryMargin.left.isFinite)),
- builder = _getBuilderForChild(child),
- super(key: key);
-
- // TODO(justinmc): Example. Or put it in the builder param docs.
- /// Creates an InteractiveViewer for a child that is created on demand.
- ///
- /// Can be used to render a child that changes in response to the current
- /// transformation.
- ///
- /// The [builder] parameter must not be null. See its docs for an example of
- /// using it to optimize a large child.
- InteractiveViewer.builder({
- Key? key,
- this.clipBehavior = Clip.hardEdge,
- this.alignPanAxis = false,
- this.boundaryMargin = EdgeInsets.zero,
- // These default scale values were eyeballed as reasonable limits for common
- // use cases.
- this.maxScale = 2.5,
- this.minScale = 0.8,
- this.onInteractionEnd,
- this.onInteractionStart,
- this.onInteractionUpdate,
- this.panEnabled = true,
- this.scaleEnabled = true,
- this.transformationController,
- required this.builder,
- }) : assert(alignPanAxis != null),
- assert(builder != null),
- assert(minScale != null),
- assert(minScale > 0),
- assert(minScale.isFinite),
- assert(maxScale != null),
- assert(maxScale > 0),
- assert(!maxScale.isNaN),
- assert(maxScale >= minScale),
- assert(panEnabled != null),
- assert(scaleEnabled != null),
- // boundaryMargin must be either fully infinite or fully finite, but not
- // a mix of both.
- assert((boundaryMargin.horizontal.isInfinite
- && boundaryMargin.vertical.isInfinite) || (boundaryMargin.top.isFinite
- && boundaryMargin.right.isFinite && boundaryMargin.bottom.isFinite
- && boundaryMargin.left.isFinite)),
- constrained = false,
super(key: key);
/// If set to [Clip.none], the child may extend beyond the size of the InteractiveViewer,
@@ -190,206 +138,13 @@
/// No edge can be NaN.
///
/// Defaults to [EdgeInsets.zero], which results in boundaries that are the
- /// exact same size and position as the child.
+ /// exact same size and position as the [child].
final EdgeInsets boundaryMargin;
- /// Builds the child of this widget.
+ /// The Widget to perform the transformations on.
///
- /// If a child is passed directly, then this is simply a function that returns
- /// that child.
- ///
- /// If using the [InteractiveViewer.builder] constructor, this can be passed
- /// directly. This allows the child to be built in response to the current
- /// transformation.
- ///
- /// {@tool dartpad --template=freeform}
- ///
- /// This example shows how to use builder to create a [Table] whose cell
- /// contents are only built when they are visible. Built and remove cells are
- /// logged in the console for illustration.
- ///
- /// ```dart main
- /// import 'package:vector_math/vector_math_64.dart' show Quad, Vector3;
- ///
- /// import 'package:flutter/material.dart';
- /// import 'package:flutter/widgets.dart';
- ///
- /// void main() => runApp(const IVBuilderExampleApp());
- ///
- /// class IVBuilderExampleApp extends StatelessWidget {
- /// const IVBuilderExampleApp({Key? key}) : super(key: key);
- ///
- /// @override
- /// Widget build(BuildContext context) {
- /// return MaterialApp(
- /// home: Scaffold(
- /// appBar: AppBar(
- /// title: const Text('IV Builder Example'),
- /// ),
- /// body: _IVBuilderExample(),
- /// ),
- /// );
- /// }
- /// }
- ///
- /// class _IVBuilderExample extends StatefulWidget {
- /// @override
- /// _IVBuilderExampleState createState() => _IVBuilderExampleState();
- /// }
- ///
- /// class _IVBuilderExampleState extends State<_IVBuilderExample> {
- /// final TransformationController _transformationController = TransformationController();
- ///
- /// static const double _cellWidth = 200.0;
- /// static const double _cellHeight = 26.0;
- ///
- /// // Returns true iff the given cell is currently visible. Caches viewport
- /// // calculations.
- /// late Quad _cachedViewport;
- /// late int _firstVisibleRow;
- /// late int _firstVisibleColumn;
- /// late int _lastVisibleRow;
- /// late int _lastVisibleColumn;
- /// bool _isCellVisible(int row, int column, Quad viewport) {
- /// if (viewport != _cachedViewport) {
- /// final Rect aabb = _axisAlignedBoundingBox(viewport);
- /// _cachedViewport = viewport;
- /// _firstVisibleRow = (aabb.top / _cellHeight).floor();
- /// _firstVisibleColumn = (aabb.left / _cellWidth).floor();
- /// _lastVisibleRow = (aabb.bottom / _cellHeight).floor();
- /// _lastVisibleColumn = (aabb.right / _cellWidth).floor();
- /// }
- /// return row >= _firstVisibleRow && row <= _lastVisibleRow
- /// && column >= _firstVisibleColumn && column <= _lastVisibleColumn;
- /// }
- ///
- /// // Returns the axis aligned bounding box for the given Quad, which might not
- /// // be axis aligned.
- /// Rect _axisAlignedBoundingBox(Quad quad) {
- /// double? xMin;
- /// double? xMax;
- /// double? yMin;
- /// double? yMax;
- /// for (final Vector3 point in <Vector3>[quad.point0, quad.point1, quad.point2, quad.point3]) {
- /// if (xMin == null || point.x < xMin) {
- /// xMin = point.x;
- /// }
- /// if (xMax == null || point.x > xMax) {
- /// xMax = point.x;
- /// }
- /// if (yMin == null || point.y < yMin) {
- /// yMin = point.y;
- /// }
- /// if (yMax == null || point.y > yMax) {
- /// yMax = point.y;
- /// }
- /// }
- /// return Rect.fromLTRB(xMin!, yMin!, xMax!, yMax!);
- /// }
- ///
- /// void _onChangeTransformation() {
- /// setState(() {});
- /// }
- ///
- /// @override
- /// void initState() {
- /// super.initState();
- /// _transformationController.addListener(_onChangeTransformation);
- /// }
- ///
- /// @override
- /// void dispose() {
- /// _transformationController.removeListener(_onChangeTransformation);
- /// super.dispose();
- /// }
- ///
- /// @override
- /// Widget build(BuildContext context) {
- /// return Center(
- /// child: LayoutBuilder(
- /// builder: (BuildContext context, BoxConstraints constraints) {
- /// return InteractiveViewer.builder(
- /// alignPanAxis: true,
- /// scaleEnabled: false,
- /// transformationController: _transformationController,
- /// builder: (BuildContext context, Quad viewport) {
- /// // A simple extension of Table that builds cells.
- /// return _TableBuilder(
- /// rowCount: 60,
- /// columnCount: 6,
- /// cellWidth: _cellWidth,
- /// builder: (BuildContext context, int row, int column) {
- /// if (!_isCellVisible(row, column, viewport)) {
- /// print('removing cell ($row, $column)');
- /// return Container(height: _cellHeight);
- /// }
- /// print('building cell ($row, $column)');
- /// return Container(
- /// height: _cellHeight,
- /// color: row % 2 + column % 2 == 1 ? Colors.white : Colors.grey.withOpacity(0.1),
- /// child: Align(
- /// alignment: Alignment.centerLeft,
- /// child: Text('$row x $column'),
- /// ),
- /// );
- /// }
- /// );
- /// },
- /// );
- /// },
- /// ),
- /// );
- /// }
- /// }
- ///
- /// typedef _CellBuilder = Widget Function(BuildContext context, int row, int column);
- ///
- /// class _TableBuilder extends StatelessWidget {
- /// const _TableBuilder({
- /// required this.rowCount,
- /// required this.columnCount,
- /// required this.cellWidth,
- /// required this.builder,
- /// }) : assert(rowCount > 0),
- /// assert(columnCount > 0);
- ///
- /// final int rowCount;
- /// final int columnCount;
- /// final double cellWidth;
- /// final _CellBuilder builder;
- ///
- /// @override
- /// Widget build(BuildContext context) {
- /// return Table(
- /// // ignore: prefer_const_literals_to_create_immutables
- /// columnWidths: <int, TableColumnWidth>{
- /// for (int column = 0; column < columnCount; column++)
- /// column: FixedColumnWidth(cellWidth),
- /// },
- /// // ignore: prefer_const_literals_to_create_immutables
- /// children: <TableRow>[
- /// for (int row = 0; row < rowCount; row++)
- /// // ignore: prefer_const_constructors
- /// TableRow(
- /// // ignore: prefer_const_literals_to_create_immutables
- /// children: <Widget>[
- /// for (int column = 0; column < columnCount; column++)
- /// builder(context, row, column),
- /// ],
- /// ),
- /// ],
- /// );
- /// }
- /// }
- /// ```
- /// {@end-tool}
- ///
- /// See also:
- ///
- /// * [ListView.builder], which follows a similar pattern.
- /// * [InteractiveViewer.builder], which has an example of building the
- /// child on demand.
- final InteractiveViewerWidgetBuilder builder;
+ /// Cannot be null.
+ final Widget child;
/// Whether the normal size constraints at this point in the widget tree are
/// applied to the child.
@@ -667,13 +422,6 @@
/// * [TextEditingController] for an example of another similar pattern.
final TransformationController? transformationController;
- // Get a InteractiveViewerWidgetBuilder that simply returns the given child.
- static InteractiveViewerWidgetBuilder _getBuilderForChild(Widget child) {
- return (BuildContext context, Quad viewport) {
- return child;
- };
- }
-
/// Returns the closest point to the given point on the given line segment.
@visibleForTesting
static Vector3 getNearestPointOnLine(Vector3 point, Vector3 l1, Vector3 l2) {
@@ -1314,52 +1062,44 @@
@override
Widget build(BuildContext context) {
+ Widget child = Transform(
+ transform: _transformationController!.value,
+ child: KeyedSubtree(
+ key: _childKey,
+ child: widget.child,
+ ),
+ );
+
+ if (!widget.constrained) {
+ child = OverflowBox(
+ alignment: Alignment.topLeft,
+ minWidth: 0.0,
+ minHeight: 0.0,
+ maxWidth: double.infinity,
+ maxHeight: double.infinity,
+ child: child,
+ );
+ }
+
+ if (widget.clipBehavior != Clip.none) {
+ child = ClipRect(
+ clipBehavior: widget.clipBehavior,
+ child: child,
+ );
+ }
+
// A GestureDetector allows the detection of panning and zooming gestures on
// the child.
return Listener(
key: _parentKey,
onPointerSignal: _receivedPointerSignal,
- child: LayoutBuilder(
- builder: (BuildContext context, BoxConstraints constraints) {
- final Matrix4 matrix = _transformationController!.value;
- // When constrained is false, such as when using
- // InteractiveViewer.builder, then the viewport is the size of the
- // constraints.
- Widget child = Transform(
- transform: matrix,
- child: KeyedSubtree(
- key: _childKey,
- child: widget.builder(context, _transformViewport(matrix, Offset.zero & constraints.biggest)),
- ),
- );
-
- if (!widget.constrained) {
- child = OverflowBox(
- alignment: Alignment.topLeft,
- minWidth: 0.0,
- minHeight: 0.0,
- maxWidth: double.infinity,
- maxHeight: double.infinity,
- child: child,
- );
- }
-
- if (widget.clipBehavior != Clip.none) {
- child = ClipRect(
- clipBehavior: widget.clipBehavior,
- child: child,
- );
- }
-
- return GestureDetector(
- behavior: HitTestBehavior.opaque, // Necessary when panning off screen.
- dragStartBehavior: DragStartBehavior.start,
- onScaleEnd: _onScaleEnd,
- onScaleStart: _onScaleStart,
- onScaleUpdate: _onScaleUpdate,
- child: child,
- );
- },
+ child: GestureDetector(
+ behavior: HitTestBehavior.opaque, // Necessary when panning off screen.
+ dragStartBehavior: DragStartBehavior.start,
+ onScaleEnd: _onScaleEnd,
+ onScaleStart: _onScaleStart,
+ onScaleUpdate: _onScaleUpdate,
+ child: child,
),
);
}
diff --git a/packages/flutter/test/widgets/interactive_viewer_test.dart b/packages/flutter/test/widgets/interactive_viewer_test.dart
index 3a5293d..08bc8e7 100644
--- a/packages/flutter/test/widgets/interactive_viewer_test.dart
+++ b/packages/flutter/test/widgets/interactive_viewer_test.dart
@@ -1026,89 +1026,6 @@
findsOneWidget,
);
});
-
- testWidgets('builder can change widgets that are off-screen', (WidgetTester tester) async {
- final TransformationController transformationController = TransformationController();
- const double childHeight = 10.0;
- await tester.pumpWidget(
- MaterialApp(
- home: Scaffold(
- body: Center(
- child: SizedBox(
- height: 50.0,
- child: InteractiveViewer.builder(
- transformationController: transformationController,
- scaleEnabled: false,
- boundaryMargin: const EdgeInsets.all(double.infinity),
- // Build visible children green, off-screen children red.
- builder: (BuildContext context, Quad viewportQuad) {
- final Rect viewport = _axisAlignedBoundingBox(viewportQuad);
- final List<Container> children = <Container>[];
- for (int i = 0; i < 10; i++) {
- final double childTop = i * childHeight;
- final double childBottom = childTop + childHeight;
- final bool visible = (childBottom >= viewport.top && childBottom <= viewport.bottom)
- || (childTop >= viewport.top && childTop <= viewport.bottom);
- children.add(Container(
- height: childHeight,
- color: visible ? Colors.green : Colors.red,
- ));
- }
- return Column(
- children: children,
- );
- },
- ),
- ),
- ),
- ),
- ),
- );
-
- expect(transformationController.value, equals(Matrix4.identity()));
-
- // The first six are partially visible and therefore green.
- int i = 0;
- for (final Element element in find.byType(Container, skipOffstage: false).evaluate()) {
- final Container container = element.widget as Container;
- if (i < 6) {
- expect(container.color, Colors.green);
- } else {
- expect(container.color, Colors.red);
- }
- i++;
- }
-
- // Drag to pan down past the first child.
- final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
- const double translationY = 15.0;
- final Offset childInterior = Offset(
- childOffset.dx,
- childOffset.dy + translationY,
- );
- final TestGesture gesture = await tester.startGesture(childInterior);
- addTearDown(gesture.removePointer);
- await tester.pump();
- await gesture.moveTo(childOffset);
- await tester.pump();
- await gesture.up();
- await tester.pumpAndSettle();
- expect(transformationController.value, isNot(Matrix4.identity()));
- expect(transformationController.value.getTranslation().y, -translationY);
-
- // After scrolling down a bit, the first child is not visible, the next
- // six are, and the final three are not.
- i = 0;
- for (final Element element in find.byType(Container, skipOffstage: false).evaluate()) {
- final Container container = element.widget as Container;
- if (i > 0 && i < 7) {
- expect(container.color, Colors.green);
- } else {
- expect(container.color, Colors.red);
- }
- i++;
- }
- });
});
group('getNearestPointOnLine', () {
@@ -1318,27 +1235,3 @@
});
});
}
-
-// Returns the axis aligned bounding box for the given Quad, which might not
-// be axis aligned.
-Rect _axisAlignedBoundingBox(Quad quad) {
- double? xMin;
- double? xMax;
- double? yMin;
- double? yMax;
- for (final Vector3 point in <Vector3>[quad.point0, quad.point1, quad.point2, quad.point3]) {
- if (xMin == null || point.x < xMin) {
- xMin = point.x;
- }
- if (xMax == null || point.x > xMax) {
- xMax = point.x;
- }
- if (yMin == null || point.y < yMin) {
- yMin = point.y;
- }
- if (yMax == null || point.y > yMax) {
- yMax = point.y;
- }
- }
- return Rect.fromLTRB(xMin!, yMin!, xMax!, yMax!);
-}