Flutter:当使用新的 Draggable 将鼠标悬停在 DragTarget 上时,DragTarget 显示错误的数据

Flutter: DragTarget shows wrong data when hovering over it with a new Draggable

我是开发新手,从 web 开发开始,虽然现在使用 Flutter 已经有大约 2 个月了,这意味着我仍然学到很多东西 - 所以请耐心等待。

我目前正在处理定期拖放 table。 我已经有了一个工作版本,玩家可以在正确的位置放置一个元素(只有一个 DragTarget 接受 Draggable)。但是,我现在想制作一个高级版本,其中每个 DragTarget 接受每个 Draggable 并显示 dropped 元素的一些信息。

我的问题是: 我可以将 DraggableElementTile 放在每个“空”DragTarget 上(如我所愿),但是当我将鼠标悬停在已经“有数据”的 DragTargets 之一上时,它会将文本更改为最后添加的文本(到不同的 DragTarget ).所以Draggable的数据并没有“绑定”到DragTarget,但是我找不到解决方法。

我还知道,在这段代码中,行中下一个元素的数据在 onAccept 时显示在 DragTarget 中。我的完整代码不会发生这种情况,也许我在这里删除了一些东西。或者它指出了解决方案?

附带说明:最终,将检查元素在 table 中的位置是否正确,因此 DragTarget 需要携带正确设置的信息(如在我的初始版本中)。

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable & DragTarget',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List elementData = [
    {
      "key": "1x1",
      "atomicNumber": 1,
      "element": "Wasserstoff",
      "symbol": "H",
      "group": 1,
      "period": 1,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    },
    {
      "key": "1x2",
      "atomicNumber": 3,
      "element": "Lithium",
      "symbol": "Li",
      "group": 1,
      "period": 2,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    },
    {
      "key": "1x3",
      "atomicNumber": 11,
      "element": "Natrium",
      "symbol": "Na",
      "group": 1,
      "period": 3,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    },
    {
      "key": "1x4",
      "atomicNumber": 19,
      "element": "Kalium",
      "symbol": "K",
      "group": 1,
      "period": 4,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    }
  ];
  int j = 0;
  List<Widget> _elements;
  List shuffledElements;
  int tableRows = 4;
  int tableCols = 1;
  String key;
  int index;
  var tmpElement;
  bool accepting = false;
  bool successfulDrop = false;
  bool correctDrop = false;

  List shuffleElements() {
    var random = Random();
    shuffledElements = List.from(elementData);
    for (var i = shuffledElements.length - 1; i > 0; i--) {
      var n = random.nextInt(i + 1);
      var temp = shuffledElements[i];
      shuffledElements[i] = shuffledElements[n];
      shuffledElements[n] = temp;
    }
    return shuffledElements;
  }

  void nextElement() {
    setState(() {
      if (j < shuffledElements.length - 1) {
        j++;
      } else {}
    });
  }

  List<Widget> getElements() {
    if (_elements != null) {
      return _elements;
    }

    _elements = [];
    for (var j = 0; j < tableCols; j++) {
      for (var i = 0; i < tableRows; i++) {
        key = '${j + 1}x${i + 1}';
        index = elementData.indexWhere((e) => e.containsValue(key));

        if (!index.isNegative) {
          tmpElement = elementData[index];
          _elements.add(elementDragTarget(tmpElement));
        } else {}
      }
    }
    return _elements;
  }

  @override
  void initState() {
    shuffleElements();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white38,
      appBar: AppBar(title: Text('Drag and Drop')),
      body: Column(
        children: [
          Container(
            color: Colors.white38,
            height: MediaQuery.of(context).size.height * 0.7,
            child: GridView.count(
              crossAxisCount: tableRows,
              scrollDirection: Axis.horizontal,
              children: getElements(),
            ),
          ),
          Draggable(
            data: shuffledElements[j],
            child: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
            feedback: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
          ),
        ],
      ),
    );
  }

