dart 有没有一种方法可以让“用户”在交互式查看器上放置一个按钮小部件,(就像平面图生成器一样)

db2dz4w8  于 2023-01-15  发布在  其他
关注(0)|答案(1)|浏览(67)

我正在开发一个应用程序,允许用户建立他们的健身房平面图,并将按钮放置在机器将在的地方。
我目前正在使用一个交互式查看器来显示平面布置图。我需要的功能,以创建一个按钮小部件的“用户”长按。
下面是我的当前代码和onLongPressEnd类。

Padding(
              padding: const EdgeInsets.symmetric(vertical: 1),
              child: Center(
                child: GestureDetector(
                  onLongPressEnd: ((details) => {

//Code to create button goes here

                      }),
                  child: InteractiveViewer(
                    minScale: 1,
                    maxScale: 2,
                    child: Stack(
                      children: [
                        Image.asset(
                            'Assets/Lift View Images/room_layout_1.png'),
                          );

有人在互联网上发布了一个几乎相同的问题,并创建了一个完整的解决方案,问题是,因为他的解决方案,InteractiveViewer小部件已被添加,所以他的所有代码是过时的,不能复制到一个较新的版本。
https://medium.com/@alexander.arendar/dragging-zooming-and-placing-object-on-indoor-map-with-flutter-67667ef415ec
总之,我需要的功能,为用户创建预定义的小部件按下页面。
完整代码https://github.com/CokeMango/mapview_iteration_1.git
我花了几个小时来理解这个文档解决方案https://medium.com/@alexander.arendar/dragging-zooming-and-placing-object-on-indoor-map-with-flutter-67667ef415ec,但始终没有弄清楚如何用交互式查看器小部件来实现它,而且作为Flutter的新手,我不能完全复制它。
我也在网上搜索了一段时间,最多我找到了我已经拥有的,那是一个没有任何功能的可缩放可滚动图像查看器。

zf9nrax1

zf9nrax11#

这里你可以看到两种方法:FloorPlanWithFixedButtons添加固定大小的“按钮”(无论InteractiveViewer当前使用的缩放因子是什么),而FloorPlanWithScaledButtons直接向InteractiveViewer添加“按钮”,以便在放大/缩小时自动缩放
如果您需要可拖动的“按钮”,请选中FloorPlanWithScaledDraggableButtons小部件

带固定按钮的楼层平面图

class FloorPlanWithFixedButtons extends StatefulWidget {
  @override
  State<FloorPlanWithFixedButtons> createState() => _FloorPlanWithFixedButtonsState();
}

class _FloorPlanWithFixedButtonsState extends State<FloorPlanWithFixedButtons> with TickerProviderStateMixin {
  int animatedIndex = -1;
  late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 250));
  final chips = <ChipEntry>[];
  final transformationController = TransformationController();
  int labelNumber = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Padding(
          padding: EdgeInsets.all(8.0),
          child: Text('1) long press on the floor plan below to add a new button\n'),
        ),
        Expanded(
          child: ClipRect(
            child: GestureDetector(
              onLongPressStart: _addButton,
              child: Stack(
                children: [
                  InteractiveViewer(
                    minScale: 1,
                    maxScale: 5,
                    constrained: false,
                    transformationController: transformationController,
                    // https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg
                    child: Image.asset('images/640px-Sample_Floorplan.jpg'),
                  ),
                  CustomMultiChildLayout(
                    delegate: FloorPlanDelegate(
                      chips: chips,
                      transformationController: transformationController,
                    ),
                    children: [
                      for (int index = 0; index < chips.length; index++)
                        LayoutId(id: index, child: _button(index)),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    );
  }

  Widget _button(int index) {
    final button = Chip(
      backgroundColor: Colors.orange,
      side: const BorderSide(width: 1, color: Colors.black12),
      elevation: 4,
      onDeleted: () async {
        setState(() {
          animatedIndex = index;
        });
        await controller.reverse(from: 1.0);
        setState(() {
          chips.removeAt(index);
          animatedIndex = -1;
        });
      },
      label: InkWell(
        onTap: () => print('button |${chips[index].label}| at index $index pressed'),
        child: Text(chips[index].label),
      ),
    );
    return index == animatedIndex? ScaleTransition(scale: controller, child: button) : button;
  }

  void _addButton(LongPressStartDetails details) async {
    setState(() {
      animatedIndex = chips.length;
      final chipEntry = ChipEntry(
        offset: transformationController.toScene(details.localPosition),
        label: 'btn #$labelNumber'
      );
      chips.add(chipEntry);
      labelNumber++;
    });
    await controller.forward(from: 0.0);
    animatedIndex = -1;
  }
}

class FloorPlanDelegate extends MultiChildLayoutDelegate {
  FloorPlanDelegate({
    required this.chips,
    required this.transformationController,
  }) : super(relayout: transformationController); // NOTE: this is very important

  final List<ChipEntry> chips;
  final TransformationController transformationController;

  @override
  void performLayout(ui.Size size) {
    // print('performLayout $size');
    int id = 0;
    final constraints = BoxConstraints.loose(size);
    final matrix = transformationController.value;
    for (final chip in chips) {
      final size = layoutChild(id, constraints);
      final offset = MatrixUtils.transformPoint(matrix, chip.offset) - size.center(Offset.zero);
      positionChild(id, offset);
      id++;
    }
  }

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

class ChipEntry {
  ChipEntry({
    required this.offset,
    required this.label,
  });

  Offset offset;
  final String label;
}

带缩放按钮的平面布置图

class FloorPlanWithScaledButtons extends StatefulWidget {
  @override
  State<FloorPlanWithScaledButtons> createState() => _FloorPlanWithScaledButtonsState();
}

class _FloorPlanWithScaledButtonsState extends State<FloorPlanWithScaledButtons> with TickerProviderStateMixin {
  int animatedIndex = -1;
  late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 250));
  final chips = <ChipEntry>[];
  final transformationController = TransformationController();
  int labelNumber = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Padding(
          padding: EdgeInsets.all(8.0),
          child: Text('1) long press on the floor plan below to add a new button\n'),
        ),
        Expanded(
          child: ClipRect(
            child: GestureDetector(
              onLongPressStart: _addButton,
              child: InteractiveViewer(
                minScale: 1,
                maxScale: 5,
                constrained: false,
                transformationController: transformationController,
                child: Stack(
                  children: [
                    // https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg
                    Image.asset('images/640px-Sample_Floorplan.jpg'),
                    ...chips.mapIndexed(_positionedButton),
                  ],
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }

  Widget _positionedButton(int index, ChipEntry chip) {
    final child = Chip(
      backgroundColor: Colors.orange,
      side: const BorderSide(width: 1, color: Colors.black12),
      elevation: 4,
      onDeleted: () async {
        setState(() {
          animatedIndex = index;
        });
        await controller.reverse(from: 1.0);
        setState(() {
          chips.removeAt(index);
          animatedIndex = -1;
        });
      },
      label: InkWell(
        onTap: () => print('button |${chip.label}| at index $index pressed'),
        child: Text(chip.label),
      ),
    );

    return Positioned(
      left: chip.offset.dx,
      top: chip.offset.dy,
      child: FractionalTranslation(
        translation: const Offset(-0.5, -0.5),
        child: index == animatedIndex? ScaleTransition(scale: controller, child: child) : child,
      ),
    );
  }

  void _addButton(LongPressStartDetails details) async {
    setState(() {
      animatedIndex = chips.length;
      final chipEntry = ChipEntry(
        offset: transformationController.toScene(details.localPosition),
        label: 'btn #$labelNumber'
      );
      chips.add(chipEntry);
      labelNumber++;
    });
    await controller.forward(from: 0.0);
    animatedIndex = -1;
  }
}

带缩放可拖动按钮的楼层平面

class FloorPlanWithScaledDraggableButtons extends StatefulWidget {
  @override
  State<FloorPlanWithScaledDraggableButtons> createState() => _FloorPlanWithScaledDraggableButtonsState();
}

class _FloorPlanWithScaledDraggableButtonsState extends State<FloorPlanWithScaledDraggableButtons> {
  final chips = <ChipEntry>[];
  final transformationController = TransformationController();
  int labelNumber = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Padding(
          padding: EdgeInsets.all(8.0),
          child: Text('1) long press on the floor plan below to add a new button\n'
          '2) long press on the added button to drag it'),
        ),
        Expanded(
          child: ClipRect(
            child: GestureDetector(
              onLongPressStart: _addButton,
              child: InteractiveViewer(
                minScale: 1,
                maxScale: 5,
                constrained: false,
                transformationController: transformationController,
                child: Stack(
                  children: [
                    // https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg
                    Image.asset('images/640px-Sample_Floorplan.jpg'),
                    ...chips.mapIndexed(_button),
                  ],
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }

  Widget _button(int index, ChipEntry chip) {
    return DraggableChip(
      chip: chip,
      onTap: () => print('button |${chip.label}| at index $index pressed'),
      onDrag: (delta) => setState(() => chip.offset += _scaled(delta)),
      onDeleted: () => setState(() => chips.removeAt(index)),
    );
  }

  Offset _scaled(Offset delta) {
    return delta / transformationController.value.getMaxScaleOnAxis();
  }

  void _addButton(LongPressStartDetails details) {
    setState(() {
      final chipEntry = ChipEntry(
        offset: transformationController.toScene(details.localPosition),
        label: 'btn #$labelNumber'
      );
      chips.add(chipEntry);
      labelNumber++;
    });
  }
}

class DraggableChip extends StatefulWidget {
  const DraggableChip({
    Key? key,
    required this.chip,
    this.onTap,
    this.onDrag,
    this.onDeleted,
  }) : super(key: key);

  final ChipEntry chip;
  final VoidCallback? onTap;
  final Function(Offset)? onDrag;
  final VoidCallback? onDeleted;

  @override
  State<DraggableChip> createState() => _DraggableChipState();
}

class _DraggableChipState extends State<DraggableChip> with SingleTickerProviderStateMixin {
  late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 250));
  bool drag = false;
  Offset position = Offset.zero;
  double scale = 0;

  @override
  void initState() {
    super.initState();
    controller.forward();
  }

  @override
  void didUpdateWidget(covariant DraggableChip oldWidget) {
    super.didUpdateWidget(oldWidget);
    scale = controller.value = 1;
  }

  @override
  Widget build(BuildContext context) {
    final child = RawChip(
      selected: drag,
      showCheckmark: false,
      selectedColor: Colors.teal,
      backgroundColor: Colors.orange,
      side: const BorderSide(width: 1, color: Colors.black12),
      elevation: 4,
      onDeleted: () async {
        await controller.reverse();
        widget.onDeleted?.call();
      },
      label: GestureDetector(
        onLongPressStart: (d) => setState(() {
          drag = true;
          position = d.globalPosition;
        }),
        onLongPressMoveUpdate: (d) {
          widget.onDrag?.call(d.globalPosition - position);
          position = d.globalPosition;
        },
        onLongPressEnd: (d) => setState(() => drag = false),
        child: InkWell(
          onTap: widget.onTap,
          child: Text(widget.chip.label),
        ),
      ),
    );
    return Positioned(
      left: widget.chip.offset.dx,
      top: widget.chip.offset.dy,
      child: FractionalTranslation(
        translation: const Offset(-0.5, -0.5),
        child: ScaleTransition(
        scale: controller,
          child: child,
        ),
      ),
    );
  }
}

相关问题