【问题标题】:How to make crossword type puzzle in flutter using GridView or TableView如何使用 GridView 或 TableView 在颤振中制作填字游戏
【发布时间】:2020-07-31 18:55:48
【问题描述】:

我有一个任务,我必须制作一个类似填字游戏的东西。首先,我将向您展示我想要实现的确切图像。

我尝试过很多可能的方法,比如

  • 尝试使用颤振中提供的GridViewTable 小部件。

  • 尝试将GridView/Table 放入GestureDetector

但问题是我无法获得用户用手指拖动的单词。字母表和正确的单词来自服务器端。此外,当用户拖动一些字母时,如果单词匹配,那么我必须在正确的单词上制作一个椭圆形,因此可能会有很多单词这么多椭圆形。这意味着我如何制作椭圆形?

使用Positioned 或其他一些技巧?

我在颤振中搜索了任何可以帮助我的包,但不幸的是,我没有找到。

【问题讨论】:

  • 明天我会给你一些东西我尝试的一切都非常接近但有一件事让它不起作用我几乎得到了它
  • 非常感谢!我等你回答哥们

标签: flutter flutter-layout gesture


【解决方案1】:

目前,我大致找到了这样的解决方案。

class WordSearchGame extends StatefulWidget {
  @override
  GridState createState() {
    return new GridState();
  }
}

class GridState extends State<WordSearchGame> {
  final Set<int> selectedIndexes = Set<int>();
  final key = GlobalKey();
  final Set<_Foo> _trackTaped = Set<_Foo>();
  List<String> alphabet=[
    'I',
    'A',
    'G',
    'M',
    'F',
    'Y',
    'L',
    'I',
    'R',
    'V',
    'P',
    'D',
    'B',
    'R',
    'A',
    'I',
    'N',
    'S',
    'T',
    'O',
    'R',
    'M',
    'E',
    'S',
    'S',
    'T',
    'R',
    'A',
    'T',
    'E',
    'G',
    'Y',
    'E',
    'A',
    'B',
    'W',
    'O',
    'M',
    'G',
    'O',
    'A',
    'L',
    'S',
    'X',
    'S',
    'Q',
    'U',
    'K',
    'H',
    'J',
    'P',
    'M',
    'D',
    'W',
    'S'
  ];
  List<String> words= [
  'ARTHER',
  'GOLDEN',
  'AMADEUS',
  'IDEAS',
  'GOALS',
  'BRAINSTORM'
  ];

  _detectTapedItem(PointerEvent event) {
    final RenderBox box = key.currentContext.findRenderObject();
    final result = BoxHitTestResult();
    Offset local = box.globalToLocal(event.position);
    if (box.hitTest(result, position: local)) {
      for (final hit in result.path) {
        /// temporary variable so that the [is] allows access of [index]
        final target = hit.target;
        if (target is _Foo && !_trackTaped.contains(target)) {
          _trackTaped.add(target);
          _selectIndex(target.index);
        }
      }
    }
  }

  _selectIndex(int index) {
    setState(() {
      selectedIndexes.add(index);

    });
  }


  String word='';
  String getWord(Set<int> selectedIndexes,List<String> alphabet){
    word='';
    selectedIndexes.forEach((element) {word=word+alphabet[element];});
//    word+=alphabet[selectedIndexes.elementAt(0)];
    return word;
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
      onPointerDown: _detectTapedItem,
      onPointerMove: _detectTapedItem,
      onPointerUp: _clearSelection,

      child: GridView.builder(
        key: key,
        itemCount: alphabet.length,
        physics: NeverScrollableScrollPhysics(),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 11,
          childAspectRatio: 0.7,
          crossAxisSpacing: 5.0,
          mainAxisSpacing: 5.0,
        ),
        itemBuilder: (context, index) {
          return Foo(
            index: index,
            child: Container(
              child: Text(alphabet[index]),
              color: selectedIndexes.contains(index) ? Colors.red : Colors.blue,
            ),
          );
        },
      ),
    );
  }

  void _clearSelection(PointerUpEvent event) {

    if(words.contains(getWord(selectedIndexes, alphabet))){
      print("ok");

    }
    else{
      _trackTaped.clear();
      setState(() {
        selectedIndexes.clear();
      });
    }

  }
}

class Foo extends SingleChildRenderObjectWidget {
  final int index;

  Foo({Widget child, this.index, Key key}) : super(child: child, key: key);

  @override
  _Foo createRenderObject(BuildContext context) {
    return _Foo()..index = index;
  }

  @override
  void updateRenderObject(BuildContext context, _Foo renderObject) {
    renderObject..index = index;

  }
}

