interactive_viewer.builder.0.dart 4.18 KB
Newer Older
1 2 3 4 5 6 7
// 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/material.dart';
import 'package:vector_math/vector_math_64.dart' show Quad, Vector3;

8 9
/// Flutter code sample for [InteractiveViewer.builder].

10 11 12
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
                cellWidth: _cellWidth,
                cellHeight: _cellHeight,
                viewport: axisAlignedBoundingBox(viewport),
                builder: (BuildContext context, int row, int column) {
                  return Container(
                    height: _cellHeight,
                    width: _cellWidth,
83
                    color: row % 2 + column % 2 == 1 ? Colors.white : Colors.grey.withOpacity(0.1),
84 85 86 87 88 89
                    child: Align(
                      child: Text('$row x $column'),
                    ),
                  );
                },
              );
90 91 92 93 94 95 96 97
            },
          );
        },
      ),
    );
  }
}

98
typedef _CellBuilder = Widget Function(BuildContext context, int row, int column);
99 100 101 102

class _TableBuilder extends StatelessWidget {
  const _TableBuilder({
    required this.cellWidth,
103 104
    required this.cellHeight,
    required this.viewport,
105
    required this.builder,
106
  });
107 108

  final double cellWidth;
109 110
  final double cellHeight;
  final Rect viewport;
111 112 113 114
  final _CellBuilder builder;

  @override
  Widget build(BuildContext context) {
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    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),
              ),
        ],
      ),
140 141 142
    );
  }
}