interactive_viewer.builder.0.dart 4.23 KB
Newer Older
1 2 3 4
// 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.

5
/// Flutter code sample for [InteractiveViewer.builder].
6 7 8 9 10 11 12

import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' show Quad, Vector3;

void main() => runApp(const IVBuilderExampleApp());

class IVBuilderExampleApp extends StatelessWidget {
13
  const IVBuilderExampleApp({super.key});
14 15 16 17 18 19 20 21

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('IV Builder Example'),
        ),
22
        body: const _IVBuilderExample(),
23 24 25 26 27 28
      ),
    );
  }
}

class _IVBuilderExample extends StatefulWidget {
29 30
  const _IVBuilderExample();

31
  @override
32
  State<_IVBuilderExample> createState() => _IVBuilderExampleState();
33 34 35
}

class _IVBuilderExampleState extends State<_IVBuilderExample> {
36 37 38
  static const double _cellWidth = 160.0;
  static const double _cellHeight = 80.0;

39 40
  // Returns the axis aligned bounding box for the given Quad, which might not
  // be axis aligned.
41 42 43 44 45
  Rect axisAlignedBoundingBox(Quad quad) {
    double xMin = quad.point0.x;
    double xMax = quad.point0.x;
    double yMin = quad.point0.y;
    double yMax = quad.point0.y;
46 47 48
    for (final Vector3 point in <Vector3>[
      quad.point1,
      quad.point2,
49
      quad.point3,
50
    ]) {
51
      if (point.x < xMin) {
52
        xMin = point.x;
53
      } else if (point.x > xMax) {
54 55
        xMax = point.x;
      }
56 57

      if (point.y < yMin) {
58
        yMin = point.y;
59
      } else if (point.y > yMax) {
60 61 62 63
        yMax = point.y;
      }
    }

64
    return Rect.fromLTRB(xMin, yMin, xMax, yMax);
65 66 67 68 69 70 71 72
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          return InteractiveViewer.builder(
73
            boundaryMargin: const EdgeInsets.all(double.infinity),
74 75
            builder: (BuildContext context, Quad viewport) {
              return _TableBuilder(
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
                cellWidth: _cellWidth,
                cellHeight: _cellHeight,
                viewport: axisAlignedBoundingBox(viewport),
                builder: (BuildContext context, int row, int column) {
                  return Container(
                    height: _cellHeight,
                    width: _cellWidth,
                    color: row % 2 + column % 2 == 1
                        ? Colors.white
                        : Colors.grey.withOpacity(0.1),
                    child: Align(
                      child: Text('$row x $column'),
                    ),
                  );
                },
              );
92 93 94 95 96 97 98 99 100 101 102 103 104 105
            },
          );
        },
      ),
    );
  }
}

typedef _CellBuilder = Widget Function(
    BuildContext context, int row, int column);

class _TableBuilder extends StatelessWidget {
  const _TableBuilder({
    required this.cellWidth,
106 107
    required this.cellHeight,
    required this.viewport,
108
    required this.builder,
109
  });
110 111

  final double cellWidth;
112 113
  final double cellHeight;
  final Rect viewport;
114 115 116 117
  final _CellBuilder builder;

  @override
  Widget build(BuildContext context) {
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
    final int firstRow = (viewport.top / cellHeight).floor();
    final int lastRow = (viewport.bottom / cellHeight).ceil();
    final int firstCol = (viewport.left / cellWidth).floor();
    final int lastCol = (viewport.right / cellWidth).ceil();

    // This will create and render exactly (lastRow - firstRow) * (lastCol - firstCol) cells

    return SizedBox(
      // Stack needs constraints, even though we then Clip.none outside of them.
      // InteractiveViewer.builder always sets constrained to false, giving infinite constraints to the child.
      // See: https://master-api.flutter.dev/flutter/widgets/InteractiveViewer/constrained.html
      width: 1,
      height: 1,
      child: Stack(
        clipBehavior: Clip.none,
        children: <Widget>[
          for (int row = firstRow; row < lastRow; row++)
            for (int col = firstCol; col < lastCol; col++)
              Positioned(
                left: col * cellWidth,
                top: row * cellHeight,
                child: builder(context, row, col),
              ),
        ],
      ),
143 144 145
    );
  }
}