Unverified Commit ac1fc234 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Update example code and docs for InteractiveViewer.builder (#98623)

parent eb831810
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
// Flutter code sample for InteractiveViewer.builder // Flutter code sample for InteractiveViewer.builder
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:vector_math/vector_math_64.dart' show Quad, Vector3; import 'package:vector_math/vector_math_64.dart' show Quad, Vector3;
void main() => runApp(const IVBuilderExampleApp()); void main() => runApp(const IVBuilderExampleApp());
...@@ -32,77 +31,34 @@ class _IVBuilderExample extends StatefulWidget { ...@@ -32,77 +31,34 @@ class _IVBuilderExample extends StatefulWidget {
} }
class _IVBuilderExampleState extends State<_IVBuilderExample> { class _IVBuilderExampleState extends State<_IVBuilderExample> {
final TransformationController _transformationController = static const double _cellWidth = 160.0;
TransformationController(); static const double _cellHeight = 80.0;
static const double _cellWidth = 200.0; // Returns the axis aligned bounding box for the given Quad, which might not be axis aligned.
static const double _cellHeight = 26.0; Rect axisAlignedBoundingBox(Quad quad) {
double xMin = quad.point0.x;
// Returns true iff the given cell is currently visible. Caches viewport double xMax = quad.point0.x;
// calculations. double yMin = quad.point0.y;
Quad? _cachedViewport; double yMax = quad.point0.y;
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>[ for (final Vector3 point in <Vector3>[
quad.point0,
quad.point1, quad.point1,
quad.point2, quad.point2,
quad.point3 quad.point3
]) { ]) {
if (xMin == null || point.x < xMin) { if (point.x < xMin) {
xMin = point.x; xMin = point.x;
} } else if (point.x > xMax) {
if (xMax == null || point.x > xMax) {
xMax = point.x; xMax = point.x;
} }
if (yMin == null || point.y < yMin) {
if (point.y < yMin) {
yMin = point.y; yMin = point.y;
} } else if (point.y > yMax) {
if (yMax == null || point.y > yMax) {
yMax = point.y; yMax = point.y;
} }
} }
return Rect.fromLTRB(xMin!, yMin!, xMax!, yMax!);
}
void _onChangeTransformation() { return Rect.fromLTRB(xMin, yMin, xMax, yMax);
setState(() {});
}
@override
void initState() {
super.initState();
_transformationController.addListener(_onChangeTransformation);
}
@override
void dispose() {
_transformationController.removeListener(_onChangeTransformation);
super.dispose();
} }
@override @override
...@@ -111,32 +67,25 @@ class _IVBuilderExampleState extends State<_IVBuilderExample> { ...@@ -111,32 +67,25 @@ class _IVBuilderExampleState extends State<_IVBuilderExample> {
child: LayoutBuilder( child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
return InteractiveViewer.builder( return InteractiveViewer.builder(
alignPanAxis: true, boundaryMargin: const EdgeInsets.all(double.infinity),
scaleEnabled: false,
transformationController: _transformationController,
builder: (BuildContext context, Quad viewport) { builder: (BuildContext context, Quad viewport) {
// A simple extension of Table that builds cells.
return _TableBuilder( return _TableBuilder(
rowCount: 60,
columnCount: 6,
cellWidth: _cellWidth, cellWidth: _cellWidth,
cellHeight: _cellHeight,
viewport: axisAlignedBoundingBox(viewport),
builder: (BuildContext context, int row, int column) { builder: (BuildContext context, int row, int column) {
if (!_isCellVisible(row, column, viewport)) {
debugPrint('removing cell ($row, $column)');
return Container(height: _cellHeight);
}
debugPrint('building cell ($row, $column)');
return Container( return Container(
height: _cellHeight, height: _cellHeight,
width: _cellWidth,
color: row % 2 + column % 2 == 1 color: row % 2 + column % 2 == 1
? Colors.white ? Colors.white
: Colors.grey.withOpacity(0.1), : Colors.grey.withOpacity(0.1),
child: Align( child: Align(
alignment: Alignment.centerLeft,
child: Text('$row x $column'), child: Text('$row x $column'),
), ),
); );
}); },
);
}, },
); );
}, },
...@@ -150,38 +99,44 @@ typedef _CellBuilder = Widget Function( ...@@ -150,38 +99,44 @@ typedef _CellBuilder = Widget Function(
class _TableBuilder extends StatelessWidget { class _TableBuilder extends StatelessWidget {
const _TableBuilder({ const _TableBuilder({
required this.rowCount,
required this.columnCount,
required this.cellWidth, required this.cellWidth,
required this.cellHeight,
required this.viewport,
required this.builder, required this.builder,
}) : assert(rowCount > 0), });
assert(columnCount > 0);
final int rowCount;
final int columnCount;
final double cellWidth; final double cellWidth;
final double cellHeight;
final Rect viewport;
final _CellBuilder builder; final _CellBuilder builder;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Table( final int firstRow = (viewport.top / cellHeight).floor();
// ignore: prefer_const_literals_to_create_immutables final int lastRow = (viewport.bottom / cellHeight).ceil();
columnWidths: <int, TableColumnWidth>{ final int firstCol = (viewport.left / cellWidth).floor();
for (int column = 0; column < columnCount; column++) final int lastCol = (viewport.right / cellWidth).ceil();
column: FixedColumnWidth(cellWidth),
}, // This will create and render exactly (lastRow - firstRow) * (lastCol - firstCol) cells
// ignore: prefer_const_literals_to_create_immutables
children: <TableRow>[ return SizedBox(
for (int row = 0; row < rowCount; row++) // Stack needs constraints, even though we then Clip.none outside of them.
// ignore: prefer_const_constructors // InteractiveViewer.builder always sets constrained to false, giving infinite constraints to the child.
TableRow( // See: https://master-api.flutter.dev/flutter/widgets/InteractiveViewer/constrained.html
// ignore: prefer_const_literals_to_create_immutables width: 1,
height: 1,
child: Stack(
clipBehavior: Clip.none,
children: <Widget>[ children: <Widget>[
for (int column = 0; column < columnCount; column++) for (int row = firstRow; row < lastRow; row++)
builder(context, row, column), for (int col = firstCol; col < lastCol; col++)
], Positioned(
left: col * cellWidth,
top: row * cellHeight,
child: builder(context, row, col),
), ),
], ],
),
); );
} }
} }
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'package:flutter_api_samples/widgets/interactive_viewer/interactive_viewer.builder.0.dart'
as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('has correct items on screen', (WidgetTester tester) async {
await tester.pumpWidget(
const example.IVBuilderExampleApp(),
);
final Finder positionedFinder = find.byType(Positioned);
final Finder zeroFinder = find.text('0 x 0');
final Finder nineFinder = find.text('0 x 9');
expect(positionedFinder, findsNWidgets(35));
expect(zeroFinder, findsOneWidget);
expect(nineFinder, findsNothing);
const Offset firstLocation = Offset(750.0, 100.0);
final TestGesture gesture = await tester.startGesture(firstLocation);
const Offset secondLocation = Offset(50.0, 100.0);
await gesture.moveTo(secondLocation);
await tester.pump();
expect(positionedFinder, findsNWidgets(42));
expect(nineFinder, findsOneWidget);
expect(zeroFinder, findsNothing);
});
}
...@@ -37,15 +37,15 @@ typedef InteractiveViewerWidgetBuilder = Widget Function(BuildContext context, Q ...@@ -37,15 +37,15 @@ typedef InteractiveViewerWidgetBuilder = Widget Function(BuildContext context, Q
/// don't set [clipBehavior] or be sure that the InteractiveViewer widget is the /// don't set [clipBehavior] or be sure that the InteractiveViewer widget is the
/// size of the area that should be interactive. /// size of the area that should be interactive.
/// ///
/// See [flutter-go](https://github.com/justinmc/flutter-go) for an example of
/// robust positioning of an InteractiveViewer child that works for all screen
/// sizes and child sizes.
///
/// The [child] must not be null. /// The [child] must not be null.
/// ///
/// See also: /// See also:
/// * The [Flutter Gallery's transformations demo](https://github.com/flutter/gallery/blob/master/lib/demos/reference/transformations_demo.dart), /// * 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. /// which includes the use of InteractiveViewer.
/// * The [flutter-go demo](https://github.com/justinmc/flutter-go), which includes robust positioning of an InteractiveViewer child
/// that works for all screen sizes and child sizes.
/// * The [Lazy Flutter Performance Session](https://www.youtube.com/watch?v=qax_nOpgz7E), which includes the use of an InteractiveViewer to
/// performantly view subsets of a large set of widgets using the builder constructor.
/// ///
/// {@tool dartpad} /// {@tool dartpad}
/// This example shows a simple Container that can be panned and zoomed. /// This example shows a simple Container that can be panned and zoomed.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment