当添加另一个可拖动小部件时,如何防止堆栈中的可拖动小部件(使用 Matrix4 Transform 创建)更改其位置?

How to prevent draggable widgets (created with Matrix4 Transform) in a Stack from changing their positions when another draggable widget is added?

我正在尝试创建包裹在 Stack 小部件中的可拖动和可调整大小的小部件(Matrix4 Gesture Detector 小部件)。我能够按照我希望的方式拖动和调整小部件的大小,但问题是,当我以编程方式将另一个小部件添加到 Stack 的子级时,之前定位的所有可拖动小部件都会恢复并与新添加的可拖动小部件一起回到中心。

假设 Stack 最初有 2 个可拖动的小部件,我们也将它们定位,

当我通过点击 Add 按钮以编程方式添加另一个小部件时,位置丢失并且每个可拖动的小部件都像这样出现在中心,

如何解决此问题并防止小部件不恢复到中心?这是代码。

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider.value(value: DummyProvider())
      ],
    child: MaterialApp(
      home: MyApp(),
    ),
    )
  );
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    DummyProvider dummyProvider =
        Provider.of<DummyProvider>(context, listen: true);
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Container(
              height: Constants.deviceHeight! * .6,
              color: Colors.red,
              child: Stack(
                children: List.generate(
                    dummyProvider.list.length,
                    (index) => Drag(
                          text: Text(dummyProvider.list[index].text,
                              style: TextStyle(fontSize: 40)),
                        )),
              ),
            ),
            ElevatedButton(
              onPressed: () {
                dummyProvider.addToList(DraggableText(
                    id: DateTime.now().millisecondsSinceEpoch.toString(),
                    text: String.fromCharCodes(List.generate(
                        5, (index) => Random().nextInt(33) + 89))));
              },
              child: Text("Add"),
            )
          ],
        ),
      ),
    );
  }
}

DraggableTextclass,

class DraggableText {
  String id = '';
  String text = '';
  FontWeight fontWeight = FontWeight.normal;
  FontStyle fontStyle = FontStyle.normal;
  TextDecoration fontDecoration = TextDecoration.none;
  Matrix4 position = Matrix4.identity();
  DraggableText({required this.id, required this.text});
}

拥有所有可拖动小部件的提供者,

class DummyProvider extends ChangeNotifier {
  List<DraggableText> list = [];
  String currentText = '';

  void addToList(DraggableText text) {
    list.add(text);
    notifyListeners();
  }
}

使用 Matrix4 手势检测器包的可拖动文本小部件,

class Drag extends StatelessWidget {
  final Text text;
  const Drag({required this.text});

  @override
  Widget build(BuildContext context) {
    final ValueNotifier<Matrix4> notifier = ValueNotifier(Matrix4.identity());
    return MatrixGestureDetector(
      onMatrixUpdate: (m, tm, sm, rm) {
        notifier.value = m;
        print("$m $tm $sm $rm");
      },
      child: AnimatedBuilder(
        animation: notifier,
        builder: (ctx, child) {
          return Transform(
            transform: notifier.value,
            child: Center(
  

        child: Stack(
            children: <Widget>[
              Transform.scale(
                scale: 1,
                origin: Offset(0.0, 0.0),
                child: GestureDetector(
                    child:
                        Container(color: Colors.blueAccent, child: text)),
              ),
            ],
          ),
        ),
      );
    },
  ),
);}}

在无状态中试试这个:

class Drag extends StatelessWidget {
  final Text text;

  const Drag({Key? key, required this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {

或有状态:

class Drag extends StatefulWidget {
  final Text text;

  Drag({
    Key? key,
    required this.text,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => DragState();
}

class DragState extends State<Drag> {

将其用于:

Drag(
  key: UniqueKey(),
  text: 

生成器的问题,它正在刷新包含位置的列表,每个 setState 都会创建新列表。我在模型 class 中添加了额外的 属性 来处理它,并在测试问题时在通知程序中添加了一些额外的方法,这可能在将来有所帮助。


void main() {
  runApp(MultiProvider(
    providers: [ChangeNotifierProvider.value(value: DummyProvider())],
    child: const MaterialApp(
      home: MyApp(),
    ),
  ));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: LayoutBuilder(
        builder: (context, constraints) => SafeArea(
          child: Column(
            children: [
              Container(
                height: constraints.maxHeight * .6,
                color: Colors.red,
                child: Consumer<DummyProvider>(
                  builder: (context, value, child) => Stack(
                    children: value.list
                        .map(
                          (d) => Drag(
                            key: ValueKey(d.id),
                            notifier: d.notifier!,
                            callBack: (
                              Matrix4 matrix,
                            ) {
                              // print("${matrix.row0.a}  ${matrix.row1.a}");
                              print(matrix);
                            },
                            text: Text(
                              d.text,
                              style: TextStyle(fontSize: 40),
                            ),
                          ),
                        )
                        .toList(),
                  ),
                ),
              ),
              ElevatedButton(
                onPressed: () {
                  final provider = context.read<DummyProvider>();
                  provider.addToList(
                    DraggableText(
                      id: DateTime.now().millisecondsSinceEpoch.toString(),
                      text: String.fromCharCodes(
                        List.generate(5, (index) => Random().nextInt(33) + 89),
                      ),
                    ),
                  );
                },
                child: Text("Add"),
              )
            ],
          ),
        ),
      ),
    );
  }
}

class Drag extends StatelessWidget {
  final Text text;
  final ValueNotifier<Matrix4> notifier;
  final Function callBack;

  const Drag({
    Key? key,
    required this.text,
    required this.callBack,
    required this.notifier,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MatrixGestureDetector(
      onMatrixUpdate: (m, tm, sm, rm) {
        notifier.value = m;
        // print("$m $tm $sm $rm");
        callBack(notifier.value);
      },
      child: AnimatedBuilder(
        animation: notifier,
        builder: (ctx, child) {
          print("${notifier.value.row0.a}  ${notifier.value.row1.a}");

          return Transform(
            transform: notifier.value,
            child: Center(
              child: Stack(
                children: <Widget>[
                  Transform.scale(
                    scale: 1,
                    origin: Offset(0.0, 0.0),
                    child: GestureDetector(
                      child: Container(
                        color: Colors.blueAccent,
                        child: text,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

class DraggableText {
  String id = '';
  String text = '';
  FontWeight fontWeight = FontWeight.normal;
  FontStyle fontStyle = FontStyle.normal;
  TextDecoration fontDecoration = TextDecoration.none;
  Matrix4 position = Matrix4.identity();
  ValueNotifier<Matrix4>? notifier;
  DraggableText({
    required this.id,
    required this.text,
  }) {
    this.notifier = this.notifier ?? ValueNotifier(Matrix4.identity());
  }
}

class DummyProvider extends ChangeNotifier {
  List<DraggableText> list = [];
  String currentText = '';

  void addToList(DraggableText text) {
    list.add(text);
    notifyListeners();
  }

  void updatePosition(int index, Matrix4 position) {
    list[index].position = position;
    notifyListeners();
  }

  void updatePositionByItem(DraggableText text, Matrix4 position) {
    list.firstWhere((element) => element == text).position = position;
    notifyListeners();
  }
}