Flutter's TweenAnimationBuilder Tween animation reverts back when Tween is run again
bluenote
Posted on May 28, 2023
Hi,
I am creating Match-3 game in flutter app, I have used TweenAnimationBuilder for exchanging two adjacent tiles spots.
The source code is as below:
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
class Tile extends StatelessWidget {
List mBitmap = [
Image.asset('assets/images/games/match-3/tile0.jpg'),
Image.asset('assets/images/games/match-3/tile1.jpg'),
Image.asset('assets/images/games/match-3/tile2.jpg'),
Image.asset('assets/images/games/match-3/tile3.jpg'),
];
var mBitmapDestroy = Image.asset('assets/images/games/match-3/heart.jpg');
Image image = Image.asset("");
late int value;
late int row;
late int col;
Tile({required this.value, required this.row, required this.col, Key? key})
: super(key: key);
Image? getImage() {
switch (value) {
case 0:
return mBitmap[0];
case 1:
return mBitmap[1];
case 2:
return mBitmap[2];
case 3:
return mBitmap[3];
case 4:
return mBitmapDestroy;
}
}
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
class TileMatchingGamePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const TileMatchingGameStatePage();
}
}
class TileMatchingGameStatePage extends StatefulWidget {
const TileMatchingGameStatePage({Key? key}) : super(key: key);
@override
State<TileMatchingGameStatePage> createState() =>
_TileMatchingGameStatePage();
}
class _TileMatchingGameStatePage extends State<TileMatchingGameStatePage>
with SingleTickerProviderStateMixin {
List<List<int>> board = [
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
[
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
Random().nextInt(4),
],
];
late Animation<Offset> _animation;
late AnimationController _controller;
Offset _offset = Offset.zero;
List<List<Tile>> tiles = List.generate(
7,
(i) => List.generate(7, (j) => Tile(value: 0, row: i, col: j)),
);
late final mTileWidth;
late final mTileHeight;
@override
void initState() {
super.initState();
mTileWidth = MediaQuery.of(context).size.width / 7;
mTileHeight = mTileWidth;
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_animation = Tween<Offset>(begin: Offset.zero, end: Offset.zero)
.animate(_controller);
_animation.addListener(() {
setState(() {
// 현재 오프셋을 애니메이션 값으로 업데이트
_offset = _animation.value;
});
});
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 7; j++) {
switch (board[i][j]) {
case 0:
tiles[i][j] = Tile(value: 0, row: i, col: j);
break;
case 1:
tiles[i][j] = Tile(value: 1, row: i, col: j);
break;
case 2:
tiles[i][j] = Tile(value: 2, row: i, col: j);
break;
case 3:
tiles[i][j] = Tile(value: 3, row: i, col: j);
break;
case 4:
tiles[i][j] = Tile(value: 4, row: i, col: j);
break;
}
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void swapTiles(int i, int j, int k, int l) {
setState(() {
var temp = tiles[i][j];
tiles[i][j] = tiles[k][l];
tiles[k][l] = temp;
var temp2 = board[i][j];
board[i][j] = board[k][l];
board[k][l] = temp2;
});
}
// 드래그한 타일의 인덱스
int? dragI;
int? dragJ;
// 인접한 타일의 인덱스
int? swapI;
int? swapJ;
@override
Widget build(BuildContext context) {
return GridView.builder(
shrinkWrap: true,
itemCount: 7 * 7,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
),
itemBuilder: (BuildContext context, int index) {
int i = index ~/ 7;
int j = index % 7;
Offset offset = Offset.zero;
if (i == dragI && j == dragJ) {
// 드래그한 타일이면 인접한 타일 방향으로 이동
if (swapI != null && swapJ != null) {
offset = Offset(
((swapJ! - dragJ!) * mTileWidth).toDouble(),
((swapI! - dragI!) * mTileHeight).toDouble(),
);
}
} else if (i == swapI && j == swapJ) {
// 인접한 타일이면 드래그한 타일 방향으로 이동
if (dragI != null && dragJ != null) {
offset = Offset(
((dragJ! - swapJ!) * mTileWidth).toDouble(),
((dragI! - swapI!) * mTileHeight).toDouble(),
);
}
}
// 애니메이션 적용
return TweenAnimationBuilder<Offset>(
tween: Tween(begin: Offset.zero, end: offset),
duration: Duration(milliseconds: 300),
builder: (context, value, child) {
return Transform.translate(
offset: value,
child: child,
);
},
child: GestureDetector(
// 가로 방향으로 드래그할 때 콜백 함수
onHorizontalDragUpdate: (details) {
// 드래그한 타일의 인덱스 설정
dragI = i;
dragJ = j;
// 드래그한 방향에 따라 인접한 타일의 인덱스 설정
if (details.delta.dx > 0) {
// 오른쪽으로 드래그하면 오른쪽 타일과 교환
swapI = i;
swapJ = j + 1;
} else if (details.delta.dx < 0) {
// 왼쪽으로 드래그하면 왼쪽 타일과 교환
swapI = i;
swapJ = j - 1;
}
// 인접한 타일이 유효한 범위에 있으면 교환
if (swapI != null &&
swapJ != null &&
swapI! >= 0 &&
swapI! < 7 &&
swapJ! >= 0 &&
swapJ! < 7) {
swapTiles(dragI!, dragJ!, swapI!, swapJ!);
}
},
onVerticalDragUpdate: (details) {
// 드래그한 타일의 인덱스 설정
dragI = i;
dragJ = j;
// 드래그한 방향에 따라 인접한 타일의 인덱스 설정
if (details.delta.dy > 0) {
swapI = i + 1;
swapJ = j;
} else if (details.delta.dy < 0) {
swapI = i - 1;
swapJ = j;
}
// 인접한 타일이 유효한 범위에 있으면 교환
if (swapI != null &&
swapJ != null &&
swapI! >= 0 &&
swapI! < 7 &&
swapJ! >= 0 &&
swapJ! < 7) {
swapTiles(dragI!, dragJ!, swapI!, swapJ!);
}
},
child: SizedBox(
width: mTileWidth,
height: mTileHeight,
child: tiles[i][j].getImage(),
),
),
);
},
);
}
}
The source code quoted verbose but enough information how it works. It works well in first dragging, exchanges two tiles well. However, when more times dragging, previous exchange reverts back to original spots so four tiles are moving. I think Tween animation run again and again, the original spots reverts back to, so it runs that way. Any ideas how to repair this operation?
💖 💪 🙅 🚩
bluenote
Posted on May 28, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
flutter Mastering Flutter UI: Essential Tips and Best Practices for Stunning Designs
September 3, 2024