Search code examples
flutterflutter-layoutgesture

How to make crossword type puzzle in flutter using GridView or TableView


I got a task in which I had to make a something like crossword puzzle. First of all, I will show you the exact image which I want to achieve.

enter image description here

I have tried using many possible ways like

  • tried using GridView and Table widgets provided in flutter.

  • Tried putting GridView/Table inside GestureDetector

but the problem is that I can't get the word on which the user dragged with his finger. The alphabets and the correct word is coming from server-side. Also when the user dragged on some alphabets then if words match then I have to make an oval shape on the correct words and so there could be many words so many oval shapes. This means that how can I make the oval shape?

Using Positioned or some other tricks?

I searched for any packages in flutter which could help me but unfortunately, I didn't find any.


Solution

  • Okay as promised I have an answer for you I want apologize for how messy it is. It's getting really late here and I wanted to get this to you tonight. Now this might not be this best way but it works and you could definitely modularize some parts of my code into their own functions. You are probably going to want to test this as I'm sure it is breakable at this point and add conditions as necessary. There seems like there should be an easier way to do this but I am could not find one so this is what I went with.

    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,
                      ),
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      }
    

    Pleasure working with you hope your project goes well. I tried to clean up all the unnecessary print statements sorry if I missed any.