Home > other >  Build a hall plan in Flutter. What is the best solution?
Build a hall plan in Flutter. What is the best solution?

Time:12-09

I want to make an interactive hall plan in Flutter, but I can't figure out how to implement it correctly.

enter image description here

Key points:

  • the plan should scale without loss of quality.
  • the view of the place should change slightly when you click

The first option: is to make it as a Canvas of a large size, and put it in the InteractiveViewer, but I do not know if it is possible to change the already drawn parts on tap?

The second option: is to make each place as an SVG with onTap event, and put it all in a Stack widget. And then again in InteractiveViewer. But there will be about 500 seats on the plan, and I'm not sure if the phone can handle scaling such a scene?

Maybe there are other solutions? I will be very grateful for the advice, I still have very little experience in Flutter.

CodePudding user response:

This can be achieved with InteractiveViewer & GridView:

for any shape of place, you can draw it using 2d array, like

  List<List<int>> triangleHall = [
    [0, 0, 1, 0, 0],
    [0, 1, 1, 1, 0],
    [1, 1, 1, 1, 1],
  ]

  List<List<int>> arcHall = [
    [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
    [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  ]

  List<Widget> _generateHallItem(List<List<int>> hall){
    List<Widget> list = [];

    hall.map((row) {
      row.map((item) {
        item == 0 ? list.add(Container(width:5, height:5)) : list.add(Container(width:5, height:5, color: Colors.red));      
      });      
    });
    return list;
  }

  Wrap(
    children: _generateHallItem,

OR

  InteractiveViewer(
    minScale: 0.8,
    maxScale: 2.0,
    child: GridView(),
  ),

CodePudding user response:

try InteractiveViewer and CustomMultiChildLayout as a child (notice how easy you can animate the seats inside CinemaDelegate with passed AnimationController):

class CinemaHall extends StatefulWidget {
  const CinemaHall({Key? key}) : super(key: key);

  @override
  _CinemaHallState createState() => _CinemaHallState();
}

class _CinemaHallState extends State<CinemaHall> with TickerProviderStateMixin {
  late List<Offset> positions;
  late List<ValueNotifier<bool>> states;
  late AnimationController ctrl;

  @override
  void initState() {
    super.initState();
    positions = [
      ...List.generate(8, (index) => (Offset(index   1, 0))),
      for (int r = 1; r < 7; r  )
        ...List.generate(10, (index) => (Offset(index.toDouble(), r.toDouble()))),
    ];
    states = List.generate(positions.length, (index) => ValueNotifier(false));
    ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000));
    Future.delayed(const Duration(milliseconds: 2000), () => ctrl.forward());
  }

  @override
  Widget build(BuildContext context) {
    return ColoredBox(
      color: const Color(0xff000044),
      child: InteractiveViewer(
        minScale: 1.0,
        maxScale: 3.0,
        child: CustomMultiChildLayout(
          delegate: CinemaHallDelegate(ctrl, positions, const Size(64, 64)),
          children: List.generate(positions.length, (index) => LayoutId(
            id: index,
            child: FittedBox(
              child: ValueListenableBuilder<bool>(
                valueListenable: states[index],
                builder: (context, state, child) {
                  return TweenAnimationBuilder<double>(
                    duration: const Duration(milliseconds: 500),
                    tween: Tween(end: state? 0.8 : 0.2),
                    builder: (context, t, child) {
                      return IconButton(
                        icon: Icon(index == 43? Icons.people : Icons.person, color: Colors.white.withOpacity(t)),
                        padding: EdgeInsets.zero,
                        iconSize: 64,
                        tooltip: 'seat ${positions[index].dy.toInt()   1}, ${positions[index].dx.toInt()   1}',
                        onPressed: () {
                          states[index].value = !state;
                          print(index);
                        },
                      );
                    }
                  );
                }
              ),
            )
          )),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    ctrl.dispose();
  }
}

class CinemaHallDelegate extends MultiChildLayoutDelegate {
  final AnimationController ctrl;
  final List<Offset> positions;
  final Size seatSize;
  late Size cinemaHallSize;

  CinemaHallDelegate(this.ctrl, this.positions, this.seatSize) : super(relayout: ctrl) {
    double cols = positions.map((o) => o.dx).reduce(max)   1;
    double rows = positions.map((o) => o.dy).reduce(max)   1;
    cinemaHallSize = Size(cols * seatSize.width, rows * seatSize.height);
  }

  @override
  void performLayout(ui.Size size) {
    final matrix = sizeToRect(cinemaHallSize, Offset.zero & size);
    final seatRect = MatrixUtils.transformRect(matrix, Offset.zero & seatSize);
    final center = size.center(Offset(-seatRect.width / 2, -seatRect.height / 2));

    int childId = 0;
    final constraints = BoxConstraints.tight(Size(seatRect.width, seatRect.height));
    final t = Curves.bounceOut.transform(ctrl.value);
    for (final position in positions) {
      layoutChild(childId, constraints);
      final offset = Offset(
        seatRect.left   position.dx * seatRect.width,
        seatRect.top   position.dy * seatRect.height);
      positionChild(childId, Offset.lerp(center, offset, t)!);
      childId  ;
    }
  }

  @override
  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => false;
}

Matrix4 sizeToRect(Size src, Rect dst, {BoxFit fit = BoxFit.contain, Alignment alignment = Alignment.center}) {
  FittedSizes fs = applyBoxFit(fit, src, dst.size);
  double scaleX = fs.destination.width / fs.source.width;
  double scaleY = fs.destination.height / fs.source.height;
  Size fittedSrc = Size(src.width * scaleX, src.height * scaleY);
  Rect out = alignment.inscribe(fittedSrc, dst);

  return Matrix4.identity()
    ..translate(out.left, out.top)
    ..scale(scaleX, scaleY);
}
  • Related