class _Foo extends RenderProxyBox {
  int index;
}

【讨论】:

    【解决方案2】:

    好的,正如我所承诺的那样,我有一个答案给你,我想为它的混乱程度道歉。这里已经很晚了,我想今晚把这个送给你。现在这可能不是最好的方法,但它确实有效,您绝对可以将我的代码的某些部分模块化为它们自己的函数。您可能会想要测试它,因为我确信它此时是可破坏的,并根据需要添加条件。似乎应该有一种更简单的方法来做到这一点,但我找不到,所以这就是我的选择。

    List<bool> isSelected = [];
      List<String> selectedLetters = [];
      Map<GlobalKey, String> lettersMap;
    
      Offset initialTappedPosition = Offset(0, 0);
      Offset initialPosition = Offset(0, 0);
      Offset finalPosition;
    
      int intialSquare;
      int crossAxisCount = 4; //whether you use GridView or not still need to provide this
      int index = -1;
      bool isTapped = false;
    
      String selectedWord = '';
    
      double width = 50;
      double height = 50;
      Size size;
    
      List<String> letters = [
        'a',
        'b',
        'c',
        'd',
        'e',
        'f',
        'g',
        'h',
        'i',
        'j',
        'k',
        'b',
        'b',
        'b',
        'b',
        'z',
      ];
    
      @override
      void initState() {
        super.initState();
        lettersMap =
            Map.fromIterable(letters, key: (i) => GlobalKey(), value: (i) => i[0]);
        isSelected = List.generate(letters.length, (e) => false);
      }
    
      _determineWord() {
        double differnce;
        int numberOfSquares;
    
        if ((finalPosition.dx - initialPosition.dx) > 20) {
          print('right');
    
          //moved right
          differnce = finalPosition.dx - initialPosition.dx;
          numberOfSquares = (differnce / size.width).ceil();
          for (int i = intialSquare + 1;
              i < (intialSquare + numberOfSquares);
              i++) {
            isSelected[i] = true;
          }
          for (int i = 0; i < isSelected.length; i++) {
            if (isSelected[i]) {
              selectedWord += letters[i];
            }
          }
          print(selectedWord);
        } else if ((initialPosition.dx - finalPosition.dx) > 20) {
          print('left');
    
          // moved left
          differnce = finalPosition.dx + initialPosition.dx;
          numberOfSquares = (differnce / size.width).ceil();
          for (int i = intialSquare - 1;
              i >= (intialSquare - numberOfSquares + 1);
              i--) {
            isSelected[i] = true;
          }
          for (int i = 0; i < isSelected.length; i++) {
            if (isSelected[i]) {
              selectedWord += letters[i];
            }
          }
          print(selectedWord);
        } else if ((finalPosition.dy - initialPosition.dy) > 20) {
          //moved up when moving up/down number of squares numberOfSquares is also number of rows
    
          differnce = finalPosition.dy - initialPosition.dy;
          numberOfSquares = (differnce / size.height).ceil();
    
          for (int i = intialSquare + crossAxisCount;
              i < (intialSquare + (numberOfSquares * crossAxisCount));
              i += 4) {
            isSelected[i] = true;
          }
          for (int i = 0; i < isSelected.length; i++) {
            if (isSelected[i]) {
              selectedWord += letters[i];
            }
          }
    
          print(selectedWord);
        } else if ((initialPosition.dy - finalPosition.dy) > 20) {
          //moved down
          differnce = initialPosition.dy - finalPosition.dy;
          numberOfSquares = (differnce / size.height).ceil();
    
          for (int i = intialSquare - crossAxisCount;
              i > (intialSquare - (numberOfSquares * crossAxisCount));
              i -= 4) {
            isSelected[i] = true;
            print('$i');
          }
          for (int i = isSelected.length - 1; i >= 0; i--) {
            if (isSelected[i]) {
              selectedWord += letters[i];
            }
          }
          print(selectedWord);
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.black,
          body: Stack(
            children: <Widget>[
              Center(
                child: Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: GestureDetector(
                    child: GridView(
                      physics: NeverScrollableScrollPhysics(), //Very Important if
    // you don't have this line you will have conflicting touch inputs and with
    // gridview being the child will win
                      shrinkWrap: true,
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: crossAxisCount,
                        childAspectRatio: 2,
                      ),
                      children: <Widget>[
                        for (int i = 0; i != lettersMap.length; ++i)
                          Listener(
                            child: Container(
                              key: lettersMap.keys.toList()[i],
                              child: Text(
                                lettersMap.values.toList()[i],
                                textAlign: TextAlign.center,
                                style: TextStyle(
                                  color: Colors.amber,
                                  fontSize: 18,
                                ),
                              ),
                            ),
                            onPointerDown: (PointerDownEvent event) {
    
                              final RenderBox renderBox = lettersMap.keys
                                  .toList()[i]
                                  .currentContext
                                  .findRenderObject();
                              size = renderBox.size;
                              setState(() {
                                isSelected[i] = true;
                                intialSquare = i;
                              });
                            },
                          ),
                      ],
                    ),
                    onTapDown: (TapDownDetails details) {
                      //User Taps Screen
                      // print('Global Position: ${details.globalPosition}');
                      setState(() {
                        initialPosition = Offset(
                          details.globalPosition.dx - 25,
                          details.globalPosition.dy - 25,
                        );
                        initialTappedPosition = Offset(
                          details.globalPosition.dx - 25,
                          details.globalPosition.dy - 25,
                        );
                        isTapped = true;
                      });
                      // print(initialPosition);
                    },
                    onVerticalDragUpdate: (DragUpdateDetails details) {
                      // print('${details.delta.dy}');
                      setState(() {
                        if (details.delta.dy < 0) {
                          initialTappedPosition = Offset(initialTappedPosition.dx,
                              initialTappedPosition.dy + details.delta.dy);
                          height -= details.delta.dy;
                        } else {
                          height += details.delta.dy;
                        }
                        finalPosition = Offset(
                          details.globalPosition.dx - 25,
                          details.globalPosition.dy - 25,
                        );
                      });
                    },
                    onHorizontalDragUpdate: (DragUpdateDetails details) {
                      // print('${details.delta.dx}');
                      setState(() {
                        if (details.delta.dx < 0) {
                          initialTappedPosition = Offset(
                            initialTappedPosition.dx + details.delta.dx,
                            initialTappedPosition.dy,
                          );
                          width -= details.delta.dx;
                        } else {
                          width += details.delta.dx;
                        }
    
                        finalPosition = Offset(
                          details.globalPosition.dx - 25,
                          details.globalPosition.dy - 25,
                        );
                      });
                    },
                    onHorizontalDragEnd: (DragEndDetails details) {
                      _determineWord();
                    },
                    onVerticalDragEnd: (DragEndDetails details) {
                      _determineWord();
                    },
                  ),
                ),
              ),
              Positioned(
                top: initialTappedPosition.dy,
                left: initialTappedPosition.dx,
                child: Container(
                  height: height,
                  width: width,
                  decoration: ShapeDecoration(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(30),
                      side: BorderSide(
                        color: isTapped ? Colors.blue : Colors.transparent,
                        width: 3.0,
                      ),
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      }
    

    很高兴与您合作,希望您的项目进展顺利。如果我错过了,我试图清理所有不必要的打印语句。

    【讨论】:

    • 非常感谢哥们!这里有一些问题,但您为我提供了正确的路径
    • 我知道在我的测试中存在一些问题,它打印了正确的单词
    • 如果您认为可以接受,请您接受并点赞:)
    【解决方案3】:

    我写了一些可能会给你一个想法的东西。它绝不是一个完整的优质应用程序,它肯定有几个错误。

    首先,我创建了一个WordMarker。这是围绕单词的黄色矩形。

    class WordMarker extends StatelessWidget {
      const WordMarker({
        Key key,
        @required this.rect,
        @required this.startIndex,
        this.color = Colors.yellow,
        this.width = 2.0,
        this.radius = 6.0,
      }) : super(key: key);
    
      final Rect rect;
      final Color color;
      final double width;
      final double radius;
      final int startIndex;
    
      @override
      Widget build(BuildContext context) {
        return Positioned.fromRect(
          rect: rect,
          child: DecoratedBox(
            decoration: BoxDecoration(
              border: Border.all(
                color: color,
                width: width,
              ),
              borderRadius: BorderRadius.circular(radius),
            ),
          ),
        );
      }
    
      WordMarker copyWith({Rect rect}) {
        return WordMarker(
          key: key,
          rect: rect ?? this.rect,
          startIndex: startIndex,
          color: color,
          width: width,
          radius: radius,
        );
      }
    }
    

    注意

    • Rect 结合了大小和偏移量,用于定位正确单词上方的标记。

    然后我们有 WordSearch 小部件,它是拼图板。

    class WordSearch extends StatefulWidget {
      const WordSearch({Key key, this.alphabet, this.words, this.wordsPerLine})
          : super(key: key);
    
      final int wordsPerLine;
      final List<String> alphabet;
      final List<String> words;
    
      @override
      _WordSearchState createState() => _WordSearchState();
    }
    
    class _WordSearchState extends State<WordSearch> {
      final markers = <WordMarker>[];
      int correctAnswers = 0;
      var uniqueLetters;
    
      @override
      void initState() {
        super.initState();
        uniqueLetters = widget.alphabet
            .map((letter) => {'letter': letter, 'key': GlobalKey()})
            .toList();
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            GridView.count(
              crossAxisCount: widget.wordsPerLine,
              children: <Widget>[
                for (int i = 0; i != uniqueLetters.length; ++i)
                  GestureDetector(
                    behavior: HitTestBehavior.opaque,
                    onTap: () {
                      setState(() {
                        final key = uniqueLetters[i]['key'];
                        final renderBox = key.currentContext.findRenderObject();
                        final markerRect = renderBox.localToGlobal(Offset.zero,
                                ancestor: context.findRenderObject()) &
                            renderBox.size;
                        if (markers.length == correctAnswers) {
                          addMarker(markerRect, i);
                        } else if (widget.words
                            .contains(pathAsString(markers.last.startIndex, i))) {
                          markers.last = adjustedMarker(markers.last, markerRect);
                          ++correctAnswers;
                        } else {
                          markers.removeLast();
                        }
                      });
                    },
                    child: Center(
                      child: Padding(
                        padding: const EdgeInsets.all(4.0),
                        key: uniqueLetters[i]['key'],
                        child: Text(
                          uniqueLetters[i]['letter'],
                        ),
                      ),
                    ),
                  ),
              ],
            ),
            ...markers,
          ],
        );
      }
    
      void addMarker(Rect rect, int startIndex) {
        markers.add(WordMarker(
          rect: rect,
          startIndex: startIndex,
        ));
      }
    
      WordMarker adjustedMarker(WordMarker originalMarker, Rect endRect) {
        return originalMarker.copyWith(
            rect: originalMarker.rect.expandToInclude(endRect));
      }
    
      String pathAsString(int start, int end) {
        final isHorizontal =
            start ~/ widget.wordsPerLine == end ~/ widget.wordsPerLine;
        final isVertical = start % widget.wordsPerLine == end % widget.wordsPerLine;
    
        String result = '';
    
        if (isHorizontal) {
          result = widget.alphabet.sublist(start, end + 1).join();
        } else if (isVertical) {
          for (int i = start;
              i < widget.alphabet.length;
              i += widget.wordsPerLine) {
            result += widget.alphabet[i];
          }
        }
    
        return result;
      }
    }
    

    注意

    • 小部件从其父级接收字母表,并为每个字母分配一个GlobalKey。这使得以后在用户点击它时识别这个字母成为可能,并获得它的偏移量和大小。
    • 请参阅markerRect 以了解Rect 计算。另请参阅 adjustedMarker() 以了解在点击下一个字母时 Rect 如何扩展。
    • 正在使用StackGridView,但GestureDetector 单独包装每个字母。
    • 每个标记都与其第一个字母的索引一起保存,因此在创建它和点击的下一个字母之间的路径时可以轻松实现。请注意,我认为这不是最佳解决方案。
    • 在功能方面 - 该板可让您一个接一个地点击任意两个字母。如果他们都给出了正确答案的路径 - 它被圈起来。否则圆圈被删除。我希望它可以帮助您掌握代码。

    您还可以打开一个包含两个小部件的项目,它应该可以轻松运行。我已经用你发送的单词和字母运行了它:

    WordSearch(
              wordsPerLine: 11,
              alphabet: [
                'I',
                'A',
                'G',
                'M',
                'F',
                'Y',
                'L',
                'I',
                'R',
                'V',
                'P',
                'D',
                'B',
                'R',
                'A',
                'I',
                'N',
                'S',
                'T',
                'O',
                'R',
                'M',
                'E',
                'S',
                'S',
                'T',
                'R',
                'A',
                'T',
                'E',
                'G',
                'Y',
                'E',
                'A',
                'B',
                'W',
                'O',
                'M',
                'G',
                'O',
                'A',
                'L',
                'S',
                'X',
                'S',
                'Q',
                'U',
                'K',
                'H',
                'J',
                'P',
                'M',
                'D',
                'W',
                'S'
              ],
              words: [
                'ARTHER',
                'GOLDEN',
                'AMADEUS',
                'IDEAS',
                'GOALS',
                'BRAINSTORM'
              ],
            ),
    

    【讨论】:

    • 我认为这种行为只能通过 Listener 而不是 GestureDetector。
    猜你喜欢
    • 2020-08-26
    • 1970-01-01
    • 2021-08-15
    • 1970-01-01
    • 1970-01-01
    • 2020-10-13
    • 1970-01-01
    • 2021-12-04
    • 2019-01-30
    相关资源
    最近更新 更多