如何在颤动中创建一个可移动的小部件,使其停留在被拖动到的位置

How to create a movable widget in flutter such that is stays at the position it is dragged to

我如何在 flutter 中创建一个 movable/draggable 小部件,使其保持在被拖动到的位置。我尝试使用可拖动小部件,但被包裹在可拖动 returns 中的小部件回到原来的位置释放拖动后的位置。

As you can see in this GIF that the draggable object returns back to its original position. How to make it stay there

您需要使用某种状态管理来管理拖动项的位置。在下面的代码示例中,我使用了 Flutter Hooks useState.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

const imgData =
    'data:image/jpeg;base64,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';

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        body: DragArea(
          child: Image.network(imgData),
        ),
      ),
    ),
  );
}

class DragArea extends HookWidget {
  final Widget child;

  const DragArea({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final position = useState(Offset(100, 100));
    return Stack(
      children: [
        Positioned(
          left: position.value.dx,
          top: position.value.dy,
          child: Draggable(
            feedback: child,
            childWhenDragging: Opacity(
              opacity: .3,
              child: child,
            ),
            onDragEnd: (details) => position.value = details.offset,
            child: child,
          ),
        )
      ],
    );
  }
}

有状态版本

根据 anikait 的要求

class StatefulDragArea extends StatefulWidget {
  final Widget child;

  const StatefulDragArea({Key key, this.child}) : super(key: key);

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

class _DragAreaStateStateful extends State<StatefulDragArea> {
  Offset position = Offset(100, 100);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned(
          left: position.dx,
          top: position.dy,
          child: Draggable(
            feedback: widget.child,
            childWhenDragging: Opacity(
              opacity: .3,
              child: widget.child,
            ),
            onDragEnd: (details) => setState(() => position = details.offset),
            child: widget.child,
          ),
        )
      ],
    );
  }
}

除了四处拖动对象之外,您还可以借助 GestureDetector 使其可缩放。我将 GestureDetector 应用于主堆栈,这样您就可以在屏幕上的任何位置捏合缩放 in/out。它使您更容易看到自己在做什么。


HookWidget 版本

class DragArea extends HookWidget {
  final Widget child;

  const DragArea({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final position = useState(Offset(100, 100));
    final prevScale = useState(1.0);
    final scale = useState(1.0);
    return GestureDetector(
      onScaleUpdate: (details) => scale.value = prevScale.value * details.scale,
      onScaleEnd: (_) => prevScale.value = scale.value,
      child: Stack(
        children: [
          Positioned.fill(
              child: Container(color: Colors.amber.withOpacity(.4))),
          Positioned(
            left: position.value.dx,
            top: position.value.dy,
            child: Draggable(
              maxSimultaneousDrags: 1,
              feedback: Transform.scale(
                scale: scale.value,
                child: child,
              ),
              childWhenDragging: Opacity(
                opacity: .3,
                child: Transform.scale(
                  scale: scale.value,
                  child: child,
                ),
              ),
              onDragEnd: (details) => position.value = details.offset,
              child: Transform.scale(
                scale: scale.value,
                child: child,
              ),
            ),
          )
        ],
      ),
    );
  }
}

StatefulWidget 版本

class StatefulDragArea extends StatefulWidget {
  final Widget child;

  const StatefulDragArea({Key key, this.child}) : super(key: key);

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

class _DragAreaStateStateful extends State<StatefulDragArea> {
  Offset position = Offset(100, 100);
  double prevScale = 1;
  double scale = 1;

  void updateScale(double zoom) => setState(() => scale = prevScale * zoom);
  void commitScale() => setState(() => prevScale = scale);
  void updatePosition(Offset newPosition) =>
      setState(() => position = newPosition);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onScaleUpdate: (details) => updateScale(details.scale),
      onScaleEnd: (_) => commitScale(),
      child: Stack(
        children: [
          Positioned.fill(
              child: Container(color: Colors.amber.withOpacity(.4))),
          Positioned(
            left: position.dx,
            top: position.dy,
            child: Draggable(
              maxSimultaneousDrags: 1,
              feedback: widget.child,
              childWhenDragging: Opacity(
                opacity: .3,
                child: widget.child,
              ),
              onDragEnd: (details) => updatePosition(details.offset),
              child: Transform.scale(
                scale: scale,
                child: widget.child,
              ),
            ),
          ),
        ],
      ),
    );
  }
}