【问题标题】:Build a hall plan in Flutter. What is the best solution?在 Flutter 中构建大厅平面图。什么是最好的解决方案?
【发布时间】:2022-01-12 17:47:10
【问题描述】:

我想在Flutter中做一个互动大厅平面图,但是不知道如何正确实现。

要点:

  • 计划应在不损失质量的情况下扩展。
  • 单击时该地点的视图应略有变化

第一种选择:是做成一个大尺寸的Canvas,放到InteractiveViewer中,但是不知道能不能在tap上改变已经绘制好的部分?

第二个选项: 是将每个地方制作成带有 onTap 事件的 SVG,并将其全部放入 Stack 小部件中。然后再次在 InteractiveViewer 中。但是计划上大概有500个座位,不知道手机能不能处理这样的场景缩放?

也许还有其他解决方案?非常感谢您的建议,我对 Flutter 的经验仍然很少。

【问题讨论】:

  • 为什么需要interactiveViewer?你想要双指缩放功能吗?
  • 是的,我需要捏缩放...但我认为 InteractiveViewer 是唯一一种我可以实现它的方法...
  • 所以使用InteractiveViewer 作为父母,CustomMultiChildLayout 作为孩子
  • 我没听说过。谢谢,将阅读文档
  • 请阅读。为什么不使用具有绝对定位子小部件的 Stack 小部件?就像 CSS 中的 <div id="parent" style ="position: relative"> <div id="seat1" style="postion: absolute; top: Xpx; left: Ypx "><img>/div> <div id="seatN" style="postion: absolute; top: Xpx; left: Ypx "><img>/div> </div> 一样

标签: flutter svg canvas stack interactive


【解决方案1】:

这可以通过 InteractiveViewer 和 GridView 来实现:

对于任何形状的地方,你都可以用二维数组来绘制,比如

  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,

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

【讨论】:

  • 但是大厅中的位置可以是随机位置,而不是网格视图。它们可以通过屏幕对面的弧形路径放置
  • 用二维数组来塑造你的大厅怎么样?
  • 大厅可以是任何形状,像这样:explosion.ru/pic/12.jpg,可能我没有正确描述任务
【解决方案2】:

小时候尝试InteractiveViewerCustomMultiChildLayout(请注意,通过AnimationController,您可以轻松地为CinemaDelegate 内部的座位设置动画):

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);
}

【讨论】:

  • 谢谢,但这对我来说真的很难 ;) ...这是我的第一个应用程序,我什至无法理解发生了什么。在网络中这很容易:制作一个 SVG,将所有座位作为独立对象放置,并在每个座位上添加事件侦听器,并带有 css 动画。
  • 我想我在 Fultter 的知识还不够)......只是稍微了解一下逻辑。根本不懂动画,根本不懂CinemaHallDelegate课。我了解这个类的目的,但可以找出座位如何定位、动画如何工作、什么是 sizeToRect 和 performLayout。我会回到这个,在两个月内,我有它可以更好地学习 Flutter。谢谢!
  • performLayout 当框架需要调整/定位CustomMultiChildLayout 的子级并定位每个座位final List&lt;Offset&gt; positions 使用列表时调用 - 你尝试运行它吗?如果是这样,请在 performLayout 中添加一些 print 并查看日志
  • 我应该将此代码放在 MaterialApp 小部件中吗?
  • 是的,就像任何其他小部件一样:MaterialApp > Scaffold > CinemaHall,更多:docs.flutter.dev/get-started/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-10
  • 1970-01-01
  • 2012-05-11
  • 2011-10-09
  • 1970-01-01
  • 2023-03-22
  • 2011-12-24
相关资源
最近更新 更多