//problem: if I hover over tiles that already show data
// it changes to last data
  Widget elementDragTarget(tmpElement) {
    return DragTarget(
      onWillAccept: (data) {
        if (tmpElement['successfulDrop'] == true) {
          tmpElement['accepting'] = false;
          return false;
        } else {
          setState(() {
            tmpElement['accepting'] = true;
          });
          return true;
        }
      },
      onAccept: (data) {
        setState(() {
          tmpElement['successfulDrop'] = true;

          if (shuffledElements[j]["atomicNumber"] ==
              tmpElement['atomicNumber']) {
            tmpElement['correctDrop'] = true;
            tmpElement['accepting'] = false;
          } else {
            tmpElement['correctDrop'] = false;
            tmpElement['accepting'] = false;
          }
        });
        nextElement();
      },
      onLeave: (data) {
        setState(() {
          tmpElement['accepting'] = false;
        });
        return false;
      },
      builder: (context, acceptedData, rejectedData) {
        return buildElementTileInGrid(tmpElement);
      },
    );
  }

  //show in grid onAccept
  Container buildElementTileInGrid(tmpElement) {
    accepting = tmpElement['accepting'];
    successfulDrop = tmpElement['successfulDrop'];
    correctDrop = tmpElement['correctDrop'];

    return Container(
      padding: EdgeInsets.all(4),
      margin: EdgeInsets.all(4),
      decoration: BoxDecoration(
        border: Border.all(
          width: 4,
          color: accepting == true ? Colors.teal : Colors.transparent,
        ),
        color: Colors.white38,
      ),
      child: successfulDrop == true
          ? Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Text(shuffledElements[j]['atomicNumber'].toString()),
                Text(shuffledElements[j]['symbol']),
              ],
            )
          : Container(),
    );
  }
}

//draggable
class DraggableElementTile extends StatelessWidget {
  const DraggableElementTile({
    Key key,
    @required this.shuffledElements,
    @required this.j,
  }) : super(key: key);

  final List shuffledElements;
  final int j;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.teal,
      padding: EdgeInsets.all(12),
      margin: EdgeInsets.all(8),
      height: 100,
      width: 80,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(
            shuffledElements[j]['symbol'],
            style: TextStyle(fontSize: 14),
          ),
          Text(
            shuffledElements[j]['element'],
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(fontSize: 14),
          ),
        ],
      ),
    );
  }
}

感谢任何有用的想法。

编辑:我想我需要将我想要显示的数据保存在一个新的列表中,但是当我尝试实现它时仍然遇到了困难。

我设法通过使用 getElements() 中的模型创建初始 elementData 的深层副本(称为 elementDataCopy)使其工作。然后我可以用 DragTargetonAccept 中的 Draggable 中删除的元素的数据覆盖数据,从而导致预期的行为:

import 'dart:math';

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable & DragTarget',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class ElementModel {
  ElementModel({
    this.key,
    this.atomicNumber,
    this.element,
    this.symbol,
    this.group,
    this.period,
    this.droppedKey,
    this.accepting,
    this.successfulDrop,
    this.correctDrop,
    this.answer,
  });
  String key;
  int atomicNumber;
  String element;
  String symbol;
  int group;
  int period;
  String droppedKey = '';
  bool accepting = false;
  bool successfulDrop = false;
  bool correctDrop = false;
  String answer = '';
}

class _MyHomePageState extends State<MyHomePage> {
  List elementData = [
    {
      "key": "1x1",
      "atomicNumber": 1,
      "element": "Wasserstoff",
      "symbol": "H",
      "group": 1,
      "period": 1
    },
    {
      "key": "1x2",
      "atomicNumber": 3,
      "element": "Lithium",
      "symbol": "Li",
      "group": 1,
      "period": 2
    },
    {
      "key": "1x3",
      "atomicNumber": 11,
      "element": "Natrium",
      "symbol": "Na",
      "group": 1,
      "period": 3
    },
    {
      "key": "1x4",
      "atomicNumber": 19,
      "element": "Kalium",
      "symbol": "K",
      "group": 1,
      "period": 4
    }
  ];

  int j = 0;
  List<Widget> _elements;
  List shuffledElements;
  int tableRows = 4;
  int tableCols = 1;
  String key;
  int index;
  var tmpElement;
  bool accepting = false;
  bool successfulDrop = false;
  bool correctDrop = false;

  var droppedItem;

  List droppedItems = [];
  int droppedItemIndex;
  List shuffledElementsCopy;
  List elementDataCopy;

  List shuffleElements() {
    var random = Random();
    shuffledElements = List.from(elementData);
    for (var i = shuffledElements.length - 1; i > 0; i--) {
      var n = random.nextInt(i + 1);
      var temp = shuffledElements[i];
      shuffledElements[i] = shuffledElements[n];
      shuffledElements[n] = temp;
    }
    return shuffledElements;
  }

  void nextElement() {
    setState(() {
      if (j < shuffledElements.length - 1) {
        j++;
      } else {}
    });
  }

