0

I'm looking for a way to move a block on the page, and, if I overlap another block, the second should move the other way.

On my example, I have a game board (6*6 tiles) and init it with some blocks (Pieces) on some offsets.

I thought it should be a good idea to have all pieces as independants draggable widgets, and the game board as a controller to manage the game state.

So, when I drag a block, it call a game board function to check if there is a side block and move it the other way.

But it doesn't work, no matter what I tried, because each block has it's own setState() because of Gesture management, and if I ran a setState() on the GameBoard, the Gesture in progress is killed because all blocks are redrawed...

Is there a good way to achieve this ?

Thanks !

My snippet (use an external library "package:align_positioned/align_positioned.dart") :

import 'dart:math';

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

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Scaffold(
          appBar: AppBar(title: Center(child: Text('Flutter Demo'))),
          body: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                GameBoard(),
                Padding(
                    padding: EdgeInsets.only(bottom: 4),
                    child: SizedBox(
                        height: 64,
                        width: double.infinity,
                        child: Container(
                            margin: EdgeInsets.all(8),
                            decoration: BoxDecoration(
                                border: Border.all(
                                    color: Colors.white.withOpacity(0.2)),
                                borderRadius: BorderRadius.circular(8)),
                            child: MaterialButton(
                                color: Colors.grey.withOpacity(0.2),
                                onPressed: () {},
                                child: Text('start')))))
              ])),
    );
  }
}

class GameBoard extends StatefulWidget {
  GameBoard({Key key}) : super(key: key);

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

class _GameBoardState extends State<GameBoard> {
  List<GamePiece> pieces = [];

  void pieceMoveInProgress(Offset offset, String direction, double delta) {
    GamePiece sidePiece;
    if (direction == 'left') {
      sidePiece = getPiece(offset.dx - 1, offset.dy);
    } else {
      sidePiece = getPiece(offset.dx + 1, offset.dy);
    }
    if (sidePiece != null) {
      // sidePiece.move(delta * -1);
      // How to move sidePiece ? failed to find a way that works with "setState" too...
    }
  }

  GamePiece getPiece(double x, double y) {
    for (var piece in this.pieces) {
      if (piece.x == x && piece.y == y) {
        return piece;
      }
    }
    return null;
  }

  @override
  void initState() {
    super.initState();
    pieces = [
      GamePiece(x: 0.0, y: 0.0, moveInProgress: pieceMoveInProgress),
      GamePiece(x: 1.0, y: 1.0, moveInProgress: pieceMoveInProgress),
      GamePiece(x: 1.0, y: 2.0, moveInProgress: pieceMoveInProgress),
      GamePiece(x: 6.0, y: 2.0, moveInProgress: pieceMoveInProgress),
      GamePiece(x: 5.0, y: 2.0, moveInProgress: pieceMoveInProgress),
    ];
  }

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    double root = size.width;

    return Expanded(
        child: AspectRatio(
            aspectRatio: 1 / 1,
            child: Center(
                child: Container(
                    margin: EdgeInsets.all(8),
                    decoration: BoxDecoration(
                        border: Border.all(
                            color: Colors.cyan.withOpacity(0.4), width: 1),
                        borderRadius: BorderRadius.circular(24)),
                    width: root,
                    height: root,
                    child: Container(
                        child: Stack(key: UniqueKey(), children: pieces))))));
  }
}

class GamePiece extends StatefulWidget {
  final double x;
  final double y;
  final moveInProgress;

  GamePiece({Key key, this.x, this.y, this.moveInProgress})
      : super(key: key);

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

class _GamePieceState extends State<GamePiece> {
  double x;
  double y;
  double _dragStart;
  double currentX = 0.0;

  void initState() {
    super.initState();
    this.x = widget.x;
    this.y = widget.y;
    this.currentX = 0.0;
  }

  void onPanDown(DragDownDetails details) {
    _dragStart = details.localPosition.dx;
  }

  void onPanUpdate(DragUpdateDetails details, double elementSize) {
    final double minDelta = x > 0 ? elementSize * -1 : 0;
    final double maxDelta = x < 6 ? elementSize : 0;
    final double globalDelta =
        min(maxDelta, max(minDelta, details.localPosition.dx - _dragStart));

    if (globalDelta != this.currentX) {
      setState(() {
        this.currentX = globalDelta;
      });
      final String direction = globalDelta > 0 ? 'right' : 'left';
      widget.moveInProgress(Offset(x, y), direction, globalDelta);
    }
    // should event emit move((x, y), direction) when drag end
  }

  Widget build(BuildContext context) {
    return AlignPositioned(
        alignment: FractionalOffset(x / 6, y / 6),
        dx: this.currentX,
        child: Container(
            child: FractionallySizedBox(
          widthFactor: 1 / 7,
          heightFactor: 1 / 7,
          child: new LayoutBuilder(
              builder: (BuildContext context, BoxConstraints constraints) {
            return GestureDetector(
                onPanDown: onPanDown,
                onPanUpdate: (details) {
                  onPanUpdate(details, constraints.maxWidth);
                },
                child: Container(
                    padding: EdgeInsets.all(3),
                    decoration: BoxDecoration(
                        color: Colors.red.withOpacity(0.5),
                        border: Border.all(color: Colors.red, width: 1),
                        borderRadius:
                            BorderRadius.circular(constraints.maxWidth / 2))));
          }),
        )));
  }
}
Doubidou
  • 1,573
  • 3
  • 18
  • 35
  • what you mean by *"the Gesture in progress is killed"*? – pskink Feb 12 '21 at 07:59
  • Hmm I'm not sure between "it's killed" or "it starts a exploding loop" ; but, it's the piece setState that move itself when I drag, and if the GameBoard setState is called concurrently, it doesn't move anymore – Doubidou Feb 12 '21 at 08:02

0 Answers0