  List<Widget> getElements() {
    if (_elements != null) {
      return _elements;
    }

    elementDataCopy = elementData
        .map((element) => ElementModel(
              key: element['key'],
              atomicNumber: element['atomicNumber'],
              element: element['element'],
              symbol: element['symbol'],
              group: element['group'],
              period: element['period'],
              accepting: element['accepting'],
              successfulDrop: element['successfulDrop'],
              correctDrop: element['correctDrop'],
              droppedKey: element['droppedKey'],
              answer: element['answer'],
            ))
        .toList();
    _elements = [];
    for (var c = 0; c < tableCols; c++) {
      for (var r = 0; r < tableRows; r++) {
        key = '${c + 1}x${r + 1}';
        index = elementDataCopy.indexWhere((e) => e.key.contains(key));

        if (!index.isNegative) {
          tmpElement = elementDataCopy[index];
          _elements.add(elementDragTarget(tmpElement));
        } else {}
      }
    }
    return _elements;
  }

  @override
  void initState() {
    shuffleElements();
    shuffledElementsCopy = List.from(shuffledElements);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white38,
      appBar: AppBar(title: Text('Drag and Drop')),
      body: Column(
        children: [
          Container(
            color: Colors.white38,
            height: MediaQuery.of(context).size.height * 0.7,
            child: GridView.count(
              crossAxisCount: tableRows,
              scrollDirection: Axis.horizontal,
              children: getElements(),
            ),
          ),
          Draggable(
            data: shuffledElements[j],
            child: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
            feedback: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
            childWhenDragging: Container(
              margin: EdgeInsets.all(8),
              height: 100,
              width: 80,
              color: Colors.blueGrey,
            ),
          ),
        ],
      ),
    );
  }

  Widget elementDragTarget(tmpElement) {
    return DragTarget(
      onWillAccept: (data) {
        if (tmpElement.successfulDrop == true) {
          tmpElement.accepting = false;
          return false;
        } else {
          setState(() {
            tmpElement.accepting = true;
          });
          return true;
        }
      },
      onAccept: (data) {
        setState(() {
          tmpElement.successfulDrop = true;

          if (shuffledElements[j]["atomicNumber"] == tmpElement.atomicNumber) {
            tmpElement.correctDrop = true;
            tmpElement.accepting = false;

            shuffledElementsCopy[j]['answer'] = 'correct';
          } else {
            tmpElement.correctDrop = false;
            tmpElement.accepting = false;
            shuffledElementsCopy[j]['answer'] = 'wrong';
          }

          tmpElement.droppedKey = shuffledElements[j]['key'] + 'dropped';
          shuffledElementsCopy[j]['droppedKey'] = tmpElement.droppedKey;
          droppedItems.add(shuffledElements[j]);
          droppedItemIndex = droppedItems.indexWhere(
              (e) => e['droppedKey'] == shuffledElements[j]['key'] + 'dropped');
          droppedItem = droppedItems[droppedItemIndex];

          tmpElement.symbol = droppedItem['symbol'];
          tmpElement.atomicNumber = droppedItem['atomicNumber'];
          tmpElement.element = droppedItem['element'];
          tmpElement.group = droppedItem['group'];
          tmpElement.period = droppedItem['period'];
          tmpElement.answer = droppedItem['answer'];
        });
        nextElement();
      },
      onLeave: (data) {
        setState(() {
          tmpElement.accepting = false;
        });
        return false;
      },
      builder: (context, acceptedData, rejectedData) {
        return buildElementTileInGrid(tmpElement);
      },
    );
  }

  //show in grid onAccept
  Container buildElementTileInGrid(tmpElement) {
    accepting = tmpElement.accepting;
    successfulDrop = tmpElement.successfulDrop;
    correctDrop = tmpElement.correctDrop;

    return Container(
      padding: EdgeInsets.all(4),
      margin: EdgeInsets.all(4),
      decoration: BoxDecoration(
        border: Border.all(
          width: 4,
          color: accepting == true ? Colors.teal : Colors.transparent,
        ),
        color: Colors.white38,
      ),
      child: successfulDrop == true
          ? Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Text(tmpElement.atomicNumber.toString()),
                Text(tmpElement.symbol),
              ],
            )
          : Container(),
    );
  }
}

//draggable
class DraggableElementTile extends StatelessWidget {
  const DraggableElementTile({
    Key key,
    @required this.shuffledElements,
    @required this.j,
  }) : super(key: key);

  final List shuffledElements;
  final int j;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.teal,
      padding: EdgeInsets.all(12),
      margin: EdgeInsets.all(8),
      height: 100,
      width: 80,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(
            shuffledElements[j]['symbol'],
            style: TextStyle(fontSize: 14),
          ),
          Text(
            shuffledElements[j]['element'],
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(fontSize: 14),
          ),
        ],
      ),
    );
  }
}